def proxy_device(self): ''' Return this component's corresponding proxy device, creating it in the proxy_deviceclass if it does not exist. Default assumes that the names must match. ''' device = GUIDManager(self.dmd).getObject(getattr(self, 'openstackProxyDeviceUUID', None)) if device: guid = IGlobalIdentifier(self).getGUID() # this shouldn't happen, but if we've somehow become half-connected # (we know about the device, it doesn't know about us), reconnect. if getattr(device, 'openstackProxyComponentUUID', None) != guid: LOG.info("%s component '%s' linkage to device '%s' is broken. Re-claiming it." % (self.meta_type, self.name(), device.name())) self.claim_proxy_device(device) return device # Does a device with a matching name exist? Claim that one. device = self.dmd.Devices.findDevice(self.name()) if device: self_pdc_path = self.proxy_deviceclass().getPrimaryPath() device_path = device.getPrimaryPath()[:len(self_pdc_path)] if device_path == self_pdc_path and device.id != self.device().id: self.claim_proxy_device(device) return device else: return self.create_proxy_device() else: return self.create_proxy_device()
def apply(self, eventProxy, dmd): device = dmd.Devices.findDeviceByIdExact(eventProxy.device) if device and hasattr(device, 'openstackProxyComponentUUID'): LOG.debug("tagging event on %s with openstack proxy component component uuid %s", eventProxy.device, device.openstackProxyComponentUUID) tags = [device.openstackProxyComponentUUID] # Also tag it with the openstack endpoint that the component is part of, # if possible. try: component = GUIDManager(dmd).getObject(device.openstackProxyComponentUUID) if component: endpoint = component.device() tags.append(IGlobalIdentifier(endpoint).getGUID()) except Exception: LOG.debug("Unable to determine endpoint for proxy component uuid %s", device.openstackProxyComponentUUID) # Get OSProcess component, if the event has one for brain in ICatalogTool(dmd).search('Products.ZenModel.OSProcess.OSProcess', query=Eq('id', eventProxy.component)): osprocess = brain.getObject() # Figure out if we have a corresponding software component: try: for software in component.hostedSoftware(): if software.binary == osprocess.osProcessClass().id: # Matches! tags.append(IGlobalIdentifier(software).getGUID()) except Exception: LOG.debug("Unable to append event for OSProcess %s", osprocess.osProcessClass().id) eventProxy.tags.addAll('ZenPacks.zenoss.OpenStackInfrastructure.DeviceProxyComponent', tags)
def release_proxy_device(self): device = GUIDManager(self.dmd).getObject(getattr(self, 'openstackProxyDeviceUUID', None)) if device: LOG.debug("device %s is now detached from %s component '%s'" % (device.name(), self.meta_type, self.name())) device.openstackProxyComponentUUID = None self.openstackProxyDeviceUUID = None LOG.debug("%s component '%s' is now detached from any devices" % (self.meta_type, self.name()))
def release_proxy_device(self): device = GUIDManager(self.dmd).getObject( getattr(self, 'openstackProxyDeviceUUID', None)) if device: LOG.debug("device %s is now detached from %s component '%s'" % (device.name(), self.meta_type, self.name())) device.openstackProxyComponentUUID = None self.openstackProxyDeviceUUID = None LOG.debug("%s component '%s' is now detached from any devices" % (self.meta_type, self.name()))
def onDeviceDeleted(object, event): ''' Clean up the dangling reference to a device if that device has been removed. (Note: we may re-create the device automatically next time someone calls self.ensure_proxy_device, though) ''' if not IObjectWillBeAddedEvent.providedBy(event) and not IObjectWillBeMovedEvent.providedBy(event): if hasattr(object, 'openstackProxyComponentUUID'): component = GUIDManager(object.dmd).getObject(getattr(object, 'openstackProxyComponentUUID', None)) if component: component.release_proxy_device() object.openstackProxyComponentUUID = None
def setupAction(self, dmd): self.guidManager = GUIDManager(dmd) self.dmd = dmd self.zep = getFacade("zep", self.dmd) self.statuses = { "0": "New", "1": "Acknowledged", "2": "Suppressed", "3": "Closed", "4": "Cleared", "5": "Dropped", "6": "Aged", }
def apply(self, eventProxy, dmd): # See ZPS-1677 for explanation. This workaround will hopefully be # removed in the future (ZPS-1685) if eventProxy.eventClass == '/Status/Ping': return device = dmd.Devices.findDeviceByIdExact(eventProxy.device) if device and hasattr(device, 'openstackProxyComponentUUID'): LOG.debug("tagging event on %s with openstack proxy component component uuid %s", eventProxy.device, device.openstackProxyComponentUUID) tags = [] try: component = GUIDManager(dmd).getObject(device.openstackProxyComponentUUID) if component: # Tag the event with the corresponding openstack component. tags.append(device.openstackProxyComponentUUID) # Also tag it with the openstack endpoint that the # component is part of, if possible. endpoint = component.device() tags.append(IGlobalIdentifier(endpoint).getGUID()) except Exception: LOG.debug("Unable to determine endpoint for proxy component uuid %s", device.openstackProxyComponentUUID) # Get OSProcess component, if the event has one if eventProxy.component: for brain in ICatalogTool(dmd).search('Products.ZenModel.OSProcess.OSProcess', query=Eq('id', eventProxy.component)): try: osprocess = brain.getObject() except Exception: # ignore a stale entry pass else: # Figure out if we have a corresponding software component: try: for software in component.hostedSoftware(): if software.binary == osprocess.osProcessClass().id: # Matches! tags.append(IGlobalIdentifier(software).getGUID()) except Exception: LOG.debug("Unable to append event for OSProcess %s", osprocess.osProcessClass().id) if tags: eventProxy.tags.addAll('ZenPacks.zenoss.OpenStackInfrastructure.DeviceProxyComponent', tags)
def onDeviceDeleted(object, event): ''' Clean up the dangling reference to a device if that device has been removed. (Note: we may re-create the device automatically next time someone calls self.ensure_proxy_device, though) ''' if not IObjectWillBeAddedEvent.providedBy( event) and not IObjectWillBeMovedEvent.providedBy(event): if hasattr(object, 'openstackProxyComponentUUID'): component = GUIDManager(object.dmd).getObject( getattr(object, 'openstackProxyComponentUUID', None)) if component: component.release_proxy_device() object.openstackProxyComponentUUID = None
def proxy_device(self): ''' Return this component's corresponding proxy device, or None if there isn't one at this time. This method will not attempt to create a new device. ''' # A claimed device already exists. device = GUIDManager(self.dmd).getObject( getattr(self, 'openstackProxyDeviceUUID', None)) if device: # Make sure that the GUID is correct in the reverse direction, # then return it. return self.ensure_valid_claim(device) # Look for device that matches our requirements device = self.find_claimable_device() # Claim it. if device: self.claim_proxy_device(device) return device # We found nothing to claim, so return None. return None
def need_maintenance(self): guid = IGlobalIdentifier(self).getGUID() device = GUIDManager(self.dmd).getObject( getattr(self, 'openstackProxyDeviceUUID', None)) if device and getattr(device, 'openstackProxyComponentUUID', None) \ and device.openstackProxyComponentUUID == guid: return False return True
def component_for_proxy_device(cls, device): ''' Given any device in the system, check if it has a DeviceProxyComponent associated with it, and if it does, return that component. ''' uuid = getattr(device, 'openstackProxyComponentUUID', None) if uuid: component = GUIDManager(device.dmd).getObject(uuid) # ensure that the component is also linked back to this device- # a uni-directional linkage (device to component, but not # component to device) is not valid. component_device_uuid = getattr(component, 'openstackProxyDeviceUUID', None) if component_device_uuid == IGlobalIdentifier(device).getGUID(): return component else: LOG.warning( "Device %s is linked to component %s, but it is not linked back. Disregarding linkage.", device, component) return None
class CommandAction(IActionBase, TargetableAction): implements(IAction) id = "command" name = "Command" actionContentInfo = ICommandActionContentInfo shouldExecuteInBatch = False def configure(self, options): super(CommandAction, self).configure(options) self.processQueue = ProcessQueue(options.get("maxCommands", 10)) self.processQueue.start() def setupAction(self, dmd): self.guidManager = GUIDManager(dmd) self.dmd = dmd def execute(self, notification, signal): # check to see if we have any targets if notification.recipients: return super(CommandAction, self).execute(notification, signal) else: self._execute(notification, signal) def executeOnTarget(self, notification, signal, target): log.debug("Executing command action: %s on %s", self.name, target) environ = {} environ["user"] = getattr(self.dmd.ZenUsers, target, None) self._execute(notification, signal, environ) def _execute(self, notification, signal, extra_env={}): self.setupAction(notification.dmd) log.debug("Executing command action: %s", self.name) if signal.clear: command = notification.content["clear_body_format"] else: command = notification.content["body_format"] log.debug("Executing this command: %s", command) actor = signal.event.occurrence[0].actor device = None if actor.element_uuid: device = self.guidManager.getObject(actor.element_uuid) component = None if actor.element_sub_uuid: component = self.guidManager.getObject(actor.element_sub_uuid) user_env_format = notification.content.get("user_env_format", "") or "" env = dict(envvar.split("=") for envvar in user_env_format.split(";") if "=" in envvar) environ = {"dev": device, "component": component, "dmd": notification.dmd, "env": env} data = self._signalToContextDict(signal, notification) environ.update(data) if environ.get("evt", None): environ["evt"] = self._escapeEvent(environ["evt"]) if environ.get("clearEvt", None): environ["clearEvt"] = self._escapeEvent(environ["clearEvt"]) environ.update(extra_env) # Get the proper command command = processTalSource(command, **environ) log.debug('Executing this compiled command: "%s"' % command) _protocol = EventCommandProtocol(command, notification) log.debug("Queueing up command action process.") self.processQueue.queueProcess( "/bin/sh", ("/bin/sh", "-c", command), env=environ["env"], processProtocol=_protocol, timeout=int(notification.content["action_timeout"]), timeout_callback=_protocol.timedOut, ) def getActionableTargets(self, target): ids = [target.id] if isinstance(target, GroupSettings): ids = [x.id for x in target.getMemberUserSettings()] return ids def _escapeEvent(self, evt): """ Escapes the relavent fields of an event context for event commands. """ if evt.message: evt.message = self._wrapInQuotes(evt.message) if evt.summary: evt.summary = self._wrapInQuotes(evt.summary) return evt def _wrapInQuotes(self, msg): """ Wraps the message in quotes, escaping any existing quote. Before: How do you pronounce "Zenoss"? After: "How do you pronounce \"Zenoss\"?" """ QUOTE = '"' BACKSLASH = "\\" return "".join((QUOTE, msg.replace(QUOTE, BACKSLASH + QUOTE), QUOTE))
class sendGCM(IActionBase): implements(IAction) id = 'sendgcm' name = 'Send Alert to Rhybudd' actionContentInfo = IConfigurableGCMActionContentInfo def setupAction(self, dmd): self.guidManager = GUIDManager(dmd) self.dmd = dmd # def executeBatch(self, notification, signal, targets): # log.debug("Executing %s action for targets: %s", self.name, targets) # # self.setupAction(notification.dmd) # # data = _signalToContextDict(signal, self.options.get('zopeurl'), notification, self.guidManager) def execute(self, notification, signal): self.setupAction(notification.dmd) data = _signalToContextDict(signal, self.options.get('zopeurl'), notification, self.guidManager) #if notification.content['gcmdeviceid'] == "AABBCCDDEEFF00112233": # raise ActionExecutionException("Cannot send a Rhybudd GCM message with the default GCM Key") #if signal.clear and data['clearEventSummary'].uuid: # log.info('------------------------------------ This is a clear message') #else: # log.info('------------------------------------ This is an alert') #log.info(data['eventSummary'].summary) #log.info(data['eventSummary'].status) #log.info(data['eventSummary'].count) #log.info(data['eventSummary'].severity) #log.info(data['eventSummary'].event_class) #log.info(data['eventSummary'].event_class_key) actor = signal.event.occurrence[0].actor device = None if actor.element_uuid: device = self.guidManager.getObject(actor.element_uuid) #------------------------------------ #The CURL Way of doing things #curlCall = "curl -H \"Content-Type: application/json\" -X POST -d '{\"gcm_id\": \"%s\",\"evid\": \"%s\",\"device\": \"%s\",\"summary\": \"%s\",\"status\": \"%s\",\"count\": \"%s\",\"severity\": \"%s\",\"event_class\": \"%s\",\"event_class_key\": \"%s\"}' http://api.coldstart.io/1/zenoss.php" % (notification.content['gcmdeviceid'], signal.event.uuid,device,data['eventSummary'].summary,data['eventSummary'].status,data['eventSummary'].count,data['eventSummary'].severity,data['eventSummary'].event_class,data['eventSummary'].event_class_key) #call(curlCall, shell=True) #------------------------------------ #The URL Lib way of doing things prodstate = "" try: prodstate = "%s" % data.prodstate except Exception: prodstate = "" pass payload = { 'filter_key': notification.content['gcmdeviceid'], "evid": "%s" % signal.event.uuid, "device": "%s" % device, "summary": "%s" % data['eventSummary'].summary, "status": data['eventSummary'].status, "count": data['eventSummary'].count, "severity": data['eventSummary'].severity, "event_class": "%s" % data['eventSummary'].event_class, "event_class_key": "%s" % data['eventSummary'].event_class_key, "prodstate": "%s" % prodstate, "firsttime": "%s" % data['eventSummary'].first_seen_time, #"componenttext": "%s" % data['eventSummary'].component.text, "ownerid": "%s" % data['eventSummary'].current_user_name } gcm_details = getattr(self.dmd, 'rhybudd_gcm', Gcm("", "")) log.info("%s",gcm_details.gcm_api_key) stored_regids = getattr(self.dmd, 'rhybudd_regids', []) reg_ids = [] for regDetails in stored_regids: #log.info('Found a GCM ID: %s',regDetails.gcm_reg_id) reg_ids.append(regDetails.gcm_reg_id) if gcm_details.gcm_api_key == "": #------------------------------------ #No GCM Key specified so we'll proxy through ColdStart.io so as to not expose our GCM API Key log.info('------------------------------------ Sending a coldstart GCM Request') coldstart_payload = {'payload': payload, 'regids': reg_ids} data = "json=%s" % json.dumps(coldstart_payload) h = httplib.HTTPSConnection('api.coldstart.io') headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"} h.request('POST', '/1/zenoss', data, headers) else: #------------------------------------ # Direct to GCM log.info('------------------------------------ Sending a direct GCM Request') gcm = GCMSERVER(gcm_details.gcm_api_key) #reg_ids = ['APA91bEPJ_Pf7k5KgpZxbNBpq9snGjYyQn6Q21w_JYl-_4FADgNH54kzcQxGb6Wjb1PkWGiEaVQE0MXhMw7q-jTOvDN_smiaSa96F9sEOLd1xYt4yd7PiYVCYsVULiFoN_isvz1AcN-HXjZVfipBLBIzN5ohqN_MM2tpmBj9JFpdwjFgM6ZNhPU'] response = gcm.json_request(registration_ids=reg_ids, data=payload, collapse_key=signal.event.uuid, time_to_live=0) log.info("%s",json.dumps(response)) log.info('------------------------------------ Sent a direct GCM Request') def updateContent(self, content=None, data=None): content['gcmdeviceid'] = data.get('gcmdeviceid')
def setupAction(self, dmd): self.guidManager = GUIDManager(dmd) self.dmd = dmd self.zenossHostname = dmd.zenossHostname
def setupAction(self, dmd): log.debug('[research] setup : %s' % (self.name)) self.guidManager = GUIDManager(dmd) self.dmd = dmd
class WinCommandAction(IActionBase): """ Derived class to execute an arbitrary command on a remote windows machine when a notification is triggered. """ implements(IAction) id = 'wincommand' name = 'WinCommand' actionContentInfo = IWinCommandActionContentInfo def setupAction(self, dmd): """ Configure the action with properties form the dmd. """ self.guidManager = GUIDManager(dmd) self.dmd = dmd def updateContent(self, content=None, data=None): """ Update notification content. Called when changes are submitted in the 'Edit Notification' form. """ updates = dict() for k in ('wincmd_command', 'clear_wincmd_command'): updates[k] = data.get(k) content.update(updates) def execute(self, notification, signal): """ Set up the execution environment and run a CMD command. """ self.setupAction(notification.dmd) log.debug('Executing action: {0}'.format(self.name)) if signal.clear: command = notification.content['clear_wincmd_command'] else: command = notification.content['wincmd_command'] environ = self._get_environ(notification, signal) if not command: log.debug("The CMD command was not set") return if not hasattr(environ['dev'], 'windows_servername'): log.debug("The target device is non-Windows device") return try: command = processTalSource(command, **environ) except Exception: log.error('Unable to perform TALES evaluation on "{0}" ' '-- is there an unescaped $?'.format(command)) log.debug('Executing this compiled command "{0}"'.format(command)) self._execute_command(environ['dev'], command) def _get_environ(self, notification, signal): """ Set up TALES environment for the action. """ actor = signal.event.occurrence[0].actor device = None if actor.element_uuid: device = self.guidManager.getObject(actor.element_uuid) component = None if actor.element_sub_uuid: component = self.guidManager.getObject(actor.element_sub_uuid) environ = dict(dev=device, component=component, dmd=notification.dmd) data = _signalToContextDict(signal, self.options.get('zopeurl'), notification, self.guidManager) environ.update(data) return environ def _conn_info(self, device): """ Return a ConnectionInfo object with device credentials. """ service = device.zWinScheme if hasattr(device, 'zWinUseWsmanSPN') and device.zWinUseWsmanSPN: service = 'wsman' envelope_size = getattr(device, 'zWinRMEnvelopeSize', 512000) locale = getattr(device, 'zWinRMLocale', 'en-US') code_page = getattr(device, 'zWinRSCodePage', 65001) include_dir = getattr(device, 'zWinRMKrb5includedir', None) disable_rdns = getattr(device, 'kerberos_rdns', False) connect_timeout = getattr(device, 'zWinRMConnectTimeout', 60) return ConnectionInfo( hostname=device.windows_servername() or device.manageIp, auth_type='kerberos' if '@' in device.zWinRMUser else 'basic', username=device.zWinRMUser, password=device.zWinRMPassword, scheme=device.zWinScheme, port=int(device.zWinRMPort), connectiontype='Keep-Alive', keytab=device.zWinKeyTabFilePath, dcip=device.zWinKDC, trusted_realm=device.zWinTrustedRealm, trusted_kdc=device.zWinTrustedKDC, ipaddress=device.manageIp, service=service, envelope_size=envelope_size, locale=locale, code_page=code_page, include_dir=include_dir, disable_rdns=disable_rdns, connect_timeout=connect_timeout) def _execute_command(self, device, command): """ Create WinRS client and run the command remotely. """ winrs = SingleCommandClient(self._conn_info(device)) result = winrs.run_command(str(command)) result.addCallback(lambda res: self._on_success(device, res, command)) result.addErrback(lambda err: self._on_error(device, err)) def _on_success(self, device, result, command): """ Called after the command was successfully executed and CommandResponse instance was received. """ if result.exit_code != 0: log.error("Command '{0}' failed on host {1}. Reason: {2}".format( command, device.manageIp, ' '.join(result.stderr))) else: log.debug("Command '{0}' was executed on host {1}: {2}".format( command, device.manageIp, ' '.join(result.stdout))) def _on_error(self, device, error): """ Called if an error occured when connecting to the remote host. """ if hasattr(error, 'value'): log.error(error.value) else: log.error(error) # Not twisted failure.
class WinCommandAction(IActionBase): """ Derived class to execute an arbitrary command on a remote windows machine when a notification is triggered. """ implements(IAction) id = 'wincommand' name = 'WinCommand' actionContentInfo = IWinCommandActionContentInfo def setupAction(self, dmd): """ Configure the action with properties form the dmd. """ self.guidManager = GUIDManager(dmd) self.dmd = dmd def updateContent(self, content=None, data=None): """ Update notification content. Called when changes are submitted in the 'Edit Notification' form. """ updates = dict() for k in ('wincmd_command', 'clear_wincmd_command'): updates[k] = data.get(k) content.update(updates) def execute(self, notification, signal): """ Set up the execution environment and run a CMD command. """ self.setupAction(notification.dmd) log.debug('Executing action: {0}'.format(self.name)) if signal.clear: command = notification.content['clear_wincmd_command'] else: command = notification.content['wincmd_command'] environ = self._get_environ(notification, signal) if not command: log.debug("The CMD command was not set") return if not hasattr(environ['dev'], 'windows_servername'): log.debug("The target device is non-Windows device") return try: command = processTalSource(command, **environ) except Exception: log.error('Unable to perform TALES evaluation on "{0}" ' '-- is there an unescaped $?'.format(command)) log.debug('Executing this compiled command "{0}"'.format(command)) self._execute_command(environ['dev'], command) def _get_environ(self, notification, signal): """ Set up TALES environment for the action. """ actor = signal.event.occurrence[0].actor device = None if actor.element_uuid: device = self.guidManager.getObject(actor.element_uuid) component = None if actor.element_sub_uuid: component = self.guidManager.getObject(actor.element_sub_uuid) environ = dict(dev=device, component=component, dmd=notification.dmd) data = _signalToContextDict(signal, self.options.get('zopeurl'), notification, self.guidManager) environ.update(data) return environ def _conn_info(self, device): """ Return a ConnectionInfo object with device credentials. """ return ConnectionInfo( hostname=device.windows_servername() or device.manageIp, auth_type='kerberos' if '@' in device.zWinRMUser else 'basic', username=device.zWinRMUser, password=device.zWinRMPassword, scheme=device.zWinScheme, port=int(device.zWinRMPort), connectiontype='Keep-Alive', keytab=device.zWinKeyTabFilePath, dcip=device.zWinKDC) def _execute_command(self, device, command): """ Create WinRS client and run the command remotely. """ winrs = create_single_shot_command(self._conn_info(device)) result = winrs.run_command(str(command)) result.addCallback(lambda res: self._on_success(device, res, command)) result.addErrback(lambda err: self._on_error(device, err)) def _on_success(self, device, result, command): """ Called after the command was successfully executed and CommandResponse instance was received. """ if result.exit_code != 0: log.error("Command '{0}' failed on host {1}. Reason: {2}".format( command, device.manageIp, ' '.join(result.stderr))) else: log.debug("Command '{0}' was executed on host {1}: {2}".format( command, device.manageIp, ' '.join(result.stdout))) def _on_error(self, device, error): """ Called if an error occured when connecting to the remote host. """ if hasattr(error, 'value'): log.error(error.value) else: log.error(error) # Not twisted failure.
class PagerDutyEventsAPIAction(IActionBase): """ Derived class to contact PagerDuty's events API when a notification is triggered. """ implements(IAction) id = 'pagerduty' name = 'PagerDuty' actionContentInfo = IPagerDutyEventsAPIActionContentInfo shouldExecuteInBatch = False def __init__(self): super(PagerDutyEventsAPIAction, self).__init__() def setupAction(self, dmd): self.guidManager = GUIDManager(dmd) self.dmd = dmd def execute(self, notification, signal): """ Sets up the execution environment and POSTs to PagerDuty's Event API. """ log.debug('Executing Pagerduty Events API action: %s', self.name) self.setupAction(notification.dmd) if signal.clear: eventType = EventType.RESOLVE elif signal.event.status == STATUS_ACKNOWLEDGED: eventType = EventType.ACKNOWLEDGE else: eventType = EventType.TRIGGER # Set up the TALES environment environ = {'dmd': notification.dmd, 'env':None} actor = signal.event.occurrence[0].actor device = None if actor.element_uuid: device = self.guidManager.getObject(actor.element_uuid) environ.update({'dev': device}) component = None if actor.element_sub_uuid: component = self.guidManager.getObject(actor.element_sub_uuid) environ.update({'component': component}) data = _signalToContextDict(signal, self.options.get('zopeurl'), notification, self.guidManager) environ.update(data) try: details_list = json.loads(notification.content['details']) except ValueError: raise ActionExecutionException('Invalid JSON string in details') details = dict() for kv in details_list: details[kv['key']] = kv['value'] details['zenoss'] = { 'version' : ZENOSS_VERSION, 'zenpack_version': zenpack_version() } body = {'event_type': eventType, 'client' : 'Zenoss', 'client_url': '${urls/eventUrl}', 'details' : details} for prop in REQUIRED_PROPERTIES: if prop in notification.content: body[prop] = notification.content[prop] else: raise ActionExecutionException("Required property '%s' not found" % (prop)) self._performRequest(body, environ) def _performRequest(self, body, environ): """ Actually performs the request to PagerDuty's Event API. Raises: ActionExecutionException: Some error occurred while contacting PagerDuty's Event API (e.g., API down, invalid service key). """ request_body = json.dumps(self._processTalExpressions(body, environ)) headers = {'Content-Type' : 'application/json'} req = urllib2.Request(EVENT_API_URI, request_body, headers) try: f = urllib2.urlopen(req, None, API_TIMEOUT_SECONDS) except urllib2.URLError as e: if hasattr(e, 'reason'): msg = 'Failed to contact the PagerDuty server: %s' % (e.reason) raise ActionExecutionException(msg) elif hasattr(e, 'code'): msg = 'The PagerDuty server couldn\'t fulfill the request: HTTP %d (%s)' % (e.code, e.msg) raise ActionExecutionException(msg) else: raise ActionExecutionException('Unknown URLError occurred') response = f.read() f.close() def _processTalExpressions(self, data, environ): if type(data) is str or type(data) is unicode: try: return processTalSource(data, **environ) except Exception: raise ActionExecutionException( 'Unable to perform TALES evaluation on "%s" -- is there an unescaped $?' % data) elif type(data) is list: return [self._processTalExpressions(e, environ) for e in data] elif type(data) is dict: return dict([(k, self._processTalExpressions(v, environ)) for (k, v) in data.iteritems()]) else: return data def updateContent(self, content=None, data=None): updates = dict() for k in NotificationProperties.ALL: updates[k] = data.get(k) content.update(updates)
class HttpBinAction(IActionBase): implements(IAction) id = "HttpBin" name = "HttpBin" skipfails = True actionContentInfo = IHttpBinActionContentInfo shouldExecuteInBatch = False def logEventAction(self, notificationId, eventId, status, payload=None): logPayload = "Notification {} sent event {} as {} to HttpBin with payload: {}".format( notificationId, eventId, status, payload, ) LOG.info(logPayload) def setupAction(self, dmd): self.guidManager = GUIDManager(dmd) self.dmd = dmd self.zep = getFacade("zep", self.dmd) self.statuses = { "0": "New", "1": "Acknowledged", "2": "Suppressed", "3": "Closed", "4": "Cleared", "5": "Dropped", "6": "Aged", } def updateEvent(self, evid, comment=None): if not evid: LOG.warn("Received an empty evid -- ignoring") return if comment: tryNum = 0 while tryNum < 3: try: self.zep.addNote(evid, comment, 'admin') return except (ServiceConnectionError, ZepConnectionError, ZepConnectionTimeout): tryNum += 1 LOG.error("Unable to update event: %s" % evid) return def buildPayload(self, signal, notification, data): skipfails = notification.content.get('skipfails', False) event = EventSummaryProxy(signal.event) hbSource = processTALES(notification.content.get('hbSource'), skipfails, data) hbEventClass = processTALES(notification.content.get('hbEventClass'), skipfails, data) hbDescription = processTALES(notification.content.get('hbDescription'), skipfails, data) hbFullDescription = processTALES( notification.content.get('hbFullDescription'), skipfails, data) severity = EventManagerBase.severities.get( event.severity, 'Unknown (%s)' % event.severity) clear = signal.clear payload = { "source": hbSource, "event_class": hbEventClass, "severity": 'Clear' if clear else severity, "summary": hbDescription, "details": hbFullDescription, } return payload def execute(self, notification, signal): url = "https://httpbin.org/post" headers = { 'Content-Type': 'application/json', 'Accept': 'application/json', } # Building out context data for TALES expressions data = self._signalToContextDict(signal, notification) actor = signal.event.occurrence[0].actor if not data.get('dev', False): device = None if actor.element_uuid: device = self.guidManager.getObject(actor.element_uuid) data['dev'] = device data['device'] = device if not data.get('component', False): component = None if actor.element_sub_uuid: component = self.guidManager.getObject(actor.element_sub_uuid) data['component'] = component payload = self.buildPayload(signal, notification, data) s = requests.Session() requestData = dict( url=url, headers=headers, data=json.dumps(payload), ) try: request = s.post(**requestData) except Exception as e: LOG.error("Unable to send event (%s): %s" % ( signal.event.uuid, e.message, )) comment = "Unable to send event (%s)" % e.message self.updateEvent(signal.event.uuid, comment) return if request.ok: LOG.info("Request to HttpBin returned: %s" % request.content) elif not request.ok: LOG.error("Unable to send event (%s): %s" % ( signal.event.uuid, request.content, )) comment = "Unable to send event (%s)" % request.content self.updateEvent(signal.event.uuid, comment) return responseContent = request.json() if responseContent.get('status', False) == 'failure': LOG.error("Unable to send event (%s): %s" % ( signal.event.uuid, request.content, )) comment = "Unable to send event (%s)" % request.content self.updateEvent(signal.event.uuid, comment) return status = self.statuses.get(str(signal.event.status), 'Unknown (%s)' % signal.event.status) comment = "Sent to HttpBin by Notification: %s using Trigger: %s" % ( notification.titleOrId(), data.get('trigger', {}).get( 'name', 'Unknown')) self.logEventAction(notification.titleOrId(), signal.event.uuid, status, payload) self.updateEvent(signal.event.uuid, comment) return
def apply(self, eventProxy, dmd): # See ZPS-1677 for explanation. This workaround will hopefully be # removed in the future (ZPS-1685) if eventProxy.eventClass == '/Status/Ping': return device = dmd.Devices.findDeviceByIdExact(eventProxy.device) if device and hasattr(device, 'openstackProxyComponentUUID'): LOG.debug( "tagging event on %s with openstack proxy component component uuid %s", eventProxy.device, device.openstackProxyComponentUUID) tags = [] try: component = GUIDManager(dmd).getObject( device.openstackProxyComponentUUID) if component: # Tag the event with the corresponding openstack component. tags.append(device.openstackProxyComponentUUID) # Also tag it with the openstack endpoint that the # component is part of, if possible. endpoint = component.device() tags.append(IGlobalIdentifier(endpoint).getGUID()) except Exception: LOG.debug( "Unable to determine endpoint for proxy component uuid %s", device.openstackProxyComponentUUID) # Get OSProcess component, if the event has one if eventProxy.component: for brain in ICatalogTool(dmd).search( 'Products.ZenModel.OSProcess.OSProcess', query=Eq('id', eventProxy.component)): try: osprocess = brain.getObject() except Exception: # ignore a stale entry pass else: # Figure out if we have a corresponding software component: try: for software in component.hostedSoftware(): if software.binary == osprocess.osProcessClass( ).id: # Matches! tags.append( IGlobalIdentifier(software).getGUID()) except Exception: LOG.debug( "Unable to append event for OSProcess %s", osprocess.osProcessClass().id) if tags: eventProxy.tags.addAll( 'ZenPacks.zenoss.OpenStackInfrastructure.DeviceProxyComponent', tags)
def setupAction(self, dmd): self.guidManager = GUIDManager(dmd) self.page_command = dmd.pageCommand
class UserCommandAction(IActionBase, TargetableAction): implements(IAction) id = 'user_command' name = 'User Command' actionContentInfo = IUserCommandActionContentInfo shouldExecuteInBatch = False def configure(self, options): super(UserCommandAction, self).configure(options) self.processQueue = ProcessQueue(options.get('maxCommands', 10)) self.processQueue.start() def setupAction(self, dmd): self.guidManager = GUIDManager(dmd) self.dmd = dmd def executeOnTarget(self, notification, signal, target): self.setupAction(notification.dmd) log.debug('Executing action: %s on %s', self.name, target) if signal.clear: command = notification.content['clear_body_format'] else: command = notification.content['body_format'] log.debug('Executing this command: %s', command) actor = signal.event.occurrence[0].actor device = None if actor.element_uuid: device = self.guidManager.getObject(actor.element_uuid) component = None if actor.element_sub_uuid: component = self.guidManager.getObject(actor.element_sub_uuid) user_env_format = notification.content['user_env_format'] env = dict( envvar.split('=') for envvar in user_env_format.split(';') if '=' in envvar) environ = {'dev': device, 'component': component, 'dmd': notification.dmd, 'env': env} data = _signalToContextDict(signal, self.options.get('zopeurl'), notification, self.guidManager) environ.update(data) if environ.get('evt', None): environ['evt'] = self._escapeEvent(environ['evt']) if environ.get('clearEvt', None): environ['clearEvt'] = self._escapeEvent(environ['clearEvt']) environ['user'] = getattr(self.dmd.ZenUsers, target, None) try: command = processTalSource(command, **environ) except Exception: raise ActionExecutionException('Unable to perform TALES evaluation on "%s" -- is there an unescaped $?' % command) log.debug('Executing this compiled command: "%s"' % command) _protocol = EventCommandProtocol(command) log.debug('Queueing up command action process.') self.processQueue.queueProcess( '/bin/sh', ('/bin/sh', '-c', command), env=environ['env'], processProtocol=_protocol, timeout=int(notification.content['action_timeout']), timeout_callback=_protocol.timedOut ) def getActionableTargets(self, target): ids = [target.id] if isinstance(target, GroupSettings): ids = [x.id for x in target.getMemberUserSettings()] return ids def updateContent(self, content=None, data=None): updates = dict() properties = ['body_format', 'clear_body_format', 'action_timeout', 'user_env_format'] for k in properties: updates[k] = data.get(k) content.update(updates) def _escapeEvent(self, evt): """ Escapes the relavent fields of an event context for event commands. """ if evt.message: evt.message = self._wrapInQuotes(evt.message) if evt.summary: evt.summary = self._wrapInQuotes(evt.summary) return evt def _wrapInQuotes(self, msg): """ Wraps the message in quotes, escaping any existing quote. Before: How do you pronounce "Zenoss"? After: "How do you pronounce \"Zenoss\"?" """ QUOTE = '"' BACKSLASH = '\\' return ''.join((QUOTE, msg.replace(QUOTE, BACKSLASH + QUOTE), QUOTE))
def setupAction(self, dmd): self.guidManager = GUIDManager(dmd) self.dmd = dmd
def __init__(self, dmd): self.dmd = dmd self.notification_manager = self.dmd.getDmdRoot( NotificationSubscriptionManager.root) self.guidManager = GUIDManager(dmd)
def setupAction(self, dmd): """ Configure the action with properties form the dmd. """ self.guidManager = GUIDManager(dmd) self.dmd = dmd
def setupAction(self, dmd): self.guidManager = GUIDManager(dmd) self.dmd = dmd self.page_command = dmd.pageCommand self.zenossHostname = dmd.zenossHostname
class JIRAReporter(IActionBase, TargetableAction): implements(IAction) id = 'JIRAReporter' name = 'JIRA Issue Reporter' actionContentInfo = IJIRAActionContentInfo shouldExecuteInBatch = False def __init__(self): log.debug('[research] %s : initialized' % (self.id)) super(JIRAReporter, self).__init__() self.connected = False self.jira = None def setupAction(self, dmd): log.debug('[research] setup : %s' % (self.name)) self.guidManager = GUIDManager(dmd) self.dmd = dmd def executeOnTarget(self, notification, signal, target): self.setupAction(notification.dmd) log.debug('[research] execute : %s on %s' % (self.name, target)) jiraURL = notification.content['jira_instance'] jiraUser = notification.content['jira_user'] jiraPass = notification.content['jira_password'] issueProject = notification.content['issue_project'] issueType = notification.content['issue_type'] issuePriority = notification.content['issue_priority_key'] customfields = notification.content['customfield_keypairs'] eventRawData = notification.content['event_rawdata'] serviceRoot = notification.content['service_group_root'] summary = '' description = '' if (signal.clear): log.info('[research] event cleared : %s' % (target)) description = notification.content['clear_issue_description'] else: log.warn('[research] event detected : %s' % (target)) summary = notification.content['issue_summary'] description = notification.content['issue_description'] actor = signal.event.occurrence[0].actor device = None if (actor.element_uuid): device = self.guidManager.getObject(actor.element_uuid) component = None if (actor.element_sub_uuid): component = self.guidManager.getObject(actor.element_sub_uuid) environ = { 'dev': device, 'component': component, 'dmd': notification.dmd } data = _signalToContextDict(signal, self.options.get('zopeurl'), notification, self.guidManager) environ.update(data) if (environ.get('evt', None)): environ['evt'] = self._escapeEvent(environ['evt']) if (environ.get('clearEvt', None)): environ['clearEvt'] = self._escapeEvent(environ['clearEvt']) environ['user'] = getattr(self.dmd.ZenUsers, target, None) issueValues = { 'summary': summary, 'description': description, 'eventraw': eventRawData, 'customfields': customfields } log.debug('[research] base issue values : %s' % (issueValues)) targetValues = { 'project': issueProject, 'issuetype': issueType, 'priority': issuePriority, 'serviceroot': serviceRoot } log.debug('[research] base target values : %s' % (targetValues)) self.connectJIRA(jiraURL, jiraUser, jiraPass) if (signal.clear): self.clearEventIssue(environ, targetValues, issueValues) else: self.createEventIssue(environ, targetValues, issueValues) log.debug("[research] event update reported : %s" % (jiraURL)) def getActionableTargets(self, target): ids = [target.id] if isinstance(target, GroupSettings): ids = [x.id for x in target.getMemberUserSettings()] return ids def _escapeEvent(self, evt): """ Escapes the relavent fields of an event context for event commands. """ if evt.message: evt.message = self._wrapInQuotes(evt.message) if evt.summary: evt.summary = self._wrapInQuotes(evt.summary) return evt def _wrapInQuotes(self, msg): """ Wraps the message in quotes, escaping any existing quote. Before: How do you pronounce "Zenoss"? After: "How do you pronounce \"Zenoss\"?" """ QUOTE = '"' BACKSLASH = '\\' return ''.join((QUOTE, msg.replace(QUOTE, BACKSLASH + QUOTE), QUOTE)) def updateContent(self, content=None, data=None): super(JIRAReporter, self).updateContent(content, data) updates = dict() properties = [ 'jira_instance', 'jira_user', 'jira_password', 'issue_project', 'issue_type', 'issue_priority_key', 'issue_summary', 'issue_description', 'clear_issue_summary', 'customfield_keypairs', 'event_rawdata', 'service_group_root' ] for k in properties: updates[k] = data.get(k) content.update(updates) # jira client methods def connectJIRA(self, URL, user, password): log.debug('[research] : connecting to %s' % (URL)) basicauth = (user, password) try: self.jira = JIRA(options={'server': URL}, basic_auth=basicauth) self.connected = True log.debug('[research] : connected to %s' % (URL)) except JIRAError as jx: log.error('[research] jira.error : %s' % (jx)) except Exception as ex: log.debug('[research] exception : %s' % (ex)) finally: log.debug('[research] connection info (%s)' % (URL)) def setIssueValues(self, data, targetValues, issueValues): log.debug('[research] process issue values') if ('project' in targetValues): issueValues['project'] = {'key': targetValues['project']} if ('issuetype' in targetValues): issueValues['issuetype'] = {'name': targetValues['issuetype']} issueValues['summary'] = self.processEventFields( data, issueValues['summary'], 'Summary') issueValues['description'] = self.processEventFields( data, issueValues['description'], 'Description') log.debug('[research] issue values : %s' % (issueValues)) return issueValues def setCustomFieldValues(self, data, targetValues, issueValues): log.debug('[research] process customfield values') customfields = None if ('customfields' in issueValues): customfields = issueValues['customfields'] del issueValues['customfields'] if (customfields): customfields = json.loads(customfields) else: customfields = {} if ('priority' in targetValues): customfields['Priority'] = targetValues['priority'] if ('serviceroot' in targetValues): customfields['Service'] = targetValues['serviceroot'] if ('eventraw' in issueValues): customfields['Zenoss EventRAW'] = self.processEventFields( data, issueValues['eventraw'], 'Event Raw Data') del issueValues['eventraw'] customfields = self.setZenossFields(data, customfields) log.debug('[research] customfield values : %s' % (customfields)) createmeta = self.jira.createmeta( projectKeys=targetValues['project'], issuetypeNames=targetValues['issuetype'], expand='projects.issuetypes.fields') issuetype = None fields = None if (createmeta): if ('projects' in createmeta): if ('issuetypes' in createmeta['projects'][0]): issuetype = createmeta['projects'][0]['issuetypes'][0] log.debug('[research] createmeta issuetype : available') if (issuetype): if ('fields' in issuetype): fields = issuetype['fields'] log.debug('[research] createmeta fields : available') else: log.debug('[research] createmeta : NOT AVAILABLE') if (fields): for fKey, fAttr in fields.iteritems(): if ('name' in fAttr): if (fAttr['name'] in customfields): log.debug('[research] customfield found') fieldValue = customfields[fAttr['name']] if ('allowedValues' in fAttr): log.debug('[research] has customfield options') fieldValue = self.getCustomFieldOption( fAttr['allowedValues'], fieldValue) if (fieldValue): log.debug('[research] cf (%s) set to %s' % (fAttr['name'], fieldValue)) try: if (fAttr['schema']['type'] in ['array']): fieldValue = [fieldValue] except: pass issueValues[fKey] = fieldValue log.debug('[research] issue customfields : %s' % (issueValues)) return issueValues def setZenossFields(self, data, customfields): log.debug('[research] process customfield values') if (not customfields): customfields = {} zEventID = self.getEventID(data) if (zEventID): customfields['Zenoss ID'] = zEventID zDeviceID = self.getDeviceID(data) if (zDeviceID): customfields['Zenoss DevID'] = zDeviceID zBaseURL = self.getBaseURL(data) if (zBaseURL): customfields['Zenoss Instance'] = zBaseURL zEnv = self.getEnvironment(data) if (zEnv): customfields['Environment'] = zEnv if ('Service' in customfields): zSrvc = self.getServiceGroup(data, customfields['Service']) if (zSrvc): customfields['Service'] = zSrvc zLoc = self.getLocation(data) if (zLoc): customfields['DataCenter'] = zLoc log.debug('[research] Zenoss customfields : %s' % (customfields)) return customfields def createEventIssue(self, data, targetValues, issueValues): log.debug('[research] create event issue') project = targetValues['project'] eventID = self.getEventID(data) baseHost = self.getBaseHost(data) deviceID = self.getDeviceID(data) hasIssues = self.hasEventIssues(project, baseHost, eventID) if (hasIssues): log.warn('[research] issue exists for EventID %s' % (eventID)) else: issueValues = self.setIssueValues(data, targetValues, issueValues) issueValues = self.setCustomFieldValues(data, targetValues, issueValues) newissue = self.jira.create_issue(fields=issueValues) log.info('[research] issue created : %s' % (newissue.key)) def clearEventIssue(self, data, targetValues, issueValues): log.debug('[research] clear event issue') project = targetValues['project'] eventID = self.getEventID(data) baseHost = self.getBaseHost(data) issues = self.getEventIssues(project, baseHost, eventID) if (not issues): log.warn('[research] no issue mapped to clear : %s' % (eventID)) return issueValues = self.setIssueValues(data, targetValues, issueValues) description = issueValues['description'] eventCLR = self.getEventClearDate(data) for issue in issues: zenossCLR = self.getCustomFieldID(issue, 'Zenoss EventCLR') issuekey = issue.key if (zenossCLR): issue.update(fields={zenossCLR: eventCLR}) log.info('[research] EventCLR updated : %s' % (issuekey)) if (description): self.jira.add_comment(issue.key, description) log.info('[research] EventCLR commented : %s' % (issuekey)) def hasEventIssues(self, project, eventINS, eventID): log.debug('[research] has event issues') issues = self.getEventIssues(project, eventINS, eventID) log.debug('[research] has event issues : %s' % (len(issues) > 0)) return (len(issues) > 0) def getEventIssues(self, project, eventINS, eventID): log.debug('[research] get event issues') issues = [] if (eventID): issueFilter = '(project = "%s")' issueFilter += ' and ("Zenoss Instance" ~ "%s")' issueFilter += ' and ("Zenoss ID" ~ "%s")' issueFilter = issueFilter % (project, eventINS, eventID) log.debug('[research] event issue filter : %s' % (issueFilter)) try: issues = self.jira.search_issues(issueFilter) log.debug('[research] event issues : %s' % (len(issues))) except JIRAError as jx: log.error('[research] jira.error : %s' % (jx)) except Exception as ex: log.error('[research] exception : %s' % (ex)) return issues def getCustomFieldOption(self, fieldOptions, value, defaultValue='', exactMatch=False, firstMatch=False): log.debug('[research] get customfield options') if (not value): return None if (not fieldOptions): return None bDefault = False matchValue = None if (value.__class__.__name__ in ('str', 'unicode')): value = value.split(';') if (len(value) > 1): defaultValue = value[1].strip() log.debug('[research] option default : %s' % (defaultValue)) value = value[0].strip() if (not value): log.debug('[research] invalid option value : %s' % (value)) for av in fieldOptions: if ('value' in av): valueName = av['value'] elif ('name' in av): valueName = av['name'] else: continue if (value): if (value.__class__.__name__ in ('str', 'unicode')): if (exactMatch): value = '^%s$' % (value) if (re.match(value, valueName, re.IGNORECASE)): if ('id' in av): matchValue = {'id': av['id']} else: matchValue = valueName if (firstMatch): break if (not defaultValue): continue if (defaultValue.__class__.__name__ in ('str', 'unicode')): if (re.match(defaultValue, valueName, re.IGNORECASE)): bDefault = True if ('id' in av): defaultValue = {'id': av['id']} else: defaultValue = valueName if (not value): break if (not matchValue): if (bDefault): log.debug('[research] default option : %s' % (defaultValue)) matchValue = defaultValue return matchValue def getCustomFieldID(self, issue, fieldName): log.debug('[research] get issue customfield ID') fieldID = '' for field in self.jira.fields(): if (field['name'].lower() == fieldName.lower()): log.debug('[research] customfield matched %s' % (fieldName)) fieldID = field['id'] break return fieldID def getEventID(self, data): log.debug('[research] get eventID') eventID = '${evt/evid}' try: eventID = self.processEventFields(data, eventID, 'eventID') except Exception: eventID = '' return eventID def getEventClearDate(self, data): log.debug('[research] get event clear date') eventCLR = '${evt/stateChange}' try: eventCLR = self.processEventFields(data, eventCLR, 'clear date') except Exception: eventCLR = '' if (eventCLR): try: eventCLR = datatime.strptime( eventCLR, '%Y-%m-%d %H:%M:%S').isoformat()[:19] + '.000+0000' except: try: eventCLR = datatime.strptime( eventCLR, '%Y-%m-%d %H:%M:%S.%f').isoformat()[:19] + '.000+0000' except: eventCLR = '' if (not eventCLR): eventCLR = datetime.now().isoformat()[:19] + '.000+0000' return eventCLR def getDeviceID(self, data): log.debug('[research] get deviceID') deviceID = '${evt/device}' try: deviceID = self.processEventFields(data, deviceID, 'deviceID') except Exception: deviceID = '' return deviceID def getBaseURL(self, data): log.debug('[research] get baseURL') baseURL = '${urls/baseUrl}' try: baseURL = self.processEventFields(data, baseURL, 'baseURL') except Exception: baseURL = '' if (baseURL): baseURL = self.getSiteURI(baseURL) return baseURL def getBaseHost(self, data): log.debug('[research] get baseHost') baseHost = '' baseHost = self.getBaseURL(data) return urlparse(baseHost).hostname def getEnvironment(self, data): log.debug('[research] get environment') eventENV = '${dev/getProductionStateString}' try: eventENV = self.processEventFields(data, eventENV, 'Event ENV (dev)') except Exception: eventENV = '${evt/prodState}' try: eventENV = self.processEventFields(data, eventENV, 'Event ENV (evt)') except Exception: eventENV = '' return eventENV def getServiceGroup(self, data, valuePattern): log.debug('[research] get service group') srvcGRP = '${evt/DeviceGroups}' try: srvcGRP = self.processEventFields(data, srvcGRP, 'Service') srvcGRP = srvcGRP.split('|') except Exception: srvcGRP = [] extendGRP = [] defaultGRP = None valuePattern = valuePattern.split(';') if (len(valuePattern) > 1): defaultGRP = valuePattern[1].strip() valuePattern = valuePattern[0].strip() if (valuePattern): for ix in range(len(srvcGRP)): svcm = re.match(valuePattern, srvcGRP[ix], re.IGNORECASE) if (svcm): valGRP = svcm.group(2) if (valGRP): valGRP = valGRP.split('/') for ex in range(len(valGRP)): extendGRP.append('\(' + '/'.join(valGRP[:ex + 1]) + '\)') log.debug('[research] service group patterns : %s' % (extendGRP)) if (extendGRP): srvcGRP = '.*(' + '|'.join(extendGRP) + ').*' else: srvcGRP = '' if (defaultGRP): srvcGRP += ';' + defaultGRP log.debug('[research] service pattern : %s' % (srvcGRP)) return srvcGRP def getLocation(self, data): log.debug('[research] get location') loc = '${evt/Location}' try: loc = self.processEventFields(data, loc, 'Location') except Exception: loc = '' for locx in loc.split('/'): if (locx): return locx return loc def getSiteURI(self, source): outURI = re.findall("((http|https)://[a-zA-Z0-9-\.:]*)", source) if (outURI.__class__.__name__ in ['list']): if (len(outURI) > 0): if (len(outURI[0]) > 0): outURI = outURI[0][0] log.debug('[research] zenoss URL : %s' % (outURI)) outURI = urlparse(source) return "%s://%s" % (outURI.scheme, outURI.netloc) def processEventFields(self, data, content, name): log.debug('[research] process TAL expressions') try: content = processTalSource(content, **data) log.debug('[research] %s : %s' % (name, content)) except Exception: log.debug('[research] unable to process : %s' % (name)) raise ActionExecutionException( '[research] failed to process TAL in %s' % (name)) if (content == 'None'): content = '' return content def removeEmptyListElements(self, listObj): log.debug('[research] remove empty list elements') bDirty = True for lx in range(len(listObj)): try: ix = listObj.index('') listObj[ix:ix + 1] = [] except Exception: bDirty = False try: ix = listObj.index() listObj[ix:ix + 1] = [] if (not bDirty): bDirty = True except Exception: if (not bDirty): bDirty = False if (not bDirty): break return listObj def processServiceGroupUsingRoot(self, serviceGroups, rootPattern): log.debug('[research] filter service group values') return serviceGroups
def setupAction(self, dmd): self.guidManager = GUIDManager(dmd)
class JIRAReporter(IActionBase, TargetableAction): implements(IAction) id = 'JIRAReporter' name = 'JIRA Issue Reporter' actionContentInfo = IJIRAActionContentInfo shouldExecuteInBatch = False def __init__(self): log.debug('[research] %s : initialized' % (self.id)) super(JIRAReporter, self).__init__() self.connected = False self.jira = None def setupAction(self, dmd): log.debug('[research] setup : %s' % (self.name)) self.guidManager = GUIDManager(dmd) self.dmd = dmd def executeOnTarget(self, notification, signal, target): self.setupAction(notification.dmd) log.debug('[research] execute : %s on %s' % (self.name, target)) jiraURL = notification.content['jira_instance'] jiraUser = notification.content['jira_user'] jiraPass = notification.content['jira_password'] issueProject = notification.content['issue_project'] issueType = notification.content['issue_type'] issuePriority = notification.content['issue_priority_key'] customfields = notification.content['customfield_keypairs'] eventRawData = notification.content['event_rawdata'] serviceRoot = notification.content['service_group_root'] summary = '' description = '' if (signal.clear): log.info('[research] event cleared : %s' % (target)) description = notification.content['clear_issue_description'] else: log.warn('[research] event detected : %s' % (target)) summary = notification.content['issue_summary'] description = notification.content['issue_description'] actor = signal.event.occurrence[0].actor device = None if (actor.element_uuid): device = self.guidManager.getObject(actor.element_uuid) component = None if (actor.element_sub_uuid): component = self.guidManager.getObject(actor.element_sub_uuid) environ = { 'dev': device, 'component': component, 'dmd': notification.dmd } data = _signalToContextDict( signal, self.options.get('zopeurl'), notification, self.guidManager ) environ.update(data) if (environ.get('evt', None)): environ['evt'] = self._escapeEvent(environ['evt']) if (environ.get('clearEvt', None)): environ['clearEvt'] = self._escapeEvent(environ['clearEvt']) environ['user'] = getattr(self.dmd.ZenUsers, target, None) issueValues = { 'summary' : summary, 'description' : description, 'eventraw' : eventRawData, 'customfields' : customfields } log.debug('[research] base issue values : %s' % (issueValues)) targetValues = { 'project' : issueProject, 'issuetype' : issueType, 'priority' : issuePriority, 'serviceroot' : serviceRoot } log.debug('[research] base target values : %s' % (targetValues)) self.connectJIRA(jiraURL, jiraUser, jiraPass) if (signal.clear): self.clearEventIssue(environ, targetValues, issueValues) else: self.createEventIssue(environ, targetValues, issueValues) log.debug("[research] event update reported : %s" % (jiraURL)); def getActionableTargets(self, target): ids = [target.id] if isinstance(target, GroupSettings): ids = [x.id for x in target.getMemberUserSettings()] return ids def _escapeEvent(self, evt): """ Escapes the relavent fields of an event context for event commands. """ if evt.message: evt.message = self._wrapInQuotes(evt.message) if evt.summary: evt.summary = self._wrapInQuotes(evt.summary) return evt def _wrapInQuotes(self, msg): """ Wraps the message in quotes, escaping any existing quote. Before: How do you pronounce "Zenoss"? After: "How do you pronounce \"Zenoss\"?" """ QUOTE = '"' BACKSLASH = '\\' return ''.join((QUOTE, msg.replace(QUOTE, BACKSLASH + QUOTE), QUOTE)) def updateContent(self, content=None, data=None): super(JIRAReporter, self).updateContent(content, data) updates = dict() properties = [ 'jira_instance', 'jira_user', 'jira_password', 'issue_project', 'issue_type', 'issue_priority_key', 'issue_summary', 'issue_description', 'clear_issue_summary', 'customfield_keypairs', 'event_rawdata', 'service_group_root' ] for k in properties: updates[k] = data.get(k) content.update(updates) # jira client methods def connectJIRA(self, URL, user, password): log.debug('[research] : connecting to %s' % (URL)) basicauth = (user, password) try: self.jira = JIRA( options = {'server' : URL}, basic_auth = basicauth ) self.connected = True log.debug('[research] : connected to %s' % (URL)) except JIRAError as jx: log.error('[research] jira.error : %s' % (jx)) except Exception as ex: log.debug('[research] exception : %s' % (ex)) finally: log.debug('[research] connection info (%s)' % (URL)) def setIssueValues(self, data, targetValues, issueValues): log.debug('[research] process issue values') if ('project' in targetValues): issueValues['project'] = { 'key' : targetValues['project'] } if ('issuetype' in targetValues): issueValues['issuetype'] = { 'name' : targetValues['issuetype'] } issueValues['summary'] = self.processEventFields( data, issueValues['summary'], 'Summary' ) issueValues['description'] = self.processEventFields( data, issueValues['description'], 'Description' ) log.debug('[research] issue values : %s' % (issueValues)) return issueValues def setCustomFieldValues(self, data, targetValues, issueValues): log.debug('[research] process customfield values') customfields = None if ('customfields' in issueValues): customfields = issueValues['customfields'] del issueValues['customfields'] if (customfields): customfields = json.loads(customfields) else: customfields = {} if ('priority' in targetValues): customfields['Priority'] = targetValues['priority'] if ('serviceroot' in targetValues): customfields['Service'] = targetValues['serviceroot'] if ('eventraw' in issueValues): customfields['Zenoss EventRAW'] = self.processEventFields( data, issueValues['eventraw'], 'Event Raw Data' ) del issueValues['eventraw'] customfields = self.setZenossFields(data, customfields) log.debug('[research] customfield values : %s' % (customfields)) createmeta = self.jira.createmeta( projectKeys = targetValues['project'], issuetypeNames = targetValues['issuetype'], expand = 'projects.issuetypes.fields' ) issuetype = None fields = None if (createmeta): if ('projects' in createmeta): if ('issuetypes' in createmeta['projects'][0]): issuetype = createmeta['projects'][0]['issuetypes'][0] log.debug('[research] createmeta issuetype : available') if (issuetype): if ('fields' in issuetype): fields = issuetype['fields'] log.debug('[research] createmeta fields : available') else: log.debug('[research] createmeta : NOT AVAILABLE') if (fields): for fKey, fAttr in fields.iteritems(): if ('name' in fAttr): if (fAttr['name'] in customfields): log.debug('[research] customfield found') fieldValue = customfields[fAttr['name']] if ('allowedValues' in fAttr): log.debug('[research] has customfield options') fieldValue = self.getCustomFieldOption( fAttr['allowedValues'], fieldValue ) if (fieldValue): log.debug('[research] cf (%s) set to %s' % ( fAttr['name'], fieldValue) ) try: if (fAttr['schema']['type'] in ['array']): fieldValue = [fieldValue] except: pass issueValues[fKey] = fieldValue log.debug('[research] issue customfields : %s' % (issueValues)) return issueValues def setZenossFields(self, data, customfields): log.debug('[research] process customfield values') if (not customfields): customfields = {} zEventID = self.getEventID(data) if (zEventID): customfields['Zenoss ID'] = zEventID zDeviceID = self.getDeviceID(data) if (zDeviceID): customfields['Zenoss DevID'] = zDeviceID zBaseURL = self.getBaseURL(data) if (zBaseURL): customfields['Zenoss Instance'] = zBaseURL zEnv = self.getEnvironment(data) if (zEnv): customfields['Environment'] = zEnv if ('Service' in customfields): zSrvc = self.getServiceGroup(data, customfields['Service']) if (zSrvc): customfields['Service'] = zSrvc zLoc = self.getLocation(data) if (zLoc): customfields['DataCenter'] = zLoc log.debug('[research] Zenoss customfields : %s' % (customfields)) return customfields def createEventIssue(self, data, targetValues, issueValues): log.debug('[research] create event issue') project = targetValues['project'] eventID = self.getEventID(data) baseHost = self.getBaseHost(data) deviceID = self.getDeviceID(data) hasIssues = self.hasEventIssues(project, baseHost, eventID) if (hasIssues): log.warn('[research] issue exists for EventID %s' % (eventID)) else: issueValues = self.setIssueValues( data, targetValues, issueValues ) issueValues = self.setCustomFieldValues( data, targetValues, issueValues ) newissue = self.jira.create_issue(fields = issueValues) log.info('[research] issue created : %s' % (newissue.key)) def clearEventIssue(self, data, targetValues, issueValues): log.debug('[research] clear event issue') project = targetValues['project'] eventID = self.getEventID(data) baseHost = self.getBaseHost(data) issues = self.getEventIssues(project, baseHost, eventID) if (not issues): log.warn('[research] no issue mapped to clear : %s' % (eventID)) return issueValues = self.setIssueValues( data, targetValues, issueValues ) description = issueValues['description'] eventCLR = self.getEventClearDate(data) for issue in issues: zenossCLR = self.getCustomFieldID(issue, 'Zenoss EventCLR') issuekey = issue.key if (zenossCLR): issue.update(fields = {zenossCLR : eventCLR}) log.info('[research] EventCLR updated : %s' % (issuekey)) if (description): self.jira.add_comment(issue.key, description) log.info('[research] EventCLR commented : %s' % (issuekey)) def hasEventIssues(self, project, eventINS, eventID): log.debug('[research] has event issues') issues = self.getEventIssues(project, eventINS, eventID) log.debug('[research] has event issues : %s' % (len(issues) > 0)) return (len(issues) > 0) def getEventIssues(self, project, eventINS, eventID): log.debug('[research] get event issues') issues = [] if (eventID): issueFilter = '(project = "%s")' issueFilter += ' and ("Zenoss Instance" ~ "%s")' issueFilter += ' and ("Zenoss ID" ~ "%s")' issueFilter = issueFilter % (project, eventINS, eventID) log.debug('[research] event issue filter : %s' % (issueFilter)) try: issues = self.jira.search_issues(issueFilter) log.debug('[research] event issues : %s' % (len(issues))) except JIRAError as jx: log.error('[research] jira.error : %s' % (jx)) except Exception as ex: log.error('[research] exception : %s' % (ex)) return issues def getCustomFieldOption( self, fieldOptions, value, defaultValue = '', exactMatch = False, firstMatch = False): log.debug('[research] get customfield options') if (not value): return None if (not fieldOptions): return None bDefault = False matchValue = None if (value.__class__.__name__ in ('str', 'unicode')): value = value.split(';') if (len(value) > 1): defaultValue = value[1].strip() log.debug('[research] option default : %s' % (defaultValue)) value = value[0].strip() if (not value): log.debug('[research] invalid option value : %s' % (value)) for av in fieldOptions: if ('value' in av): valueName = av['value'] elif ('name' in av): valueName = av['name'] else: continue if (value): if (value.__class__.__name__ in ('str', 'unicode')): if (exactMatch): value = '^%s$' % (value) if (re.match(value, valueName, re.IGNORECASE)): if ('id' in av): matchValue = {'id' : av['id']} else: matchValue = valueName if (firstMatch): break if (not defaultValue): continue if (defaultValue.__class__.__name__ in ('str', 'unicode')): if (re.match(defaultValue, valueName, re.IGNORECASE)): bDefault = True if ('id' in av): defaultValue = {'id' : av['id']} else: defaultValue = valueName if (not value): break if (not matchValue): if (bDefault): log.debug('[research] default option : %s' % (defaultValue)) matchValue = defaultValue return matchValue def getCustomFieldID(self, issue, fieldName): log.debug('[research] get issue customfield ID') fieldID = '' for field in self.jira.fields(): if (field['name'].lower() == fieldName.lower()): log.debug('[research] customfield matched %s' % (fieldName)) fieldID = field['id'] break return fieldID def getEventID(self, data): log.debug('[research] get eventID') eventID = '${evt/evid}' try: eventID = self.processEventFields(data, eventID, 'eventID') except Exception: eventID = '' return eventID def getEventClearDate(self, data): log.debug('[research] get event clear date') eventCLR = '${evt/stateChange}' try: eventCLR = self.processEventFields(data, eventCLR, 'clear date') except Exception: eventCLR = '' if (eventCLR): try: eventCLR = datatime.strptime( eventCLR, '%Y-%m-%d %H:%M:%S' ).isoformat()[:19] + '.000+0000' except: try: eventCLR = datatime.strptime( eventCLR, '%Y-%m-%d %H:%M:%S.%f' ).isoformat()[:19] + '.000+0000' except: eventCLR = '' if (not eventCLR): eventCLR = datetime.now().isoformat()[:19] + '.000+0000' return eventCLR def getDeviceID(self, data): log.debug('[research] get deviceID') deviceID = '${evt/device}' try: deviceID = self.processEventFields(data, deviceID, 'deviceID') except Exception: deviceID = '' return deviceID def getBaseURL(self, data): log.debug('[research] get baseURL') baseURL = '${urls/baseUrl}' try: baseURL = self.processEventFields(data, baseURL, 'baseURL') except Exception: baseURL = '' if (baseURL): baseURL = self.getSiteURI(baseURL) return baseURL def getBaseHost(self, data): log.debug('[research] get baseHost') baseHost = '' baseHost = self.getBaseURL(data) return urlparse(baseHost).hostname def getEnvironment(self, data): log.debug('[research] get environment') eventENV = '${dev/getProductionStateString}' try: eventENV = self.processEventFields( data, eventENV, 'Event ENV (dev)' ) except Exception: eventENV = '${evt/prodState}' try: eventENV = self.processEventFields( data, eventENV, 'Event ENV (evt)' ) except Exception: eventENV = '' return eventENV def getServiceGroup(self, data, valuePattern): log.debug('[research] get service group') srvcGRP = '${evt/DeviceGroups}' try: srvcGRP = self.processEventFields(data, srvcGRP, 'Service') srvcGRP = srvcGRP.split('|') except Exception: srvcGRP = [] extendGRP = [] defaultGRP = None valuePattern = valuePattern.split(';') if (len(valuePattern) > 1): defaultGRP = valuePattern[1].strip() valuePattern = valuePattern[0].strip() if (valuePattern): for ix in range(len(srvcGRP)): svcm = re.match(valuePattern, srvcGRP[ix], re.IGNORECASE) if (svcm): valGRP = svcm.group(2) if (valGRP): valGRP = valGRP.split('/') for ex in range(len(valGRP)): extendGRP.append( '\(' + '/'.join(valGRP[:ex + 1]) + '\)' ) log.debug('[research] service group patterns : %s' % (extendGRP)) if (extendGRP): srvcGRP = '.*(' + '|'.join(extendGRP) + ').*' else: srvcGRP = '' if (defaultGRP): srvcGRP += ';' + defaultGRP log.debug('[research] service pattern : %s' % (srvcGRP)) return srvcGRP def getLocation(self, data): log.debug('[research] get location') loc = '${evt/Location}' try: loc = self.processEventFields(data, loc, 'Location') except Exception: loc = '' for locx in loc.split('/'): if (locx): return locx return loc def getSiteURI(self, source): outURI = re.findall("((http|https)://[a-zA-Z0-9-\.:]*)", source) if (outURI.__class__.__name__ in ['list']): if (len(outURI) > 0): if (len(outURI[0]) > 0): outURI = outURI[0][0] log.debug('[research] zenoss URL : %s' % (outURI)) outURI = urlparse(source) return "%s://%s" % (outURI.scheme, outURI.netloc) def processEventFields(self, data, content, name): log.debug('[research] process TAL expressions') try: content = processTalSource(content, **data) log.debug('[research] %s : %s' % (name, content)) except Exception: log.debug('[research] unable to process : %s' % (name)) raise ActionExecutionException( '[research] failed to process TAL in %s' % (name)) if (content == 'None'): content = '' return content def removeEmptyListElements(self, listObj): log.debug('[research] remove empty list elements') bDirty = True for lx in range(len(listObj)): try: ix = listObj.index('') listObj[ix:ix + 1] = [] except Exception: bDirty = False try: ix = listObj.index() listObj[ix:ix + 1] = [] if (not bDirty): bDirty = True except Exception: if (not bDirty): bDirty = False if (not bDirty): break return listObj def processServiceGroupUsingRoot(self, serviceGroups, rootPattern): log.debug('[research] filter service group values') return serviceGroups
class CommandAction(IActionBase, TargetableAction): implements(IAction) id = 'command' name = 'Command' actionContentInfo = ICommandActionContentInfo shouldExecuteInBatch = False def configure(self, options): super(CommandAction, self).configure(options) self.processQueue = ProcessQueue(options.get('maxCommands', 10)) self.processQueue.start() def setupAction(self, dmd): self.guidManager = GUIDManager(dmd) self.dmd = dmd self.zenossHostname = dmd.zenossHostname def execute(self, notification, signal): # check to see if we have any targets if notification.recipients: return super(CommandAction, self).execute(notification, signal) else: self._execute(notification, signal) def executeOnTarget(self, notification, signal, target): log.debug('Executing command action: %s on %s', self.name, target) environ = {} environ['user'] = getattr(self.dmd.ZenUsers, target, None) self._execute(notification, signal, environ) def _execute(self, notification, signal, extra_env= {}): self.setupAction(notification.dmd) log.debug('Executing command action: %s', self.name) if signal.clear: command = notification.content['clear_body_format'] else: command = notification.content['body_format'] log.debug('Executing this command: %s', command) actor = signal.event.occurrence[0].actor device = None if actor.element_uuid: device = self.guidManager.getObject(actor.element_uuid) component = None if actor.element_sub_uuid: component = self.guidManager.getObject(actor.element_sub_uuid) user_env_format = notification.content.get('user_env_format', '') or '' env = os.environ.copy() user_env = dict( envvar.split('=', 1) for envvar in user_env_format.split(';') if '=' in envvar) env.update(user_env) environ = {'dev': device, 'component': component, 'dmd': notification.dmd, 'env': env} data = self._signalToContextDict(signal, notification) environ.update(data) if environ.get('evt', None): environ['evt'] = self._escapeEvent(environ['evt']) if environ.get('clearEvt', None): environ['clearEvt'] = self._escapeEvent(environ['clearEvt']) environ.update(extra_env) # Get the proper command command = processTalSource(command, **environ) log.debug('Executing this compiled command: "%s"' % command) _protocol = EventCommandProtocol(command, notification) log.debug('Queueing up command action process.') self.processQueue.queueProcess( '/bin/sh', ('/bin/sh', '-c', command), env=environ['env'], processProtocol=_protocol, timeout=int(notification.content['action_timeout']), timeout_callback=_protocol.timedOut ) def getActionableTargets(self, target): ids = [target.id] if isinstance(target, GroupSettings): ids = [x.id for x in target.getMemberUserSettings()] return ids def _escapeEvent(self, evt): """ Escapes the relavent fields of an event context for event commands. """ if evt.message: evt.message = self._wrapInQuotes(evt.message) if evt.summary: evt.summary = self._wrapInQuotes(evt.summary) return evt def _wrapInQuotes(self, msg): """ Wraps the message in quotes, escaping any existing quote. Before: How do you pronounce "Zenoss"? After: "How do you pronounce \"Zenoss\"?" """ QUOTE = '"' BACKSLASH = '\\' return ''.join((QUOTE, msg.replace(QUOTE, BACKSLASH + QUOTE), QUOTE))
def setupAction(self, dmd): self.guidManager = GUIDManager(dmd) self.zenossHostname = dmd.zenossHostname
class PagerDutyEventsAPIAction(IActionBase): """ Derived class to contact PagerDuty's events API when a notification is triggered. """ implements(IAction) id = 'pagerduty' name = 'PagerDuty' actionContentInfo = IPagerDutyEventsAPIActionContentInfo shouldExecuteInBatch = False def __init__(self): super(PagerDutyEventsAPIAction, self).__init__() def setupAction(self, dmd): self.guidManager = GUIDManager(dmd) self.dmd = dmd def execute(self, notification, signal): """ Sets up the execution environment and POSTs to PagerDuty's Event API. """ log.debug('Executing Pagerduty Events API action: %s', self.name) self.setupAction(notification.dmd) if signal.clear: eventType = EventType.RESOLVE elif signal.event.status == STATUS_ACKNOWLEDGED: eventType = EventType.ACKNOWLEDGE else: eventType = EventType.TRIGGER # Set up the TALES environment environ = {'dmd': notification.dmd, 'env': None} actor = signal.event.occurrence[0].actor device = None if actor.element_uuid: device = self.guidManager.getObject(actor.element_uuid) environ.update({'dev': device}) component = None if actor.element_sub_uuid: component = self.guidManager.getObject(actor.element_sub_uuid) environ.update({'component': component}) data = _signalToContextDict(signal, self.options.get('zopeurl'), notification, self.guidManager) environ.update(data) try: detailsList = json.loads(notification.content['details']) except ValueError: raise ActionExecutionException('Invalid JSON string in details') details = dict() for kv in detailsList: details[kv['key']] = kv['value'] details['zenoss'] = { 'version': ZENOSS_VERSION, 'zenpack_version': zenpack_version(), } payload = { 'severity': '${evt/severity}', 'class': '${evt/eventClass}', 'custom_details': details, } body = { 'event_action': eventType, 'dedup_key': data['evt'].evid, 'payload': payload } for prop in REQUIRED_PROPERTIES: if prop in notification.content: payload.update({prop: notification.content[prop]}) else: raise ActionExecutionException( "Required property '%s' not found" % prop) if NotificationProperties.SERVICE_KEY in notification.content: body.update({'routing_key': notification.content['serviceKey']}) else: raise ActionExecutionException( "API Key for PagerDuty service was not found. " "Did you configure a notification correctly?") self._performRequest(body, environ) def _performRequest(self, body, environ): """ Actually performs the request to PagerDuty's Event API. Raises: ActionExecutionException: Some error occurred while contacting PagerDuty's Event API (e.g., API down, invalid service key). """ bodyWithProcessedTalesExpressions = self._processTalExpressions( body, environ) bodyWithProcessedTalesExpressions['payload'][ 'severity'] = EVENT_MAPPING[ bodyWithProcessedTalesExpressions['payload']['severity']] requestBody = json.dumps(bodyWithProcessedTalesExpressions) headers = {'Content-Type': 'application/json'} req = urllib2.Request(EVENT_API_URI, requestBody, headers) try: # bypass default handler SVC-1819 opener = urllib2.build_opener() f = opener.open(req, None, API_TIMEOUT_SECONDS) except urllib2.URLError as e: if hasattr(e, 'reason'): msg = 'Failed to contact the PagerDuty server: %s' % (e.reason) raise ActionExecutionException(msg) elif hasattr(e, 'code'): msg = 'The PagerDuty server couldn\'t fulfill the request: HTTP %d (%s)' % ( e.code, e.msg) raise ActionExecutionException(msg) else: raise ActionExecutionException('Unknown URLError occurred') response = f.read() log.debug('PagerDuty response: %s', response) f.close() def _processTalExpression(self, value, environ): if type(value) is str or type(value) is unicode: if '${' not in value: return value try: return processTalSource(value, **environ) except Exception: raise ActionExecutionException( 'Unable to perform TALES evaluation on "%s" -- is there an unescaped $?' % value) else: return value def _processTalExpressions(self, data, environ): for payloadKey in data['payload']: if not payloadKey.startswith('custom_details'): data['payload'][payloadKey] = self._processTalExpression( data['payload'][payloadKey], environ) for detailKey in data['payload']['custom_details']: data['payload']['custom_details'][ detailKey] = self._processTalExpression( data['payload']['custom_details'][detailKey], environ) return data def updateContent(self, content=None, data=None): updates = dict() for k in NotificationProperties.ALL: updates[k] = data.get(k) content.update(updates)