1
2
3
4
5
6
7
8
9
10
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
41
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
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):
122
123 - def set(self, start, duration, repeat, enabled=True):
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
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
151 "Return start time as a string with nice sort qualities"
152 return Time.LocalDateTime(self.start)
153
155 "Return a string version of the startProductionState"
156 return self.convertProdState(self.startProductionState)
157
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
214
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
269
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
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
338 base[2:6] = time.localtime(self.start)[2:6]
339 base = time.mktime(base)
340
341
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
355
356 security.declareProtected(ZEN_MAINTENANCE_WINDOW_EDIT, 'setProdState')
357 - def setProdState(self, target, 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
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
394
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
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