1
2
3
4
5
6
7
8
9
10
11
12
13
14 __doc__='Base Classes for loading gunk in a ZenPack'
15
16 import Globals
17 from Products.ZenReports.ReportLoader import ReportLoader
18 from Products.ZenUtils.Utils import zenPath, binPath
19 from Products.ZenUtils.guid.interfaces import IGUIDManager
20 from Products.Zuul import getFacade
21
22 from zenoss.protocols.jsonformat import from_dict
23 from zenoss.protocols.protobufs.zep_pb2 import EventDetailItemSet, EventDetailItem
24 from zenoss.protocols.services import ServiceResponseError
25
26 import os
27 import json
28 import ConfigParser
29 import subprocess
30 import logging
31 log = logging.getLogger('zen.ZPLoader')
32
33 CONFIG_FILE = 'about.txt'
34 CONFIG_SECTION_ABOUT = 'about'
35
48
56
58 "return the branch after the given directory name"
59 path = filename.split('/')
60 return prefix + '/'.join(path[path.index(directory)+1:])
61
62
64
65 name = "Set This Name"
66
67 - def load(self, pack, app):
68 """Load things from the ZenPack and put it
69 into the app"""
70
71 - def unload(self, pack, app, leaveObjects=False):
72 """Remove things from Zenoss defined in the ZenPack"""
73
74 - def list(self, pack, app):
75 "List the items that would be loaded from the given (unpacked) ZenPack"
76
78 "Run an upgrade on an existing pack"
79
80 from xml.sax import make_parser
81
83
84 name = "Objects"
85
86 - def load(self, pack, app):
102 importer = AddToPack(noopts=True, app=app)
103 importer.options.noindex = True
104 for f in self.objectFiles(pack):
105 log.info("Loading %s", f)
106 importer.loadObjectFromXML(xmlfile=f)
107
108
109 - def parse(self, filename, handler):
113
114
115 - def unload(self, pack, app, leaveObjects=False):
136
137 - def list(self, pack, unused):
139
140
142 def isXml(f): return f.endswith('.xml')
143 return findFiles(pack, 'objects', isXml)
144
145
147
148 name = "Reports"
149
150 - def load(self, pack, app):
156 rl = HookReportLoader(noopts=True, app=app)
157 rl.options.force = True
158 rl.loadDirectory(pack.path('reports'))
159
161 self.unload(pack, app)
162 self.load(pack, app)
163
164 - def list(self, pack, unused):
166
167
169
170 name = "Daemons"
171
172 extensionsToIgnore = ('.svn-base', '.pyc' '~')
174 if 'zenexample' in f:
175 return False
176
177 for ext in self.extensionsToIgnore:
178 if f.endswith(ext):
179 return False
180
181 return True
182
183
186
187
189 """
190 Attempt to generate a conf file for any daemons. Wait for completion.
191 """
192 try:
193 p = subprocess.Popen(binPath('create_sample_config.sh'),
194 stdout=subprocess.PIPE,
195 stderr=subprocess.PIPE,
196 cwd=pack.path())
197 p.wait()
198 except OSError:
199 pass
200
202 """
203 Update conf files for any daemons to account for logfile path
204 differences on the localhost collector.
205 """
206 for fs in findFiles(pack, 'daemons', filter=self.filter):
207 name = fs.rsplit('/', 1)[-1]
208 logpath = pack.About._getLogPath(name).rsplit('/', 1)[0]
209 if logpath != zenPath('log'):
210 try:
211 with open(zenPath('etc', '%s.conf' % name), 'a') as conf:
212 conf.write('logpath %s' % logpath)
213 except IOError:
214
215 pass
216
217 - def load(self, pack, unused):
226
227
229 self.unload(pack, app)
230 self.load(pack, app)
231
232
233 - def unload(self, pack, unused, leaveObjects=False):
239
240 - def list(self, pack, unused):
243
244
246
247 name = "Bin"
248
249 extensionsToIgnore = ('.svn-base', '.pyc' '~')
251 for ext in self.extensionsToIgnore:
252 if f.endswith(ext):
253 return False
254 return True
255
256 - def load(self, pack, unused):
259
261 self.unload(pack, app)
262 self.load(pack, app)
263
264 - def list(self, pack, unused):
267
268
270
271 name = "LibExec"
272
273 extensionsToIgnore = ('.svn-base', '.pyc' '~')
275 for ext in self.extensionsToIgnore:
276 if f.endswith(ext):
277 return False
278 return True
279
280 - def load(self, pack, unused):
283
285 self.unload(pack, app)
286 self.load(pack, app)
287
288 - def list(self, pack, unused):
291
292
294
295 name = "Modeler Plugins"
296
297
298 - def list(self, pack, unused):
301
302
304
305 name = "Skins"
306
307
308 - def load(self, pack, app):
313
314
316 self.unload(pack, app)
317 return self.load(pack, app)
318
319
320 - def unload(self, pack, app, leaveObjects=False):
325
326
327 - def list(self, pack, unused):
329
330
332
333 name = "DataSources"
334
335
336 - def list(self, pack, unused):
337 return [branchAfter(d, 'datasources')
338 for d in findFiles(pack, 'datasources',
339 lambda f: not f.endswith('.pyc') and f != '__init__.py')]
340
341
342
344
345 name = "Libraries"
346
347
348 - def list(self, pack, unused):
349 d = pack.path('lib')
350 if os.path.isdir(d):
351 return [l for l in os.listdir(d)]
352 return []
353
355
356 name = "About"
357
373
374
375 - def load(self, pack, unused):
378
379
382
383
384 - def list(self, pack, unused):
386
388
389 name = "Triggers and Actions"
390
391 - def load(self, pack, app):
392 """
393 Load Notifications and Triggers from an actions.json file
394
395 Given a JSON-formatted configuration located at {zenpack}/actions/actions.json,
396 create or update triggers and notifications specific to this zenpack.
397 When creating or updating, the object is first checked to see whether or
398 not an object exists with the configured guid for notifications or uuid
399 for triggers. If an object is not found, one will be created. During
400 creation, care is taken with regard to the id - integer suffixes will be
401 appended to try to create a unique id. If we do not find a unique id after
402 100 tries, an error will occur. When updating an object, care is taken
403 to not change the name as it may have since been altered by the user (or
404 by this loader adding a suffix).
405
406 """
407 log.debug("ZPTriggerAction: load")
408 import Products.Zuul as Zuul
409 from Products.Zuul.facades import ObjectNotFoundException
410
411 tf = Zuul.getFacade('triggers', app.dmd)
412 guidManager = IGUIDManager(app)
413
414 for conf in findFiles(pack, 'zep',lambda f: f == 'actions.json'):
415
416 import json
417 data = json.load(open(conf, "r"))
418 log.debug("DATA IS: %s" % data)
419
420 triggers = data.get('triggers', [])
421 notifications = data.get('notifications', [])
422
423
424 tf.synchronize()
425 all_names = set(t['name'] for t in tf.getTriggerList())
426
427 for trigger_conf in triggers:
428
429 existing_trigger = guidManager.getObject(trigger_conf['uuid'])
430
431 if existing_trigger:
432 trigger_data = tf.getTrigger(trigger_conf['uuid'])
433 trigger_conf['name'] = trigger_data['name']
434
435 log.info('Existing trigger found, updating: %s' % trigger_conf['name'])
436 tf.updateTrigger(**trigger_conf)
437
438 else:
439
440 test_name = trigger_conf['name']
441 for x in xrange(1,101):
442 if test_name in all_names:
443 test_name = '%s_%d' % (trigger_conf['name'], x)
444 else:
445 log.debug('Found unused trigger name: %s' % test_name)
446 break
447 else:
448
449 raise Exception('Could not find unique name for trigger: "%s".' % trigger_conf['name'])
450
451 log.info('Creating trigger: %s' % test_name)
452 tf.createTrigger(test_name, uuid=trigger_conf['uuid'], rule=trigger_conf['rule'])
453
454
455 for notification_conf in notifications:
456
457 existing_notification = guidManager.getObject(str(notification_conf['guid']))
458
459 if existing_notification:
460 log.info("Existing notification found, updating: %s" % existing_notification.id)
461
462 subscriptions = set(existing_notification.subscriptions + notification_conf['subscriptions'])
463 notification_conf['uid'] = '/zport/dmd/NotificationSubscriptions/%s' % existing_notification.id
464 notification_conf['subscriptions'] = list(subscriptions)
465 notification_conf['name'] = existing_notification.id
466 tf.updateNotification(**notification_conf)
467 else:
468
469
470 test_id = notification_conf['id']
471 for x in xrange(1,101):
472 test_uid = '/zport/dmd/NotificationSubscriptions/%s' % test_id
473
474 try:
475 tf.getNotification(test_uid)
476 except ObjectNotFoundException:
477 break
478
479 test_id = '%s_%d' % (notification_conf['id'], x)
480 else:
481
482 raise Exception('Could not find unique name for notification: "%s".' % notification_conf['id'])
483
484 log.info('Creating notification: %s' % test_id)
485 tf.createNotification(str(test_id), notification_conf['action'], notification_conf['guid'])
486
487 notification_conf['uid'] = '/zport/dmd/NotificationSubscriptions/%s' % test_id
488 tf.updateNotification(**notification_conf)
489
490
492 triggers = facade.getTriggers()
493 guid = None
494 for trigger in triggers:
495 if trigger['name'] == name:
496 guid = trigger['uuid']
497 break
498 if not guid:
499 guid = facade.addTrigger(name)
500 return guid
501
502 - def unload(self, pack, app, leaveObjects=False):
503 """Remove things from Zenoss defined in the ZenPack"""
504 log.debug("ZPTriggerAction: unload")
505
506 - def list(self, pack, app):
507 "List the items that would be loaded from the given (unpacked) ZenPack"
508 log.debug("ZPTriggerAction: list")
509
511 "Run an upgrade on an existing pack"
512 log.debug("ZPTriggerAction: upgrade")
513
514
515 -class ZPZep(ZenPackLoader):
516
517 name = "ZEP"
518
520 data = {}
521 try:
522 with open(conf, "r") as configFile:
523 data = json.load(configFile)
524 except IOError, e:
525
526 log.debug("File could not be opened for reading: %s" % conf)
527 pass
528 return data
529
531 """
532 Load in the Zep configuration file which should be located here:
533 $ZENPACK/zep/zep.conf
534 """
535
536 self.handlers = (EventDetailItemHandler(), )
537 p = pack.path('zep')
538 confFile = os.path.join(p, 'zep.json')
539 data = self._data(confFile)
540
541 return data
542
543 - def load(self, pack, app):
547
548 - def unload(self, pack, app, leaveObjects=False):
552
553 - def list(self, pack, app):
554 data = self._prepare(pack, app)
555 info = []
556 for handler in self.handlers:
557 info.extend(handler.list(data))
558
563
564
565
567 key = 'EventDetailItem'
568
569 - def load(self, configData):
570 """
571 configData is a json dict. This is the entire config structure.
572 """
573 if configData:
574 self.zep = getFacade('zep')
575 items = configData.get(EventDetailItemHandler.key, [])
576 detailItemSet = from_dict(EventDetailItemSet, dict(
577 details = items
578 ))
579 self.zep.addIndexedDetails(detailItemSet)
580
581 - def list(self, configData):
582 if configData:
583 self.zep = getFacade('zep')
584 items = configData.get(EventDetailItemHandler.key, [])
585 info = []
586 for item in items:
587 info.append("Would be adding the following detail to be indexed by ZEP: %s" % item.key)
588 return info
589
590 - def unload(self, configData, leaveObjects):
591 if not leaveObjects and configData:
592 self.zep = getFacade('zep')
593 items = configData.get(EventDetailItemHandler.key, [])
594 for item in items:
595 log.info("Removing the following currently indexed detail by ZEP: %s" % item['key'])
596 try:
597 self.zep.removeIndexedDetail(item['key'])
598 except ServiceResponseError as e:
599 if e.status == 404:
600 log.debug('Indexed detail was previously removed from ZEP')
601 else:
602 log.warning("Failed to remove indexed detail: %s", e.message)
603
604
606 if configData:
607 self.zep = getFacade('zep')
608 items = configData.get(EventDetailItemHandler.key, [])
609 for item in items:
610 log.info("Upgrading the following to be indexed by ZEP: %s" % item)
611 detailItem = from_dict(EventDetailItem, item)
612 self.zep.updateIndexedDetailItem(detailItem)
613