Package ZenModel :: Module MaintenanceWindow
[hide private]
[frames] | no frames]

Source Code for Module ZenModel.MaintenanceWindow

  1  ########################################################################### 
  2  # 
  3  # This program is part of Zenoss Core, an open source monitoring platform. 
  4  # Copyright (C) 2007, Zenoss Inc. 
  5  # 
  6  # This program is free software; you can redistribute it and/or modify it 
  7  # under the terms of the GNU General Public License version 2 as published by 
  8  # the Free Software Foundation. 
  9  # 
 10  # For complete information please visit: http://www.zenoss.com/oss/ 
 11  # 
 12  ########################################################################### 
 13   
 14  __doc__="""MaintenanceWindow 
 15   
 16  A scheduled period of time during which a window is under maintenance. 
 17   
 18  $Id:$""" 
 19   
 20  __version__ = "$Revision: 1.7 $"[11:-2] 
 21   
 22  DAY_SECONDS = 24*60*60 
 23  WEEK_SECONDS = 7*DAY_SECONDS 
 24   
 25  import time 
 26  import Globals 
 27   
 28  from AccessControl import ClassSecurityInfo 
 29  from ZenossSecurity import * 
 30  from AccessControl import Permissions 
 31  from ZenModelRM import ZenModelRM 
 32  from Products.ZenRelations.RelSchema import * 
 33  from Products.ZenUtils import Time 
 34   
