def __init__(self, context): super(TriggersFacade, self).__init__(context) self._guidManager = IGUIDManager(self._dmd) config = getGlobalConfiguration() schema = getUtility(IQueueSchema) self.triggers_service = TriggerServiceClient(config.get('zep_uri', 'http://localhost:8084'), schema) self.notificationPermissions = NotificationPermissionManager() self.triggerPermissions = TriggerPermissionManager()
def setUp(self): # Start a mock server on a random port self.httpd = BaseHTTPServer.HTTPServer(('', 0), MockTriggerServiceHandler) self.port = self.httpd.socket.getsockname()[1] thread = threading.Thread(target=self.httpd.serve_forever) thread.daemon = True thread.start() self.schema = Schema(SCHEMA) # Connect the client to the random port self.client = TriggerServiceClient('http://localhost:%d' % self.port, self.schema)
class TriggerServiceTest(unittest.TestCase): def setUp(self): # Start a mock server on a random port self.httpd = BaseHTTPServer.HTTPServer(('', 0), MockTriggerServiceHandler) self.port = self.httpd.socket.getsockname()[1] thread = threading.Thread(target=self.httpd.serve_forever) thread.daemon = True thread.start() self.schema = Schema(SCHEMA) # Connect the client to the random port self.client = TriggerServiceClient('http://localhost:%d' % self.port, self.schema) def test_get_triggers(self): response, content = self.client.getTriggers() assert response['content-type'] == 'application/x-protobuf' assert content.SerializeToString() == mock_trigger_set.SerializeToString() def test_add_trigger(self): response, content = self.client.addTrigger(mock_trigger) assert response.status == 204 def test_remove_trigger(self): response, content = self.client.removeTrigger(mock_trigger.uuid) assert response.status == 204 def test_get_trigger(self): response, content = self.client.getTrigger(mock_trigger.uuid) assert content.SerializeToString() == mock_trigger.SerializeToString() assert response.status == 200 def test_update_trigger(self): response, content = self.client.updateTrigger(mock_trigger) assert response.status == 204 def test_update_subscriptions(self): response, content = self.client.updateSubscriptions(mock_subscriber_uuid, mock_subscription_set) assert response.status == 204 def tearDown(self): self.httpd.shutdown()
class TriggersFacade(ZuulFacade): def __init__(self, context): super(TriggersFacade, self).__init__(context) self._guidManager = IGUIDManager(self._dmd) config = getGlobalConfiguration() schema = getUtility(IQueueSchema) self.triggers_service = TriggerServiceClient( config.get('zep_uri', 'http://localhost:8084'), schema) self.notificationPermissions = NotificationPermissionManager() self.triggerPermissions = TriggerPermissionManager() def _removeNode(self, obj): """ Remove an object in ZODB. This method was created to provide a hook for unit tests. """ context = aq_parent(obj) return context._delObject(obj.id) def _removeTriggerFromZep(self, uuid): """ Remove a trigger from ZEP. This method was created to provide a hook for unit tests. """ return self.triggers_service.removeTrigger(uuid) def removeNode(self, uid): obj = self._getObject(uid) return self._removeNode(obj) def _setTriggerGuid(self, trigger, guid): """ @param trigger: The trigger object to set the guid on. @type trigger: Products.ZenModel.Trigger.Trigger @param guid: The guid @type guid: str This method was created to provide a hook for unit tests. """ IGlobalIdentifier(trigger).guid = guid def _getTriggerGuid(self, trigger): """ @param trigger: The trigger object in zodb. @type trigger: Products.ZenModel.Trigger.Trigger This method was created to provide a hook for unit tests. """ return IGlobalIdentifier(trigger).guid def _setupTriggerPermissions(self, trigger): """ This method was created to provide a hook for unit tests. """ self.triggerPermissions.setupTrigger(trigger) def synchronize(self): """ This method will first synchronize all triggers that exist in ZEP to their corresponding objects in ZODB. Then, it will clean up notifications and remove any subscriptions to triggers that no longer exist. """ log.debug('SYNC: Starting trigger and notification synchronization.') _, trigger_set = self.triggers_service.getTriggers() zep_uuids = set(t.uuid for t in trigger_set.triggers) zodb_triggers = self._getTriggerManager().objectValues() # delete all triggers in zodb that do not exist in zep. for t in zodb_triggers: if not self._getTriggerGuid(t) in zep_uuids: log.info( 'SYNC: Found trigger in zodb that does not exist in zep, removing: %s' % t.id) self._removeNode(t) zodb_triggers = self._getTriggerManager().objectValues() zodb_uuids = set(self._getTriggerGuid(t) for t in zodb_triggers) # create all triggers in zodb that do not exist in zep. for t in trigger_set.triggers: if not t.uuid in zodb_uuids: log.info( 'SYNC: Found trigger uuid in zep that does not seem to exist in zodb, creating: %s' % t.name) triggerObject = Trigger(str(t.name)) try: self._getTriggerManager()._setObject( triggerObject.id, triggerObject) except BadRequest: # looks like the id already exists, remove this specific # trigger from zep. This can happen if multiple createTrigger # requests are sent from the browser at once - the transaction # will not abort until after the requests to create a trigger # have already been sent to zep. # See https://dev.zenoss.com/tracint/ticket/28272 log.info( 'SYNC: Found trigger with duplicate id in zodb, deleting from zep: %s (%s)' % (triggerObject.id, t.uuid)) self._removeTriggerFromZep(t.uuid) else: # setting a guid fires off events, we have to acquire the object # before we adapt it, otherwise adapters responding to the event # will get the 'regular' Trigger object and not be able to handle # it. self._setTriggerGuid( self._getTriggerManager().findChild(triggerObject.id), str(t.uuid)) self._setupTriggerPermissions( self._getTriggerManager().findChild(t.name)) # sync notifications for n in self._getNotificationManager().getChildNodes(): is_changed = False subs = list(n.subscriptions) for s in subs: if s not in zep_uuids: # this trigger no longer exists in zep, remove it from # this notification's subscriptions. log.info( 'SYNC: Notification subscription no longer valid: %s' % s) is_changed = True n.subscriptions.remove(s) if is_changed: log.debug('SYNC: Updating notification subscriptions: %s' % n.id) self.updateNotificationSubscriptions(n) log.debug('SYNC: Trigger and notification synchronization complete.') def getTriggers(self): self.synchronize() user = getSecurityManager().getUser() response, trigger_set = self.triggers_service.getTriggers() trigger_set = to_dict(trigger_set) if 'triggers' in trigger_set: return self.triggerPermissions.findTriggers( user, self._guidManager, trigger_set['triggers']) else: return [] def parseFilter(self, source): """ Parse a filter to make sure it's sane. @param source: The python expression to test. @type source: string @todo: make this not allow nasty python. """ if isinstance(source, basestring): if source: tree = parser.expr(source) if parser.isexpr(tree): return source else: raise Exception('Invalid filter expression.') else: return True # source is empty string def addTrigger(self, newId): return self.createTrigger( name=newId, uuid=None, rule=dict( source=u'(dev.production_state == 1000) and (evt.severity >= 4)' )) @require(MANAGE_TRIGGER) def createTrigger(self, name, uuid=None, rule=None): name = str(name) zodb_triggers = self._getTriggerManager().objectValues() zodb_trigger_names = set(t.id for t in zodb_triggers) if name in zodb_trigger_names: raise DuplicateTriggerName, ( 'The id "%s" is invalid - it is already in use.' % name) triggerObject = Trigger(name) self._getTriggerManager()._setObject(name, triggerObject) acquired_trigger = self._getTriggerManager().findChild(name) if uuid: IGlobalIdentifier(acquired_trigger).guid = str(uuid) else: IGlobalIdentifier(acquired_trigger).create() self.triggerPermissions.setupTrigger(acquired_trigger) trigger = zep.EventTrigger() trigger.uuid = IGlobalIdentifier(acquired_trigger).guid trigger.name = name trigger.rule.api_version = 1 trigger.rule.type = zep.RULE_TYPE_JYTHON if rule and 'source' in rule: trigger.rule.source = rule['source'] else: trigger.rule.source = '' self.triggers_service.addTrigger(trigger) log.debug('Created trigger with uuid: %s ' % trigger.uuid) return trigger.uuid @require(MANAGE_TRIGGER) def removeTrigger(self, uuid): user = getSecurityManager().getUser() trigger = self._guidManager.getObject(uuid) if self.triggerPermissions.userCanUpdateTrigger(user, trigger): # If a user has the ability to update (remove) a trigger, it is # presumed that they will be consciously deleting triggers that # may have subscribers. # # Consider that that trigger may be subscribed to by notifications # that the current user cannot read/edit. # if there was an error, the triggers service will throw an exception self._removeTriggerFromZep(uuid) context = aq_parent(trigger) context._delObject(trigger.id) relevant_notifications = self.getNotificationsBySubscription(uuid) updated_count = 0 for n in relevant_notifications: n.subscriptions.remove(uuid) log.debug('Removing trigger uuid %s from notification: %s' % (uuid, n.id)) self.updateNotificationSubscriptions(n) updated_count += 1 return updated_count else: log.warning( 'User not authorized to remove trigger: User: %s, Trigger: %s' % (user.getId(), trigger.id)) raise Exception( 'User not authorized to remove trigger: User: %s, Trigger: %s' % (user.getId(), trigger.id)) def getNotificationsBySubscription(self, trigger_uuid): for n in self._getNotificationManager().getChildNodes(): if trigger_uuid in n.subscriptions: yield n def getTrigger(self, uuid): user = getSecurityManager().getUser() trigger = self._guidManager.getObject(uuid) log.debug('Trying to fetch trigger: %s' % trigger.id) if self.triggerPermissions.userCanViewTrigger(user, trigger): response, trigger = self.triggers_service.getTrigger(uuid) return to_dict(trigger) else: log.warning('User not authorized to view this trigger: %s' % trigger.id) raise Exception('User not authorized to view this trigger: %s' % trigger.id) def getTriggerList(self): """ Retrieve a list of all triggers by uuid and name. This is used by the UI to render triggers that a user may not have permission to otherwise view, edit or manage. """ response, trigger_set = self.triggers_service.getTriggers() trigger_set = to_dict(trigger_set) triggerList = [] if 'triggers' in trigger_set: for t in trigger_set['triggers']: triggerList.append(dict(uuid=t['uuid'], name=t['name'])) return sorted(triggerList, key=lambda k: k['name']) @require(UPDATE_TRIGGER) def updateTrigger(self, **data): user = getSecurityManager().getUser() triggerObj = self._guidManager.getObject(data['uuid']) log.debug('Trying to update trigger: %s' % triggerObj.id) if self.triggerPermissions.userCanManageTrigger(user, triggerObj): if 'globalRead' in data: triggerObj.globalRead = data.get('globalRead', False) log.debug('setting globalRead %s' % triggerObj.globalRead) if 'globalWrite' in data: triggerObj.globalWrite = data.get('globalWrite', False) log.debug('setting globalWrite %s' % triggerObj.globalWrite) if 'globalManage' in data: triggerObj.globalManage = data.get('globalManage', False) log.debug('setting globalManage %s' % triggerObj.globalManage) triggerObj.users = data.get('users', []) self.triggerPermissions.clearPermissions(triggerObj) self.triggerPermissions.updatePermissions(self._guidManager, triggerObj) if self.triggerPermissions.userCanUpdateTrigger(user, triggerObj): if "name" in data: triggerObj.setTitle(data["name"]) parent = triggerObj.getPrimaryParent() path = triggerObj.absolute_url_path() oldId = triggerObj.getId() newId = triggerObj.title if not isinstance(newId, unicode): newId = Utils.prepId(newId) newId = newId.strip() if not newId: raise Exception("New trigger id cannot be empty.") if newId != oldId: # Now we have to rename the trigger id since its title changed try: if triggerObj.getDmd().Triggers.findObject(newId): message = 'Trigger %s already exists' % newId # Duplicate trigger found raise Exception(message) except AttributeError as ex: # We came here in the good case, because the newId is not a duplicate pass try: parent.manage_renameObject(oldId, newId) triggerObj.id = newId except CopyError: raise Exception("Trigger rename failed.") trigger = from_dict(zep.EventTrigger, data) response, content = self.triggers_service.updateTrigger(trigger) return content def _getTriggerManager(self): return self._dmd.findChild('Triggers') def _getNotificationManager(self): return self._dmd.findChild('NotificationSubscriptions') def getNotifications(self): self.synchronize() user = getSecurityManager().getUser() for n in self.notificationPermissions.findNotifications( user, self._getNotificationManager().getChildNodes()): yield IInfo(n) def _updateContent(self, notification, data=None): try: util = getUtility(IAction, notification.action) except ComponentLookupError: raise InvalidTriggerActionType( "Invalid action type specified: %s" % notification.action) fields = {} for iface in providedBy(util.getInfo(notification)): f = getFields(iface) if f: fields.update(f) data = util.getDefaultData(self._dmd) for k, v in fields.iteritems(): if k not in data: data[k] = v.default util.updateContent(notification.content, data) def addNotification(self, newId, action): notification = self.createNotification(newId, action) return IInfo(notification) @require(UPDATE_NOTIFICATION) def createNotification(self, id, action, guid=None): notification = NotificationSubscription(id) notification.action = action self._updateContent(notification) self._getNotificationManager()._setObject(id, notification) acquired_notification = self._getNotificationManager().findChild(id) self.notificationPermissions.setupNotification(acquired_notification) self.updateNotificationSubscriptions(notification) notification = self._getNotificationManager().findChild(id) notification.userRead = True notification.userWrite = True notification.userManage = True if guid: IGlobalIdentifier(notification).guid = guid return notification @require(UPDATE_NOTIFICATION) def removeNotification(self, uid): user = getSecurityManager().getUser() notification = self._getObject(uid) if self.notificationPermissions.userCanUpdateNotification( user, notification): return self.removeNode(uid) else: log.warning( 'User not authorized to remove notification: User: %s, Notification: %s' % (user.getId(), notification.id)) raise Exception('User not authorized to remove notification.') def getNotification(self, uid): user = getSecurityManager().getUser() notification = self._getObject(uid) if self.notificationPermissions.userCanViewNotification( user, notification): log.debug('getting notification: %s' % notification) return IInfo(notification) else: log.warning('User not authorized to view this notification: %s' % uid) raise Exception( 'User not authorized to view this notification: %s' % uid) @require(MANAGE_NOTIFICATION_SUBSCRIPTIONS) def updateNotificationSubscriptions(self, notification): triggerSubscriptions = [] notification_guid = IGlobalIdentifier(notification).getGUID() for subscription in notification.subscriptions: triggerSubscriptions.append( dict( delay_seconds=notification.delay_seconds, repeat_seconds=notification.repeat_seconds, subscriber_uuid=notification_guid, send_initial_occurrence=notification. send_initial_occurrence, trigger_uuid=subscription, )) subscriptionSet = from_dict(zep.EventTriggerSubscriptionSet, dict(subscriptions=triggerSubscriptions)) self.triggers_service.updateSubscriptions(notification_guid, subscriptionSet) @require(UPDATE_NOTIFICATION) def updateNotification(self, **data): log.debug(data) uid = data['uid'] # can't change action type after creation if 'action' in data: del data['action'] notification = self._getObject(uid) if not notification: raise Exception('Could not find notification to update: %s' % uid) # don't update any properties unless the current user has the correct # permission. user = getSecurityManager().getUser() if self.notificationPermissions.userCanUpdateNotification( user, notification): # update the action content data action = getUtility(IAction, notification.action) action.updateContent(notification.content, data) if self.notificationPermissions.userCanManageNotification( user, notification): # if these values are not sent (in the case that the fields have been # disabled, do not set the value. if 'notification_globalRead' in data: notification.globalRead = data.get('notification_globalRead') log.debug('setting globalRead') if 'notification_globalWrite' in data: notification.globalWrite = data.get('notification_globalWrite') log.debug('setting globalWrite') if 'notification_globalManage' in data: notification.globalManage = data.get( 'notification_globalManage') log.debug('setting globalManage') for field in notification._properties: notification._updateProperty(field['id'], data.get(field['id'])) notification.subscriptions = data.get('subscriptions') self.updateNotificationSubscriptions(notification) notification.recipients = data.get('recipients', []) self.notificationPermissions.clearPermissions(notification) self.notificationPermissions.updatePermissions( self._guidManager, notification) log.debug('updated notification: %s' % notification) def getRecipientOptions(self): users = self._dmd.ZenUsers.getAllUserSettings() groups = self._dmd.ZenUsers.getAllGroupSettings() data = [] for u in users: data.append(self.fetchRecipientOption(u)) for g in groups: data.append(self.fetchRecipientOption(g)) return data def fetchRecipientOption(self, recipient): my_type = 'group' if isinstance(recipient, GroupSettings) else 'user' return dict( type=my_type, label='%s (%s)' % (recipient.getId(), my_type.capitalize()), value=IGlobalIdentifier(recipient).getGUID(), ) def getWindows(self, uid): notification = self._getObject(uid) for window in notification.windows(): yield IInfo(window) def addWindow(self, contextUid, newId): notification = self._getObject(contextUid) window = NotificationSubscriptionWindow(newId) notification.windows._setObject(newId, window) new_window = notification.windows._getOb(newId) return IInfo(new_window) def removeWindow(self, uid): return self.removeNode(uid) def getWindow(self, uid): window = self._getObject(uid) return IInfo(window) def updateWindow(self, data): uid = data['uid'] window = self._getObject(uid) if not window: raise Exception('Could not find window to update: %s' % uid) for field in window._properties: if field['id'] == 'start': start = data['start'] start = start + " T" + data['starttime'] startDT = datetime.strptime(start, "%m-%d-%Y T%H:%M") setattr(window, 'start', int(startDT.strftime('%s'))) elif field['id'] == 'duration': setattr(window, 'duration', int(data['duration'])) elif field['id'] == 'skip': skip = data.get('skip') if skip is not None: window.skip = skip else: setattr(window, field['id'], data.get(field['id'])) log.debug('updated window') def exportConfiguration(self, triggerIds=None, notificationIds=None): notifications = [] if notificationIds is None: notificationIds = [] notifications = list(self.getNotifications()) elif isinstance(notificationIds, str): notificationIds = [notificationIds] triggers = self.getTriggers() if triggerIds is not None: names = [triggerIds] if isinstance(triggerIds, str) else triggerIds triggers = [x for x in triggers if x['name'] in names] for trigger in triggers: uid = trigger['uuid'] nsIds = [ x.id for x in self.getNotificationsBySubscription(uid) ] notificationIds.extend(nsIds) triggerData = self.exportTriggers(triggers) if notificationIds: notifications = [ x for x in notifications if x.id in notificationIds ] notificationData = self.exportNotifications(notifications) return triggerData, notificationData def exportTriggers(self, triggers): configs = [] junkColumns = ('id', 'newId') for config in triggers: for item in junkColumns: if item in config: del config[item] configs.append(config) return configs def exportNotifications(self, notifications): configs = [] junkColumns = ('id', 'newId', 'uid', 'inspector_type', 'meta_type') for notificationInfo in notifications: config = marshal(notificationInfo) for item in junkColumns: if item in config: del config[item] contentsTab = self._extractNotificationContentInfo(config) del config['content'] config.update(contentsTab) config['recipients'] = [r['label'] for r in config['recipients']] config['subscriptions'] = [ x['name'] for x in config['subscriptions'] ] windows = [] for window in notificationInfo._object.windows(): winconfig = marshal(IInfo(window)) for witem in ('meta_type', 'newId', 'id', 'inspector_type', 'uid'): del winconfig[witem] windows.append(winconfig) config['windows'] = windows configs.append(config) return configs def _extractNotificationContentInfo(self, notification): contents = {} try: for itemInfo in notification['content']['items'][0]['items']: key = itemInfo['name'] contents[key] = itemInfo['value'] except Exception: log.exception("Unable to extract data from notifcation: %s", notification) return contents def importConfiguration(self, triggers=None, notifications=None): itcount, incount = 0, 0 if triggers: itcount = self.importTriggers(triggers) if notifications: incount = self.importNotifications(notifications) return itcount, incount def importTriggers(self, triggers): """ Add any new trigger definitions to the system. Note: modifies the triggers argument to add 'new_uuid' to the definition. Does not attempt to link a trigger to a notification. """ existingTriggers = [x['name'] for x in self.getTriggerList()] existingUsers = [x.id for x in self._dmd.ZenUsers.getAllUserSettings()] removeDataList = ['subscriptions'] imported = 0 for trigger in triggers: name = trigger.get('name') if name is None: log.warn("Missing name in trigger definition: %s", trigger) continue if name in existingTriggers: log.warn("Skipping existing trigger '%s'", name) continue data = deepcopy(trigger) trigger['new_uuid'] = data['uuid'] = self.addTrigger(name) # Cleanup for key in removeDataList: if key in data: del data[key] # Don't delete data from list you're looping through for user in trigger.get('users', []): if user not in existingUsers: log.warning( "Unable to find trigger %s user '%s' on this server -- skipping", name, user) data['users'].remove(user) # Make changes to the definition self.updateTrigger(**data) imported += 1 return imported def importNotifications(self, notifications): """ Add new notification definitions to the system. """ existingNotifications = [x.id for x in self.getNotifications()] existingTypes = [x.action for x in self.getNotifications()] usersGroups = dict((x['label'], x) for x in self.getRecipientOptions()) trigerToUuid = dict((x['name'], x['uuid']) for x in self.getTriggers()) imported = 0 for notification in notifications: name = notification.get('name') if name is None: log.warn("Missing name in notification definition: %s", notification) continue if name in existingNotifications: log.warn("Skipping existing notification '%s'", name) continue ntype = notification.get('action') if ntype is None: log.warn("Missing 'action' in notification definition: %s", notification) continue if ntype not in existingTypes: log.warn( "The notification %s references an unknown action type: %s", name, ntype) continue data = deepcopy(notification) obj = self.addNotification(name, ntype) notification['uid'] = data['uid'] = obj.getPrimaryUrlPath() self.getRecipientsToImport(name, data, usersGroups) if 'action' in data: del data['action'] self.linkImportedNotificationToTriggers(data, trigerToUuid) windows = data.get('windows', []) if windows: for window in windows: iwindow = self.addWindow(data['uid'], window['name']) del window['name'] window['uid'] = iwindow.uid self.updateWindow(window) del data['windows'] # Make changes to the definition self.updateNotification(**data) imported += 1 return imported def getRecipientsToImport(self, name, data, usersGroups): recipients = [] for label in data.get('recipients', []): if label in usersGroups: recipients.append(usersGroups[label]) else: log.warn( "Unable to find %s for recipients for notification %s", label, name) data['recipients'] = recipients def linkImportedNotificationToTriggers(self, notification, trigerToUuid): subscriptions = [] for subscription in notification.get('subscriptions', []): uuid = trigerToUuid.get(subscription['name']) if uuid is not None: subscriptions.append(uuid) else: log.warn( "Unable to link notification %s to missing trigger '%s'", notification['name'], subscription['name']) notification['subscriptions'] = subscriptions
class TriggersFacade(ZuulFacade): def __init__(self, context): super(TriggersFacade, self).__init__(context) self._guidManager = IGUIDManager(self._dmd) config = getGlobalConfiguration() schema = getUtility(IQueueSchema) self.triggers_service = TriggerServiceClient(config.get('zep_uri', 'http://localhost:8084'), schema) self.notificationPermissions = NotificationPermissionManager() self.triggerPermissions = TriggerPermissionManager() def _removeNode(self, obj): """ Remove an object in ZODB. This method was created to provide a hook for unit tests. """ context = aq_parent(obj) return context._delObject(obj.id) def _removeTriggerFromZep(self, uuid): """ Remove a trigger from ZEP. This method was created to provide a hook for unit tests. """ return self.triggers_service.removeTrigger(uuid) def removeNode(self, uid): obj = self._getObject(uid) return self._removeNode(obj) def _setTriggerGuid(self, trigger, guid): """ @param trigger: The trigger object to set the guid on. @type trigger: Products.ZenModel.Trigger.Trigger @param guid: The guid @type guid: str This method was created to provide a hook for unit tests. """ IGlobalIdentifier(trigger).guid = guid def _getTriggerGuid(self, trigger): """ @param trigger: The trigger object in zodb. @type trigger: Products.ZenModel.Trigger.Trigger This method was created to provide a hook for unit tests. """ return IGlobalIdentifier(trigger).guid def _setupTriggerPermissions(self, trigger): """ This method was created to provide a hook for unit tests. """ self.triggerPermissions.setupTrigger(trigger) def synchronize(self): """ This method will first synchronize all triggers that exist in ZEP to their corresponding objects in ZODB. Then, it will clean up notifications and remove any subscriptions to triggers that no longer exist. """ log.debug('SYNC: Starting trigger and notification synchronization.') _, trigger_set = self.triggers_service.getTriggers() zep_uuids = set(t.uuid for t in trigger_set.triggers) zodb_triggers = self._getTriggerManager().objectValues() # delete all triggers in zodb that do not exist in zep. for t in zodb_triggers: if not self._getTriggerGuid(t) in zep_uuids: log.info('SYNC: Found trigger in zodb that does not exist in zep, removing: %s' % t.id) self._removeNode(t) zodb_triggers = self._getTriggerManager().objectValues() zodb_uuids = set(self._getTriggerGuid(t) for t in zodb_triggers) # create all triggers in zodb that do not exist in zep. for t in trigger_set.triggers: if not t.uuid in zodb_uuids: log.info('SYNC: Found trigger uuid in zep that does not seem to exist in zodb, creating: %s' % t.name) triggerObject = Trigger(str(t.name)) try: self._getTriggerManager()._setObject(triggerObject.id, triggerObject) except BadRequest: # looks like the id already exists, remove this specific # trigger from zep. This can happen if multiple createTrigger # requests are sent from the browser at once - the transaction # will not abort until after the requests to create a trigger # have already been sent to zep. # See https://dev.zenoss.com/tracint/ticket/28272 log.info('SYNC: Found trigger with duplicate id in zodb, deleting from zep: %s (%s)' % (triggerObject.id, t.uuid)) self._removeTriggerFromZep(t.uuid) else: # setting a guid fires off events, we have to acquire the object # before we adapt it, otherwise adapters responding to the event # will get the 'regular' Trigger object and not be able to handle # it. self._setTriggerGuid(self._getTriggerManager().findChild(triggerObject.id), str(t.uuid)) self._setupTriggerPermissions(self._getTriggerManager().findChild(t.name)) # sync notifications for n in self._getNotificationManager().getChildNodes(): is_changed = False subs = list(n.subscriptions) for s in subs: if s not in zep_uuids: # this trigger no longer exists in zep, remove it from # this notification's subscriptions. log.info('SYNC: Notification subscription no longer valid: %s' % s) is_changed = True n.subscriptions.remove(s) if is_changed: log.debug('SYNC: Updating notification subscriptions: %s' % n.id) self.updateNotificationSubscriptions(n) log.debug('SYNC: Trigger and notification synchronization complete.') def getTriggers(self): self.synchronize() user = getSecurityManager().getUser() response, trigger_set = self.triggers_service.getTriggers() trigger_set = to_dict(trigger_set) if 'triggers' in trigger_set: return self.triggerPermissions.findTriggers(user, self._guidManager, trigger_set['triggers']) else: return [] def parseFilter(self, source): """ Parse a filter to make sure it's sane. @param source: The python expression to test. @type source: string @todo: make this not allow nasty python. """ if isinstance(source, basestring): if source: tree = parser.expr(source) if parser.isexpr(tree): return source else: raise Exception('Invalid filter expression.') else: return True # source is empty string def addTrigger(self, newId): return self.createTrigger( name = newId, uuid = None, rule = dict( source = '' ) ) def createTrigger(self, name, uuid=None, rule=None): name = str(name) zodb_triggers = self._getTriggerManager().objectValues() zodb_trigger_names = set(t.id for t in zodb_triggers) if name in zodb_trigger_names: raise DuplicateTriggerName, ('The id "%s" is invalid - it is already in use.' % name) triggerObject = Trigger(name) self._getTriggerManager()._setObject(name, triggerObject) acquired_trigger = self._getTriggerManager().findChild(name) if uuid: IGlobalIdentifier(acquired_trigger).guid = str(uuid) else: IGlobalIdentifier(acquired_trigger).create() self.triggerPermissions.setupTrigger(acquired_trigger) trigger = zep.EventTrigger() trigger.uuid = IGlobalIdentifier(acquired_trigger).guid trigger.name = name trigger.rule.api_version = 1 trigger.rule.type = zep.RULE_TYPE_JYTHON if rule and 'source' in rule: trigger.rule.source = rule['source'] else: trigger.rule.source = '' self.triggers_service.addTrigger(trigger) log.debug('Created trigger with uuid: %s ' % trigger.uuid) return trigger.uuid def removeTrigger(self, uuid): user = getSecurityManager().getUser() trigger = self._guidManager.getObject(uuid) if self.triggerPermissions.userCanUpdateTrigger(user, trigger): # If a user has the ability to update (remove) a trigger, it is # presumed that they will be consciously deleting triggers that # may have subscribers. # # Consider that that trigger may be subscribed to by notifications # that the current user cannot read/edit. # if there was an error, the triggers service will throw an exception self._removeTriggerFromZep(uuid) context = aq_parent(trigger) context._delObject(trigger.id) relevant_notifications = self.getNotificationsBySubscription(uuid) updated_count = 0 for n in relevant_notifications: n.subscriptions.remove(uuid) log.debug('Removing trigger uuid %s from notification: %s' % (uuid, n.id)) self.updateNotificationSubscriptions(n) updated_count += 1 return updated_count else: log.warning('User not authorized to remove trigger: User: %s, Trigger: %s' % (user.getId(), trigger.id)) raise Exception('User not authorized to remove trigger: User: %s, Trigger: %s' % (user.getId(), trigger.id)) def getNotificationsBySubscription(self, trigger_uuid): for n in self._getNotificationManager().getChildNodes(): if trigger_uuid in n.subscriptions: yield n def getTrigger(self, uuid): user = getSecurityManager().getUser() trigger = self._guidManager.getObject(uuid) log.debug('Trying to fetch trigger: %s' % trigger.id) if self.triggerPermissions.userCanViewTrigger(user, trigger): response, trigger = self.triggers_service.getTrigger(uuid) return to_dict(trigger) else: log.warning('User not authorized to view this trigger: %s' % trigger.id) raise Exception('User not authorized to view this trigger: %s' % trigger.id) def getTriggerList(self): """ Retrieve a list of all triggers by uuid and name. This is used by the UI to render triggers that a user may not have permission to otherwise view, edit or manage. """ response, trigger_set = self.triggers_service.getTriggers() trigger_set = to_dict(trigger_set) triggerList = [] if 'triggers' in trigger_set: for t in trigger_set['triggers']: triggerList.append(dict( uuid = t['uuid'], name = t['name'] )) return sorted(triggerList, key=lambda k: k['name']) def updateTrigger(self, **data): user = getSecurityManager().getUser() triggerObj = self._guidManager.getObject(data['uuid']) log.debug('Trying to update trigger: %s' % triggerObj.id) if self.triggerPermissions.userCanManageTrigger(user, triggerObj): if 'globalRead' in data: triggerObj.globalRead = data.get('globalRead', False) log.debug('setting globalRead %s' % triggerObj.globalRead) if 'globalWrite' in data: triggerObj.globalWrite = data.get('globalWrite', False) log.debug('setting globalWrite %s' % triggerObj.globalWrite) if 'globalManage' in data: triggerObj.globalManage = data.get('globalManage', False) log.debug('setting globalManage %s' % triggerObj.globalManage) triggerObj.users = data.get('users', []) self.triggerPermissions.clearPermissions(triggerObj) self.triggerPermissions.updatePermissions(self._guidManager, triggerObj) if self.triggerPermissions.userCanUpdateTrigger(user, triggerObj): if "name" in data: triggerObj.setTitle(data["name"]) trigger = from_dict(zep.EventTrigger, data) response, content = self.triggers_service.updateTrigger(trigger) return content def _getTriggerManager(self): return self._dmd.findChild('Triggers') def _getNotificationManager(self): return self._dmd.findChild('NotificationSubscriptions') def getNotifications(self): self.synchronize() user = getSecurityManager().getUser() for n in self.notificationPermissions.findNotifications(user, self._getNotificationManager().getChildNodes()): yield IInfo(n) def _updateContent(self, notification, data=None): try: util = getUtility(IAction, notification.action) except ComponentLookupError: raise InvalidTriggerActionType("Invalid action type specified: %s" % notification.action) fields = {} for iface in providedBy(util.getInfo(notification)): f = getFields(iface) if f: fields.update(f) data = util.getDefaultData(self._dmd) for k, v in fields.iteritems(): if k not in data: data[k] = v.default util.updateContent(notification.content, data) def addNotification(self, newId, action): notification = self.createNotification(newId, action) return IInfo(notification) def createNotification(self, id, action, guid=None): notification = NotificationSubscription(id) notification.action = action self._updateContent(notification) self._getNotificationManager()._setObject(id, notification) acquired_notification = self._getNotificationManager().findChild(id) self.notificationPermissions.setupNotification(acquired_notification) self.updateNotificationSubscriptions(notification) notification = self._getNotificationManager().findChild(id) notification.userRead = True notification.userWrite = True notification.userManage = True if guid: IGlobalIdentifier(notification).guid = guid return notification def removeNotification(self, uid): user = getSecurityManager().getUser() notification = self._getObject(uid) if self.notificationPermissions.userCanUpdateNotification(user, notification): return self.removeNode(uid) else: log.warning('User not authorized to remove notification: User: %s, Notification: %s' % (user.getId(), notification.id)) raise Exception('User not authorized to remove notification.') def getNotification(self, uid): user = getSecurityManager().getUser() notification = self._getObject(uid) if self.notificationPermissions.userCanViewNotification(user, notification): log.debug('getting notification: %s' % notification) return IInfo(notification) else: log.warning('User not authorized to view this notification: %s' % uid) raise Exception('User not authorized to view this notification: %s' % uid) def updateNotificationSubscriptions(self, notification): triggerSubscriptions = [] notification_guid = IGlobalIdentifier(notification).getGUID() for subscription in notification.subscriptions: triggerSubscriptions.append(dict( delay_seconds = notification.delay_seconds, repeat_seconds = notification.repeat_seconds, subscriber_uuid = notification_guid, send_initial_occurrence = notification.send_initial_occurrence, trigger_uuid = subscription, )) subscriptionSet = from_dict(zep.EventTriggerSubscriptionSet, dict( subscriptions = triggerSubscriptions )) self.triggers_service.updateSubscriptions(notification_guid, subscriptionSet) def updateNotification(self, **data): log.debug(data) uid = data['uid'] # can't change action type after creation if 'action' in data: del data['action'] notification = self._getObject(uid) if not notification: raise Exception('Could not find notification to update: %s' % uid) # don't update any properties unless the current user has the correct # permission. user = getSecurityManager().getUser() if self.notificationPermissions.userCanUpdateNotification(user, notification): # update the action content data action = getUtility(IAction, notification.action) action.updateContent(notification.content, data) if self.notificationPermissions.userCanManageNotification(user, notification): # if these values are not sent (in the case that the fields have been # disabled, do not set the value. if 'notification_globalRead' in data: notification.globalRead = data.get('notification_globalRead') log.debug('setting globalRead') if 'notification_globalWrite' in data: notification.globalWrite = data.get('notification_globalWrite') log.debug('setting globalWrite') if 'notification_globalManage' in data: notification.globalManage = data.get('notification_globalManage') log.debug('setting globalManage') for field in notification._properties: notification._updateProperty(field['id'], data.get(field['id'])) notification.subscriptions = data.get('subscriptions') self.updateNotificationSubscriptions(notification) notification.recipients = data.get('recipients', []) self.notificationPermissions.clearPermissions(notification) self.notificationPermissions.updatePermissions(self._guidManager, notification) log.debug('updated notification: %s' % notification) def getRecipientOptions(self): users = self._dmd.ZenUsers.getAllUserSettings() groups = self._dmd.ZenUsers.getAllGroupSettings() data = [] for u in users: data.append(self.fetchRecipientOption(u)) for g in groups: data.append(self.fetchRecipientOption(g)) return data def fetchRecipientOption(self, recipient): my_type = 'group' if isinstance(recipient, GroupSettings) else 'user' return dict( type = my_type, label = '%s (%s)' % (recipient.getId(), my_type.capitalize()), value = IGlobalIdentifier(recipient).getGUID(), ) def getWindows(self, uid): notification = self._getObject(uid) for window in notification.windows(): yield IInfo(window) def addWindow(self, contextUid, newId): notification = self._getObject(contextUid) window = NotificationSubscriptionWindow(newId) notification.windows._setObject(newId, window) new_window = notification.windows._getOb(newId) return IInfo(new_window) def removeWindow(self, uid): return self.removeNode(uid) def getWindow(self, uid): window = self._getObject(uid) return IInfo(window) def updateWindow(self, data): uid = data['uid'] window = self._getObject(uid) if not window: raise Exception('Could not find window to update: %s' % uid) for field in window._properties: if field['id'] == 'start': start = data['start'] start = start + " T" + data['starttime'] startDT = datetime.strptime(start, "%m-%d-%Y T%H:%M") setattr(window, 'start', int(startDT.strftime('%s'))) elif field['id'] == 'duration': setattr(window, 'duration', int(data['duration'])) elif field['id'] == 'skip': skip = data.get('skip') if skip is not None: window.skip = skip else: setattr(window, field['id'], data.get(field['id'])) log.debug('updated window') def exportConfiguration(self, triggerIds=None, notificationIds=None): notifications = [] if notificationIds is None: notificationIds = [] notifications = list(self.getNotifications()) elif isinstance(notificationIds, str): notificationIds = [notificationIds] triggers = self.getTriggers() if triggerIds is not None: names = [triggerIds] if isinstance(triggerIds, str) else triggerIds triggers = [x for x in triggers if x['name'] in names] for trigger in triggers: uid = trigger['uuid'] nsIds = [x.id for x in self.getNotificationsBySubscription(uid)] notificationIds.extend(nsIds) triggerData = self.exportTriggers(triggers) if notificationIds: notifications = [x for x in notifications if x.id in notificationIds] notificationData = self.exportNotifications(notifications) return triggerData, notificationData def exportTriggers(self, triggers): configs = [] junkColumns = ('id', 'newId') for config in triggers: for item in junkColumns: if item in config: del config[item] configs.append(config) return configs def exportNotifications(self, notifications): configs = [] junkColumns = ('id', 'newId', 'uid', 'inspector_type', 'meta_type') for notificationInfo in notifications: config = marshal(notificationInfo) for item in junkColumns: if item in config: del config[item] contentsTab = self._extractNotificationContentInfo(config) del config['content'] config.update(contentsTab) config['recipients'] = [r['label'] for r in config['recipients']] config['subscriptions'] = [x['name'] for x in config['subscriptions']] windows = [] for window in notificationInfo._object.windows(): winconfig = marshal(IInfo(window)) for witem in ('meta_type', 'newId', 'id', 'inspector_type', 'uid'): del winconfig[witem] windows.append(winconfig) config['windows'] = windows configs.append(config) return configs def _extractNotificationContentInfo(self, notification): contents = {} try: for itemInfo in notification['content']['items'][0]['items']: key = itemInfo['name'] contents[key] = itemInfo['value'] except Exception: log.exception("Unable to extract data from notifcation: %s", notification) return contents def importConfiguration(self, triggers=None, notifications=None): itcount, incount = 0, 0 if triggers: itcount = self.importTriggers(triggers) if notifications: incount = self.importNotifications(notifications) return itcount, incount def importTriggers(self, triggers): """ Add any new trigger definitions to the system. Note: modifies the triggers argument to add 'new_uuid' to the definition. Does not attempt to link a trigger to a notification. """ existingTriggers = [x['name'] for x in self.getTriggerList()] existingUsers = [x.id for x in self._dmd.ZenUsers.getAllUserSettings()] removeDataList = [ 'subscriptions' ] imported = 0 for trigger in triggers: name = trigger.get('name') if name is None: log.warn("Missing name in trigger definition: %s", trigger) continue if name in existingTriggers: log.warn("Skipping existing trigger '%s'", name) continue data = deepcopy(trigger) trigger['new_uuid'] = data['uuid'] = self.addTrigger(name) # Cleanup for key in removeDataList: if key in data: del data[key] # Don't delete data from list you're looping through for user in trigger.get('users', []): if user not in existingUsers: log.warning("Unable to find trigger %s user '%s' on this server -- skipping", name, user) data['users'].remove(user) # Make changes to the definition self.updateTrigger(**data) imported += 1 return imported def importNotifications(self, notifications): """ Add new notification definitions to the system. """ existingNotifications = [x.id for x in self.getNotifications()] existingTypes = [x.action for x in self.getNotifications()] usersGroups = dict( (x['label'], x) for x in self.getRecipientOptions()) trigerToUuid = dict( (x['name'], x['uuid']) for x in self.getTriggers()) imported = 0 for notification in notifications: name = notification.get('name') if name is None: log.warn("Missing name in notification definition: %s", notification) continue if name in existingNotifications: log.warn("Skipping existing notification '%s'", name) continue ntype = notification.get('action') if ntype is None: log.warn("Missing 'action' in notification definition: %s", notification) continue if ntype not in existingTypes: log.warn("The notification %s references an unknown action type: %s", name, ntype) continue data = deepcopy(notification) obj = self.addNotification(name, ntype) notification['uid'] = data['uid'] = obj.getPrimaryUrlPath() self.getRecipientsToImport(name, data, usersGroups) if 'action' in data: del data['action'] self.linkImportedNotificationToTriggers(data, trigerToUuid) windows = data.get('windows', []) if windows: for window in windows: iwindow = self.addWindow(data['uid'], window['name']) del window['name'] window['uid'] = iwindow.uid self.updateWindow(window) del data['windows'] # Make changes to the definition self.updateNotification(**data) imported += 1 return imported def getRecipientsToImport(self, name, data, usersGroups): recipients = [] for label in data.get('recipients', []): if label in usersGroups: recipients.append(usersGroups[label]) else: log.warn("Unable to find %s for recipients for notification %s", label, name) data['recipients'] = recipients def linkImportedNotificationToTriggers(self, notification, trigerToUuid): subscriptions = [] for subscription in notification.get('subscriptions', []): uuid = trigerToUuid.get(subscription['name']) if uuid is not None: subscriptions.append(uuid) else: log.warn("Unable to link notification %s to missing trigger '%s'", notification['name'], subscription['name']) notification['subscriptions'] = subscriptions