35 -def lastDayPreviousMonth(seconds):
36 parts = list(time.localtime(seconds)) 37 # use day 1 of this month 38 parts[2] = 1 39 # and go back DAY_SECONDS 40 return time.mktime(parts) - DAY_SECONDS
41
42 -def addMonth(secs, dayOfMonthHint=0):
43 base = list(time.localtime(secs)) 44 # add a month 45 base[1] += 1 46 # year wrap 47 if base[1] == 13: 48 base[0] += 1 49 base[1] = 1 50 # Check for the case Jan 31 becomes March 3 51 # in that case, force it back to Feb 28 52 53 # first, remember the month 54 month = base[1] 55 if dayOfMonthHint: 56 base[2] = dayOfMonthHint 57 # normalize 58 base = list(time.localtime(time.mktime(base))) 59 # if the month changed, walk back to the end of the previous month 60 if base[1] != month: 61 return lastDayPreviousMonth(time.mktime(base)) 62 return time.mktime(base)
63 64
65 -class MaintenanceWindow(ZenModelRM):
66 67 name = None 68 start = None 69 started = None 70 duration = 60 71 repeat = 'Never' 72 startProductionState = 300 73 stopProductionState = -99 74 stopProductionStates = {} 75 enabled = True 76 skip = 1 77 78 _properties = ( 79 {'id':'name', 'type':'string', 'mode':'w'}, 80 {'id':'start', 'type':'int', 'mode':'w'}, 81 {'id':'started', 'type':'int', 'mode':'w'}, 82 {'id':'duration', 'type':'int', 'mode':'w'}, 83 {'id':'repeat', 'type':'string', 'mode':'w'}, 84 {'id':'skip', 'type':'int', 'mode':'w'}, 85 ) 86 87 factory_type_information = ( 88 { 89 'immediate_view' : 'maintenanceWindowDetail', 90 'actions' : 91 ( 92 { 'id' : 'status' 93 , 'name' : 'Status' 94 , 'action' : 'maintenanceWindowDetail' 95 , 'permissions' : ( Permissions.view, ) 96 }, 97 { 'id' : 'viewHistory' 98 , 'name' : 'Modifications' 99 , 'action' : 'viewHistory' 100 , 'permissions' : ( Permissions.view, ) 101 }, 102 ) 103 }, 104 ) 105 106 backCrumb = 'deviceManagement' 107 #backCrumb = 'deviceOrganizerManage' 108 _relations = ( 109 ("productionState", ToOne(ToManyCont, "Products.ZenModel.MaintenanceWindowable", "maintenanceWindows")), 110 ) 111 112 security = ClassSecurityInfo() 113 114 115 REPEAT = "Never/Daily/Every Weekday/Weekly/Monthly/First Sunday of the Month".split('/') 116 NEVER, DAILY, EVERY_WEEKDAY, WEEKLY, MONTHLY, FSOTM = REPEAT 117
118 - def __init__(self, id):
119 ZenModelRM.__init__(self, id) 120 self.start = time.time() 121 self.enabled = False
122
123 - def set(self, start, duration, repeat, enabled=True):
124 self.start = start 125 self.duration = duration 126 self.repeat = repeat 127 self.enabled = enabled
128
129 - def displayName(self):
130 if self.name is not None: return self.name 131 else: return self.id
132
133 - def repeatOptions(self):
134 "Provide the list of REPEAT options" 135 return self.REPEAT
136
137 - def getTargetId(self):
138 return self.target().id
139 140 # Nice methods used by the GUI for presentation purposes
141 - def niceDuration(self):
142 """Return a human readable version of the duration in 143 days, hours, minutes""" 144 return Time.Duration(self.duration*60)
145
146 - def niceStartDate(self):
147 "Return a date in the format use by the calendar javascript" 148 return Time.USDate(self.start)
149
150 - def niceStartDateTime(self):
151 "Return start time as a string with nice sort qualities" 152 return Time.LocalDateTime(self.start)
153
154 - def niceStartProductionState(self):
155 "Return a string version of the startProductionState" 156 return self.convertProdState(self.startProductionState)
157
158 - def niceStopProductionState(self):
159 "Return a string version of the stopProductionState" 160 if self.stopProductionState == -99: 161 return 'Original' 162 return self.convertProdState(self.stopProductionState)
163
164 - def niceStartHour(self):
165 return time.localtime(self.start)[3]
166
167 - def niceStartMinute(self):
168 return time.localtime(self.start)[4]
169 170 security.declareProtected(ZEN_MAINTENANCE_WINDOW_EDIT, 171 'manage_editMaintenanceWindow')
172 - def manage_editMaintenanceWindow(self, 173 startDate='', 174 startHours='', 175 startMinutes='00', 176 durationDays='0', 177 durationHours='00', 178 durationMinutes='00', 179 repeat='Never', 180 startProductionState=300, 181 stopProductionState=-99, 182 enabled=True, 183 skip=1, 184 REQUEST=None, 185 **kw):
186 "Update the maintenance window from GUI elements" 187 def makeInt(v, fieldName, minv=None, maxv=None, acceptBlanks=True): 188 if acceptBlanks: 189 if isinstance(v, str): 190 v = v.strip() 191 v = v or '0' 192 try: 193 v = int(v) 194 if minv != None and v < minv: 195 raise ValueError 196 if maxv != None and v > maxv: 197 raise ValueError 198 except ValueError: 199 if minv == None and maxv == None: 200 msg = '%s must be an integer.' % fieldName 201 elif minv != None and maxv != None: 202 msg = '%s must be between %s and %s inclusive.' % ( 203 fieldName, minv, maxv) 204 elif minv != None: 205 msg = '%s must be at least %s' % (fieldName, minv) 206 else: 207 msg = '%s must be no greater than %s' % (fieldName, maxv) 208 msgs.append(msg) 209 v = None 210 return v
211 212 msgs = [] 213 # startHours, startMinutes come from menus. No need to catch 214 # ValueError on the int conversion. 215 startHours = int(startHours) 216 startMinutes = int(startMinutes) 217 self.enabled = bool(enabled) 218 import re 219 try: 220 month, day, year = re.split('[^ 0-9]', startDate) 221 except ValueError: 222 msgs.append("Date needs three number fields") 223 day = int(day) 224 month = int(month) 225 year = int(year) 226 if not msgs: 227 t = time.mktime((year, month, day, startHours, startMinutes, 228 0, 0, 0, -1)) 229 if not msgs: 230 durationDays = makeInt(durationDays, 'Duration days', 231 minv=0) 232 durationHours = makeInt(durationHours, 'Duration hours', 233 minv=0, maxv=23) 234 durationMinutes = makeInt(durationMinutes, 'Duration minutes', 235 minv=0, maxv=59) 236 if not msgs: 237 duration = (durationDays * (60*24) + 238 durationHours * 60 + 239 durationMinutes) 240 241 if duration < 1: 242 msgs.append('Duration must be at least 1 minute.') 243 if msgs: 244 if REQUEST: 245 if REQUEST.has_key('message'): 246 del REQUEST['message'] 247 REQUEST['message'] = '; '.join(msgs) 248 else: 249 self.start = t 250 self.duration = duration 251 self.repeat = repeat 252 self.startProductionState = startProductionState 253 self.stopProductionState = stopProductionState 254 self.skip = skip 255 now = time.time() 256 if self.started and self.nextEvent(now) < now: 257 self.end() 258 if REQUEST: 259 REQUEST['message'] = 'Saved Changes' 260 if REQUEST: 261 return self.callZenScreen(REQUEST)
262 263
264 - def nextEvent(self, now):
265 "Return the time of the next begin() or end()" 266 if self.started: 267 return self.started + self.duration * 60 268 # ok, so maybe "now" is a little late: start anything that 269 # should have been started by now 270 return self.next(now - self.duration * 60 + 1)
271 272 273 security.declareProtected(ZEN_VIEW, 'breadCrumbs')
274 - def breadCrumbs(self, terminator='dmd'):
275 "fix up breadCrumbs to add a link back to the Manage tab" 276 bc = super(MaintenanceWindow, self).breadCrumbs(terminator) 277 url, display = bc[-2] 278 url += "/" + self.backCrumb 279 bc.insert(-1, (url, 'manage')) 280 return bc
281 282
283 - def next(self, now = None):
284 """From Unix time_t now value, return next time_t value 285 for the window to start, or None""" 286 287 if not self.enabled: 288 return None 289 290 if now is None: 291 now = time.time() 292 293 if now < self.start: 294 return self.start 295 296 if self.repeat == self.NEVER: 297 if now > self.start: 298 return None 299 return self.start 300 301 elif self.repeat == self.DAILY: 302 skip = (DAY_SECONDS * self.skip) 303 last = self.start + ((now - self.start) // skip * skip) 304 return last + skip 305 306 elif self.repeat == self.EVERY_WEEKDAY: 307 weeksSince = (now - self.start) // WEEK_SECONDS 308 weekdaysSince = weeksSince * 5 309 # start at the most recent week-even point from the start 310 base = self.start + weeksSince * DAY_SECONDS * 7 311 while 1: 312 dow = time.localtime(base).tm_wday 313 if dow not in (5,6): 314 if base > now and weekdaysSince % self.skip == 0: 315 break 316 weekdaysSince += 1 317 base += DAY_SECONDS 318 assert base >= now 319 return base 320 321 elif self.repeat == self.WEEKLY: 322 skip = (WEEK_SECONDS * self.skip) 323 last = self.start + ((now - self.start) // skip * skip) 324 return last + skip 325 326 elif self.repeat == self.MONTHLY: 327 months = 0 328 m = self.start 329 dayOfMonthHint = time.localtime(self.start).tm_mday 330 while m < now or months % self.skip: 331 m = addMonth(m, dayOfMonthHint) 332 months += 1 333 return m 334 335 elif self.repeat == self.FSOTM: 336 base = list(time.localtime(now)) 337 # Move time to this year/month 338 base[2:6] = time.localtime(self.start)[2:6] 339 base = time.mktime(base) 340 # creep ahead by days until it's the FSOTM 341 # (not the most efficient implementation) 342 count = 0 343 while 1: 344 tm = time.localtime(base) 345 if base > now and 1 <= tm.tm_mday <= 7 and tm.tm_wday == 6: 346 count += 1 347 if count % self.skip == 0: 348 break 349 base += DAY_SECONDS 350 return base 351 raise ValueError('bad value for MaintenanceWindow repeat: %r' %self.repeat)
352
353 - def target(self):
354 return self.productionState().primaryAq()
355 356 security.declareProtected(ZEN_MAINTENANCE_WINDOW_EDIT, 'setProdState')
357 - def setProdState(self, target, state):
358 devices = [] 359 from Products.ZenModel.DeviceOrganizer import DeviceOrganizer 360 if isinstance(target, DeviceOrganizer): 361 for device in target.getSubDevices(): 362 devices.append(device) 363 else: 364 devices.append(target) 365 366 for device in devices: 367 if state == -99: state = self.stopProductionStates[device.id] 368 self.stopProductionStates[device.id] = device.productionState 369 device.setProdState(state)
370 371
372 - def begin(self, now = None):
373 "hook for entering the Maintenance Window: call if you override" 374 self.setProdState(self.target(), self.startProductionState) 375 if not now: 376 now = time.time() 377 self.started = now
378 379
380 - def end(self):
381 "hook for leaving the Maintenance Window: call if you override" 382 self.started = None 383 self.setProdState(self.target(), self.stopProductionState)
384 385
386 - def execute(self, now = None):
387 "Take the next step: either start or stop the Maintenance Window" 388 if self.started: 389 self.end() 390 else: 391 self.begin(now)
392 393 #DeviceMaintenanceWindow = MaintenanceWindow 394 #OrganizerMaintenanceWindow = MaintenanceWindow 395 396 397 if __name__=='__main__': 398 m = MaintenanceWindow('tester') 399 t = time.mktime( (2006, 1, 29, 10, 45, 12, 6, 29, 0) ) 400 P = 60*60*2 401 m.set(t, P, m.NEVER) 402 assert m.next() == None 403 m.set(t, P, m.DAILY) 404 c = time.mktime( (2006, 1, 30, 10, 45, 12, 6, 29, 0) ) 405 assert m.next(t + P + 1) == c 406 m.set(t, P, m.WEEKLY) 407 c = time.mktime( (2006, 2, 5, 10, 45, 12, 6, 36, 0) ) 408 assert m.next(t + 1) == c 409 m.set(t - DAY_SECONDS, P, m.EVERY_WEEKDAY) 410 c = time.mktime( (2006, 1, 30, 10, 45, 12, 7, 30, 0) ) 411 assert m.next(t) == c 412 m.set(t, P, m.MONTHLY) 413 c = time.mktime( (2006, 2, 28, 10, 45, 12, 0, 0, 0) ) 414 assert m.next(t+1) == c 415 t2 = time.mktime( (2005, 12, 31, 10, 45, 12, 0, 0, 0) ) 416 m.set(t2, P, m.MONTHLY) 417 c = time.mktime( (2006, 1, 31, 10, 45, 12, 0, 0, 0) ) 418 c2 = time.mktime( (2006, 2, 28, 10, 45, 12, 0, 0, 0) ) 419 assert m.next(t2+1) == c 420 assert m.next(c+1) == c2 421 c = time.mktime( (2006, 2, 5, 10, 45, 12, 0, 0, 0) ) 422 m.set(t, P, m.FSOTM) 423 assert m.next(t+1) == c 424 425 # skips 426 m.skip = 2 427 m.set(t, P, m.DAILY) 428 c = time.mktime( (2006, 1, 31, 10, 45, 12, 6, 29, 0) ) 429 assert m.next(t + 1) == c 430 431 m.set(t, P, m.WEEKLY) 432 c = time.mktime( (2006, 2, 12, 10, 45, 12, 6, 36, 0) ) 433 assert m.next(t + 1) == c 434 435 m.set(t, P, m.MONTHLY) 436 c = time.mktime( (2006, 3, 29, 10, 45, 12, 6, 36, 0) ) 437 assert m.next(t + 1) == c 438 439 m.set(t - DAY_SECONDS * 2, P, m.EVERY_WEEKDAY) 440 c = time.mktime( (2006, 1, 31, 10, 45, 12, 7, 30, 0) ) 441 assert m.next(t + 1) == c 442 443 c = time.mktime( (2006, 3, 5, 10, 45, 12, 0, 0, 0) ) 444 m.set(t, P, m.FSOTM) 445 assert m.next(t+1) == c 446 447 r = {'test':None} 448 m.manage_editMaintenanceWindow( 449 startDate='01/29/2006', 450 startHours='10', 451 startMinutes='45', 452 durationDays='1', 453 durationHours='1', 454 durationMinutes='1', 455 repeat='Weekly', 456 startProductionState=300, 457 stopProductionState=-99, 458 enabled=True, 459 REQUEST=r) 460 461 if r.has_key('message'): 462 print r['message'] 463 assert not r.has_key('message') 464 assert m.start == t - 12 465 assert m.duration == 24*60+61 466 assert m.repeat == 'Weekly' 467 assert m.startProductionState == 300 468 assert m.stopProductionState == -99 469 470 DeviceMaintenanceWindow = MaintenanceWindow 471 OrganizerMaintenanceWindow = MaintenanceWindow 472