def get_samples(self, manager, cache, resources): self._inspection_duration = self._record_poll_time() for instance in resources: instance_name = util.instance_name(instance) LOG.debug(_("checking net info for instance %s"), instance.id) try: vnics = self._get_vnics_for_instance(cache, manager.inspector, instance) for vnic, info in vnics: LOG.debug( self.NET_USAGE_MESSAGE, instance_name, vnic.name, self._get_rx_info(info), self._get_tx_info(info), ) yield self._get_sample(instance, vnic, info) except virt_inspector.InstanceNotFoundException as err: # Instance was deleted while getting samples. Ignore it. LOG.debug(_("Exception while getting samples %s"), err) except NotImplementedError: # Selected inspector does not implement this pollster. LOG.debug( _("%(inspector)s does not provide data for " " %(pollster)s"), ({"inspector": manager.inspector.__class__.__name__, "pollster": self.__class__.__name__}), ) except Exception as err: LOG.warning(_("Ignoring instance %(name)s: %(error)s") % ({"name": instance_name, "error": err})) LOG.exception(err)
def _process_data(self, ctxt, version, method, namespace, args): """Process a message in a new thread. If the proxy object we have has a dispatch method (see rpc.dispatcher.RpcDispatcher), pass it the version, method, and args and let it dispatch as appropriate. If not, use the old behavior of magically calling the specified method on the proxy we have here. """ ctxt.update_store() try: rval = self.proxy.dispatch(ctxt, version, method, namespace, **args) # Check if the result was a generator if inspect.isgenerator(rval): for x in rval: ctxt.reply(x, None, connection_pool=self.connection_pool) else: ctxt.reply(rval, None, connection_pool=self.connection_pool) # This final None tells multicall that it is done. ctxt.reply(ending=True, connection_pool=self.connection_pool) except rpc_common.ClientException as e: LOG.debug(_('Expected exception during message handling (%s)') % e._exc_info[1]) ctxt.reply(None, e._exc_info, connection_pool=self.connection_pool, log_failure=False) except Exception: # sys.exc_info() is deleted by LOG.exception(). exc_info = sys.exc_info() LOG.error(_('Exception during message handling'), exc_info=exc_info) ctxt.reply(None, exc_info, connection_pool=self.connection_pool)
def __call__(self, message_data): """Consumer callback to call a method on a proxy object. Parses the message for validity and fires off a thread to call the proxy object method. Message data should be a dictionary with two keys: method: string representing the method to call args: dictionary of arg: value Example: {'method': 'echo', 'args': {'value': 42}} """ # It is important to clear the context here, because at this point # the previous context is stored in local.store.context if hasattr(local.store, 'context'): del local.store.context rpc_common._safe_log(LOG.debug, _('received %s'), message_data) self.msg_id_cache.check_duplicate_message(message_data) ctxt = unpack_context(self.conf, message_data) method = message_data.get('method') args = message_data.get('args', {}) version = message_data.get('version') namespace = message_data.get('namespace') if not method: LOG.warn(_('no method for message: %s') % message_data) ctxt.reply(_('No method for message: %s') % message_data, connection_pool=self.connection_pool) return self.pool.spawn_n(self._process_data, ctxt, version, method, namespace, args)
def _setup_subscription(self, ext, *args, **kwds): """Connect to message bus to get notifications Configure the RPC connection to listen for messages on the right exchanges and topics so we receive all of the notifications. Use a connection pool so that multiple notification agent instances can run in parallel to share load and without competing with each other for incoming messages. """ handler = ext.obj ack_on_error = cfg.CONF.notification.ack_on_event_error LOG.debug(_('Event types from %(name)s: %(type)s' ' (ack_on_error=%(error)s)') % {'name': ext.name, 'type': ', '.join(handler.event_types), 'error': ack_on_error}) for exchange_topic in handler.get_exchange_topics(cfg.CONF): for topic in exchange_topic.topics: try: self.conn.join_consumer_pool( callback=self.process_notification, pool_name=topic, topic=topic, exchange_name=exchange_topic.exchange, ack_on_error=ack_on_error) except Exception: LOG.exception(_('Could not join consumer pool' ' %(topic)s/%(exchange)s') % {'topic': topic, 'exchange': exchange_topic.exchange})
def _handle_action(self, action, alarm, state, reason): try: action = network_utils.urlsplit(action) except Exception: LOG.error( _("Unable to parse action %(action)s for alarm %(alarm)s"), locals()) return try: notifier = self.notifiers[action.scheme].obj except KeyError: scheme = action.scheme LOG.error( _("Action %(scheme)s for alarm %(alarm)s is unknown, " "cannot notify"), locals()) return try: LOG.debug("Notifying alarm %s with action %s", alarm, action) notifier.notify(action, alarm, state, reason) except Exception: LOG.exception(_("Unable to notify alarm %s"), alarm) return
def __init__(self, parsed_url): options = urlparse.parse_qs(parsed_url.query) # the values of the option is a list of url params values # only take care of the latest one if the option # is provided more than once self.per_meter_topic = bool(int( options.get('per_meter_topic', [0])[-1])) self.target = options.get('target', ['record_metering_data'])[0] self.policy = options.get('policy', ['default'])[-1] self.max_queue_length = int(options.get( 'max_queue_length', [1024])[-1]) self.local_queue = [] if self.policy in ['queue', 'drop']: LOG.info(_('Publishing policy set to %s, \ override backend retry config to 1') % self.policy) override_backend_retry_config(1) elif self.policy == 'default': LOG.info(_('Publishing policy set to %s') % self.policy) else: LOG.warn(_('Publishing policy is unknown (%s) force to default') % self.policy) self.policy = 'default'
def consume(self, sock): #TODO(ewindisch): use zero-copy (i.e. references, not copying) data = sock.recv() LOG.debug(_("CONSUMER RECEIVED DATA: %s"), data) if sock in self.mapping: LOG.debug(_("ROUTER RELAY-OUT %(data)s") % { 'data': data}) self.mapping[sock].send(data) return proxy = self.proxies[sock] if data[2] == 'cast': # Legacy protocol packenv = data[3] ctx, msg = _deserialize(packenv) request = rpc_common.deserialize_msg(msg) ctx = RpcContext.unmarshal(ctx) elif data[2] == 'impl_zmq_v2': packenv = data[4:] msg = unflatten_envelope(packenv) request = rpc_common.deserialize_msg(msg) # Unmarshal only after verifying the message. ctx = RpcContext.unmarshal(data[3]) else: LOG.error(_("ZMQ Envelope version unsupported or unknown.")) return self.pool.spawn_n(self.process, proxy, ctx, request)
def _connect(self, params): """Connect to rabbit. Re-establish any queues that may have been declared before if we are reconnecting. Exceptions should be handled by the caller. """ if self.connection: LOG.info(_("Reconnecting to AMQP server on " "%(hostname)s:%(port)d") % params) try: self.connection.release() except self.connection_errors: pass # Setting this in case the next statement fails, though # it shouldn't be doing any network operations, yet. self.connection = None self.connection = kombu.connection.BrokerConnection(**params) self.connection_errors = self.connection.connection_errors if self.memory_transport: # Kludge to speed up tests. self.connection.transport.polling_interval = 0.0 self.consumer_num = itertools.count(1) self.connection.connect() self.channel = self.connection.channel() # work around 'memory' transport bug in 1.1.3 if self.memory_transport: self.channel._new_queue('ae.undeliver') for consumer in self.consumers: consumer.reconnect(self.channel) LOG.info(_('Connected to AMQP server on %(hostname)s:%(port)d') % params)
def _poll_lease(self, lease, done): try: state = self.invoke_api(vim_util, 'get_object_property', self.vim, lease, 'state') if state == 'ready': # done LOG.debug(_("Lease is ready.")) done.send() return elif state == 'initializing': LOG.debug(_("Lease initializing...")) return elif state == 'error': error_msg = self.invoke_api(vim_util, 'get_object_property', self.vim, lease, 'error') LOG.exception(error_msg) excep = error_util.VimFaultException([], error_msg) done.send_exception(excep) else: # unknown state - complain error_msg = _("Error: unknown lease state %s.") % state raise error_util.VimFaultException([], error_msg) except Exception as excep: LOG.exception(excep) done.send_exception(excep)
def handle_sample(self, context, s): """Handle a sample, converting if necessary.""" LOG.debug(_('handling sample %s'), (s,)) if (self.source.get('unit', s.unit) == s.unit): s = self._convert(s) LOG.debug(_('converted to: %s'), (s,)) return s
def get_samples(self, manager, cache, resources): self._inspection_duration = self._record_poll_time() for instance in resources: LOG.debug(_('Checking memory usage for instance %s'), instance.id) try: memory_info = manager.inspector.inspect_memory_usage( instance, self._inspection_duration) LOG.debug(_("MEMORY USAGE: %(instance)s %(usage)f"), ({'instance': instance.__dict__, 'usage': memory_info.usage})) yield util.make_sample_from_instance( instance, name='memory.usage', type=sample.TYPE_GAUGE, unit='MB', volume=memory_info.usage, ) except virt_inspector.InstanceNotFoundException as err: # Instance was deleted while getting samples. Ignore it. LOG.debug(_('Exception while getting samples %s'), err) except NotImplementedError: # Selected inspector does not implement this pollster. LOG.debug(_('Obtaining Memory Usage is not implemented for %s' ), manager.inspector.__class__.__name__) except Exception as err: LOG.error(_('Could not get Memory Usage for %(id)s: %(e)s'), ( {'id': instance.id, 'e': err}))
def consume_in_thread(self): """Runs the ZmqProxy service.""" ipc_dir = CONF.rpc_zmq_ipc_dir consume_in = "tcp://%s:%s" % \ (CONF.rpc_zmq_bind_address, CONF.rpc_zmq_port) consumption_proxy = InternalContext(None) try: os.makedirs(ipc_dir) except os.error: if not os.path.isdir(ipc_dir): with excutils.save_and_reraise_exception(): LOG.error(_("Required IPC directory does not exist at" " %s") % (ipc_dir, )) try: self.register(consumption_proxy, consume_in, zmq.PULL, out_bind=True) except zmq.ZMQError: if os.access(ipc_dir, os.X_OK): with excutils.save_and_reraise_exception(): LOG.error(_("Permission denied to IPC directory at" " %s") % (ipc_dir, )) with excutils.save_and_reraise_exception(): LOG.error(_("Could not create ZeroMQ receiver daemon. " "Socket may already be in use.")) super(ZmqProxy, self).consume_in_thread()
def create_session(self): """Establish session with the server.""" # Login and setup the session with the server for making # API calls session_manager = self.vim.service_content.sessionManager session = self.vim.Login(session_manager, userName=self._server_username, password=self._server_password) # Terminate the earlier session, if possible (For the sake of # preserving sessions as there is a limit to the number of # sessions we can have) if self._session_id: try: self.vim.TerminateSession(session_manager, sessionId=[self._session_id]) except Exception as excep: # This exception is something we can live with. It is # just an extra caution on our side. The session may # have been cleared. We could have made a call to # SessionIsActive, but that is an overhead because we # anyway would have to call TerminateSession. LOG.exception(_("Error while terminating session: %s.") % excep) self._session_id = session.key LOG.info(_("Successfully established connection to the server."))
def publisher(waiter): LOG.info(_("Creating proxy for topic: %s"), topic) try: # The topic is received over the network, # don't trust this input. if self.badchars.search(topic) is not None: emsg = _("Topic contained dangerous characters.") LOG.warn(emsg) raise RPCException(emsg) out_sock = ZmqSocket("ipc://%s/zmq_topic_%s" % (ipc_dir, topic), sock_type, bind=True) except RPCException: waiter.send_exception(*sys.exc_info()) return self.topic_proxy[topic] = eventlet.queue.LightQueue( CONF.rpc_zmq_topic_backlog) self.sockets.append(out_sock) # It takes some time for a pub socket to open, # before we can have any faith in doing a send() to it. if sock_type == zmq.PUB: eventlet.sleep(.5) waiter.send(True) while(True): data = self.topic_proxy[topic].get() out_sock.send(data, copy=False)
def get_samples(self, manager, cache, resources): for instance in resources: LOG.debug(_('Checking CPU util for instance %s'), instance.id) try: cpu_info = manager.inspector.inspect_cpu_util(instance) LOG.debug(_("CPU UTIL: %(instance)s %(util)d"), ({'instance': instance.__dict__, 'util': cpu_info.util})) yield util.make_sample_from_instance( instance, name='cpu_util', type=sample.TYPE_GAUGE, unit='%', volume=cpu_info.util, ) except virt_inspector.InstanceNotFoundException as err: # Instance was deleted while getting samples. Ignore it. LOG.debug(_('Exception while getting samples %s'), err) except NotImplementedError: # Selected inspector does not implement this pollster. LOG.debug(_('Obtaining CPU Util is not implemented for %s' ), manager.inspector.__class__.__name__) except Exception as err: LOG.error(_('Could not get CPU Util for %(id)s: %(e)s'), ( {'id': instance.id, 'e': err}))
def _multi_send(method, context, topic, msg, timeout=None, envelope=False, _msg_id=None): """Wraps the sending of messages. Dispatches to the matchmaker and sends message to all relevant hosts. """ conf = CONF LOG.debug(_("%(msg)s") % {'msg': ' '.join(map(pformat, (topic, msg)))}) queues = _get_matchmaker().queues(topic) LOG.debug(_("Sending message(s) to: %s"), queues) # Don't stack if we have no matchmaker results if not queues: LOG.warn(_("No matchmaker results. Not casting.")) # While not strictly a timeout, callers know how to handle # this exception and a timeout isn't too big a lie. raise rpc_common.Timeout(_("No match from matchmaker.")) # This supports brokerless fanout (addresses > 1) for queue in queues: (_topic, ip_addr) = queue _addr = "tcp://%s:%s" % (ip_addr, conf.rpc_zmq_port) if method.__name__ == '_cast': eventlet.spawn_n(method, _addr, context, _topic, msg, timeout, envelope, _msg_id) return return method(_addr, context, _topic, msg, timeout, envelope)
def __init__(self, addr, zmq_type, bind=True, subscribe=None): self.sock = _get_ctxt().socket(zmq_type) self.addr = addr self.type = zmq_type self.subscriptions = [] # Support failures on sending/receiving on wrong socket type. self.can_recv = zmq_type in (zmq.PULL, zmq.SUB) self.can_send = zmq_type in (zmq.PUSH, zmq.PUB) self.can_sub = zmq_type in (zmq.SUB, ) # Support list, str, & None for subscribe arg (cast to list) do_sub = { list: subscribe, str: [subscribe], type(None): [] }[type(subscribe)] for f in do_sub: self.subscribe(f) str_data = {'addr': addr, 'type': self.socket_s(), 'subscribe': subscribe, 'bind': bind} LOG.debug(_("Connecting to %(addr)s with %(type)s"), str_data) LOG.debug(_("-> Subscribed to %(subscribe)s"), str_data) LOG.debug(_("-> bind: %(bind)s"), str_data) try: if bind: self.sock.bind(addr) else: self.sock.connect(addr) except Exception: raise RPCException(_("Could not open socket."))
def register(self, proxy, in_addr, zmq_type_in, out_addr=None, zmq_type_out=None, in_bind=True, out_bind=True, subscribe=None): LOG.info(_("Registering reactor")) if zmq_type_in not in (zmq.PULL, zmq.SUB): raise RPCException("Bad input socktype") # Items push in. inq = ZmqSocket(in_addr, zmq_type_in, bind=in_bind, subscribe=subscribe) self.proxies[inq] = proxy self.sockets.append(inq) LOG.info(_("In reactor registered")) if not out_addr: return if zmq_type_out not in (zmq.PUSH, zmq.PUB): raise RPCException("Bad output socktype") # Items push out. outq = ZmqSocket(out_addr, zmq_type_out, bind=out_bind) self.mapping[inq] = outq self.mapping[outq] = inq self.sockets.append(outq) LOG.info(_("Out reactor registered"))
def report_presence(self): """Report the presence of the current partition.""" LOG.debug(_('%s reporting presence') % self.this) try: self.coordination_rpc.presence(self.this.uuid, self.this.priority) except Exception: LOG.exception(_('presence reporting failed'))
def multicall(conf, context, topic, msg, timeout, connection_pool): """Make a call that returns multiple times.""" # TODO(pekowski): Remove all these comments in Havana. # For amqp_rpc_single_reply_queue = False, # Can't use 'with' for multicall, as it returns an iterator # that will continue to use the connection. When it's done, # connection.close() will get called which will put it back into # the pool # For amqp_rpc_single_reply_queue = True, # The 'with' statement is mandatory for closing the connection LOG.debug(_('Making synchronous call on %s ...'), topic) msg_id = uuid.uuid4().hex msg.update({'_msg_id': msg_id}) LOG.debug(_('MSG_ID is %s') % (msg_id)) _add_unique_id(msg) pack_context(msg, context) # TODO(pekowski): Remove this flag and the code under the if clause # in Havana. # (p-draigbrady): This clause is disabled to avoid qpid exchange leaks if False and not conf.amqp_rpc_single_reply_queue: conn = ConnectionContext(conf, connection_pool) wait_msg = MulticallWaiter(conf, conn, timeout) conn.declare_direct_consumer(msg_id, wait_msg) conn.topic_send(topic, rpc_common.serialize_msg(msg), timeout) else: with _reply_proxy_create_sem: if not connection_pool.reply_proxy: connection_pool.reply_proxy = ReplyProxy(conf, connection_pool) msg.update({'_reply_q': connection_pool.reply_proxy.get_reply_q()}) wait_msg = MulticallProxyWaiter(conf, msg_id, timeout, connection_pool) with ConnectionContext(conf, connection_pool) as conn: conn.topic_send(topic, rpc_common.serialize_msg(msg), timeout) return wait_msg
def __init__(self, alarm_id, type, enabled, name, description, timestamp, user_id, project_id, state, state_timestamp, ok_actions, alarm_actions, insufficient_data_actions, repeat_actions, rule, time_constraints): if not isinstance(timestamp, datetime.datetime): raise TypeError(_("timestamp should be datetime object")) if not isinstance(state_timestamp, datetime.datetime): raise TypeError(_("state_timestamp should be datetime object")) base.Model.__init__( self, alarm_id=alarm_id, type=type, enabled=enabled, name=name, description=description, timestamp=timestamp, user_id=user_id, project_id=project_id, state=state, state_timestamp=state_timestamp, ok_actions=ok_actions, alarm_actions=alarm_actions, insufficient_data_actions=insufficient_data_actions, repeat_actions=repeat_actions, rule=rule, time_constraints=time_constraints)
def record_metering_data(self, data): # We may have receive only one counter on the wire if not isinstance(data, list): data = [data] for meter in data: LOG.debug(_( 'metering data %(counter_name)s ' 'for %(resource_id)s @ %(timestamp)s: %(counter_volume)s') % ({'counter_name': meter['counter_name'], 'resource_id': meter['resource_id'], 'timestamp': meter.get('timestamp', 'NO TIMESTAMP'), 'counter_volume': meter['counter_volume']})) if publisher_utils.verify_signature( meter, self.conf.publisher.metering_secret): try: # Convert the timestamp to a datetime instance. # Storage engines are responsible for converting # that value to something they can store. if meter.get('timestamp'): ts = timeutils.parse_isotime(meter['timestamp']) meter['timestamp'] = timeutils.normalize_time(ts) self.storage_conn.record_metering_data(meter) except Exception as err: LOG.exception(_('Failed to record metering data: %s'), err) else: LOG.warning(_( 'message signature invalid, discarding message: %r'), meter)
def _callback_handler(self, message, callback): """Call callback with deserialized message. Messages that are processed without exception are ack'ed. If the message processing generates an exception, it will be ack'ed if ack_on_error=True. Otherwise it will be .reject()'ed. Rejection is better than waiting for the message to timeout. Rejected messages are immediately requeued. """ ack_msg = False try: msg = rpc_common.deserialize_msg(message.payload) callback(msg) ack_msg = True except Exception: if self.ack_on_error: ack_msg = True LOG.exception(_("Failed to process message" " ... skipping it.")) else: LOG.exception(_("Failed to process message" " ... will requeue.")) finally: if ack_msg: message.ack() else: message.reject()
def create_consumer(self, topic, proxy, fanout=False): # Register with matchmaker. _get_matchmaker().register(topic, CONF.rpc_zmq_host) # Subscription scenarios if fanout: sock_type = zmq.SUB subscribe = ('', fanout)[type(fanout) == str] topic = 'fanout~' + topic.split('.', 1)[0] else: sock_type = zmq.PULL subscribe = None topic = '.'.join((topic.split('.', 1)[0], CONF.rpc_zmq_host)) if topic in self.topics: LOG.info(_("Skipping topic registration. Already registered.")) return # Receive messages from (local) proxy inaddr = "ipc://%s/zmq_topic_%s" % \ (CONF.rpc_zmq_ipc_dir, topic) LOG.debug(_("Consumer is a zmq.%s"), ['PULL', 'SUB'][sock_type == zmq.SUB]) self.reactor.register(proxy, inaddr, sock_type, subscribe=subscribe, in_bind=False) self.topics.append(topic)
def _poll_task(self, task, done): """Poll the given task. If the task completes successfully then returns task info. In case of error sends back appropriate error. :param task: Managed object reference of the task :param event: Event that captures task status """ try: task_info = self.invoke_api(vim_util, 'get_object_property', self.vim, task, 'info') if task_info.state in ['queued', 'running']: # If task already completed on server, it will not return # the progress. if hasattr(task_info, 'progress'): LOG.debug(_("Task: %(task)s progress: %(prog)s.") % {'task': task, 'prog': task_info.progress}) return elif task_info.state == 'success': LOG.debug(_("Task %s status: success.") % task) done.send(task_info) else: error_msg = str(task_info.error.localizedMessage) LOG.exception(_("Task: %(task)s failed with error: %(err)s.") % {'task': task, 'err': error_msg}) done.send_exception(error_util.VimFaultException([], error_msg)) except Exception as excep: LOG.exception(_("Task: %(task)s failed with error: %(err)s.") % {'task': task, 'err': excep}) done.send_exception(excep)
def handle_sample(self, context, s): """Handle a sample, converting if necessary.""" LOG.debug(_('handling sample %s'), (s,)) key = s.name + s.resource_id prev = self.cache.get(key) timestamp = timeutils.parse_isotime(s.timestamp) self.cache[key] = (s.volume, timestamp) if prev: prev_volume = prev[0] prev_timestamp = prev[1] time_delta = timeutils.delta_seconds(prev_timestamp, timestamp) # we only allow negative deltas for noncumulative samples, whereas # for cumulative we assume that a reset has occurred in the interim # so that the current volume gives a lower bound on growth volume_delta = (s.volume - prev_volume if (prev_volume <= s.volume or s.type != sample.TYPE_CUMULATIVE) else s.volume) rate_of_change = ((1.0 * volume_delta / time_delta) if time_delta else 0.0) s = self._convert(s, rate_of_change) LOG.debug(_('converted to: %s'), (s,)) else: LOG.warn(_('dropping sample with no predecessor: %s'), (s,)) s = None return s
def to_bytes(text, default=0): """Converts a string into an integer of bytes. Looks at the last characters of the text to determine what conversion is needed to turn the input text into a byte number. Supports "B, K(B), M(B), G(B), and T(B)". (case insensitive) :param text: String input for bytes size conversion. :param default: Default return value when text is blank. """ match = BYTE_REGEX.search(text) if match: magnitude = int(match.group(1)) mult_key_org = match.group(2) if not mult_key_org: return magnitude elif text: msg = _('Invalid string format: %s') % text raise TypeError(msg) else: return default mult_key = mult_key_org.lower().replace('b', '', 1) multiplier = BYTE_MULTIPLIERS.get(mult_key) if multiplier is None: msg = _('Unknown byte multiplier: %s') % mult_key_org raise TypeError(msg) return magnitude * multiplier
def _parse_check(rule): """ Parse a single base check rule into an appropriate Check object. """ # Handle the special checks if rule == '!': return FalseCheck() elif rule == '@': return TrueCheck() try: kind, match = rule.split(':', 1) except Exception: LOG.exception(_("Failed to understand rule %(rule)s") % locals()) # If the rule is invalid, we'll fail closed return FalseCheck() # Find what implements the check if kind in _checks: return _checks[kind](kind, match) elif None in _checks: return _checks[None](kind, match) else: LOG.error(_("No handler for matches of kind %s") % kind) return FalseCheck()
def get_samples(self, manager, cache, resources): for instance in resources: LOG.info(_('checking instance %s'), instance.id) instance_name = util.instance_name(instance) try: cpu_info = manager.inspector.inspect_cpus(instance_name) LOG.info(_("CPUTIME USAGE: %(instance)s %(time)d") % ( {'instance': instance.__dict__, 'time': cpu_info.time})) cpu_num = {'cpu_number': cpu_info.number} yield util.make_sample_from_instance( instance, name='cpu', type=sample.TYPE_CUMULATIVE, unit='ns', volume=cpu_info.time, additional_metadata=cpu_num, ) except virt_inspector.InstanceNotFoundException as err: # Instance was deleted while getting samples. Ignore it. LOG.debug(_('Exception while getting samples %s'), err) except NotImplementedError: # Selected inspector does not implement this pollster. LOG.debug(_('Obtaining CPU time is not implemented for %s' ), manager.inspector.__class__.__name__) except Exception as err: LOG.error(_('could not get CPU time for %(id)s: %(e)s') % ( {'id': instance.id, 'e': err})) LOG.exception(err)
def _handle_action(self, action, alarm_id, previous, current, reason): try: action = network_utils.urlsplit(action) except Exception: LOG.error( _("Unable to parse action %(action)s for alarm %(alarm_id)s"), {'action': action, 'alarm_id': alarm_id}) return try: notifier = self.notifiers[action.scheme].obj except KeyError: scheme = action.scheme LOG.error( _("Action %(scheme)s for alarm %(alarm_id)s is unknown, " "cannot notify"), {'scheme': scheme, 'alarm_id': alarm_id}) return try: LOG.debug("Notifying alarm %s with action %s", alarm_id, action) notifier.notify(action, alarm_id, previous, current, reason) except Exception: LOG.exception(_("Unable to notify alarm %s"), alarm_id) return
def get_targets(self, conf): """Return a sequence of oslo.messaging.Target. Sequence is defining the exchange and topics to be connected for this plugin. :param conf: Configuration. """ # TODO(sileht): Backwards compatibility, remove in J+1 if hasattr(self, 'get_exchange_topics'): LOG.warn( _('get_exchange_topics API of NotificationPlugin is' 'deprecated, implements get_targets instead.')) targets = [] for exchange, topics in self.get_exchange_topics(conf): targets.extend([ oslo.messaging.Target(topic=topic, exchange=exchange) for topic in topics ]) return targets
def notify(self, alarm, previous, reason): actions = getattr(alarm, Alarm.ALARM_ACTIONS_MAP[alarm.state]) if not actions: LOG.debug( _('alarm %(alarm_id)s has no action configured ' 'for state transition from %(previous)s to ' 'state %(state)s, skipping the notification.') % { 'alarm_id': alarm.alarm_id, 'previous': previous, 'state': alarm.state }) return msg = self.make_msg('notify_alarm', data={ 'actions': actions, 'alarm_id': alarm.alarm_id, 'previous': previous, 'current': alarm.state, 'reason': unicode(reason) }) self.cast(context.get_admin_context(), msg)
def connect(self, url): connection_options = pymongo.uri_parser.parse_uri(url) del connection_options['database'] del connection_options['username'] del connection_options['password'] del connection_options['collection'] pool_key = tuple(connection_options) if pool_key in self._pool: client = self._pool.get(pool_key)() if client: return client splitted_url = netutils.urlsplit(url) log_data = { 'db': splitted_url.scheme, 'nodelist': connection_options['nodelist'] } LOG.info(_('Connecting to %(db)s on %(nodelist)s') % log_data) client = self._mongo_connect(url) self._pool[pool_key] = weakref.ref(client) return client
def _bound_duration(cls, alarm, constraints): """Bound the duration of the statistics query.""" now = timeutils.utcnow() # when exclusion of weak datapoints is enabled, we extend # the look-back period so as to allow a clearer sample count # trend to be established look_back = (cls.look_back if not alarm.rule.get('exclude_outliers') else alarm.rule['evaluation_periods']) window = (alarm.rule['period'] * (alarm.rule['evaluation_periods'] + look_back)) start = now - datetime.timedelta(seconds=window) LOG.debug( _('query stats from %(start)s to ' '%(now)s') % { 'start': start, 'now': now }) after = dict(field='timestamp', op='ge', value=start.isoformat()) before = dict(field='timestamp', op='le', value=now.isoformat()) constraints.extend([before, after]) return constraints
def _setup_transformers(self, cfg, transformer_manager): transformer_cfg = cfg['transformers'] or [] transformers = [] for transformer in transformer_cfg: parameter = transformer['parameters'] or {} try: ext = transformer_manager.get_ext(transformer['name']) except KeyError: raise PipelineException( "No transformer named %s loaded" % transformer['name'], cfg) transformers.append(ext.plugin(**parameter)) LOG.info( _("Pipeline %(pipeline)s: Setup transformer instance %(name)s " "with parameter %(param)s") % ({ 'pipeline': self, 'name': transformer['name'], 'param': parameter })) return transformers
def _get_sample(message, name): try: for metric in message['payload']['metrics']: if name == metric['name']: info = {} info['payload'] = metric info['event_type'] = message['event_type'] info['publisher_id'] = message['publisher_id'] info['resource_id'] = '%s_%s' % ( message['payload']['host'], message['payload']['nodename']) info['timestamp'] = str( timeutils.parse_strtime(metric['timestamp'])) return info except Exception as err: LOG.warning( _('An error occurred while building %(m)s ' 'sample: %(e)s') % { 'm': name, 'e': err })
def to_event(self, notification_body): event_type = notification_body['event_type'] message_id = notification_body['message_id'] edef = None for d in self.definitions: if d.match_type(event_type): edef = d break if edef is None: msg = (_('Dropping Notification %(type)s (uuid:%(msgid)s)') % dict(type=event_type, msgid=message_id)) if cfg.CONF.event.drop_unmatched_notifications: LOG.debug(msg) else: # If drop_unmatched_notifications is False, this should # never happen. (mdragon) LOG.error(msg) return None return edef.to_event(notification_body)
def __init__(self, url): """Hbase Connection Initialization.""" opts = self._parse_connection_url(url) if opts['host'] == '__test__': url = os.environ.get('CEILOMETER_TEST_HBASE_URL') if url: # Reparse URL, but from the env variable now opts = self._parse_connection_url(url) self.conn_pool = self._get_connection_pool(opts) else: # This is a in-memory usage for unit tests if Connection._memory_instance is None: LOG.debug( _('Creating a new in-memory HBase ' 'Connection object')) Connection._memory_instance = ( hbase_inmemory.MConnectionPool()) self.conn_pool = Connection._memory_instance else: self.conn_pool = self._get_connection_pool(opts)
def _sufficient_states(self, alarm, states): """Check for the sufficiency of the data for evaluation. Ensure that there is sufficient data for evaluation, transitioning to unknown otherwise. """ # note(sileht): alarm can be evaluated only with # stable state of other alarm alarms_missing_states = [ alarm_id for alarm_id, state in states if not state or state == evaluator.UNKNOWN ] sufficient = len(alarms_missing_states) == 0 if not sufficient and alarm.state != evaluator.UNKNOWN: reason = (_('Alarms %(alarm_ids)s' ' are in unknown state') % { 'alarm_ids': ",".join(alarms_missing_states) }) reason_data = self._reason_data(alarms_missing_states) self._refresh(alarm, evaluator.UNKNOWN, reason, reason_data) return sufficient
def inspect_disks(self, instance_name): domain = self._lookup_by_name(instance_name) state = domain.info()[0] if state == libvirt.VIR_DOMAIN_SHUTOFF: LOG.warn(_('Failed to inspect disks of %(instance_name)s, ' 'domain is in state of SHUTOFF'), {'instance_name': instance_name}) return tree = etree.fromstring(domain.XMLDesc(0)) for device in filter( bool, [target.get("dev") for target in tree.findall('devices/disk/target')]): disk = virt_inspector.Disk(device=device) block_stats = domain.blockStats(device) stats = virt_inspector.DiskStats(read_requests=block_stats[0], read_bytes=block_stats[1], write_requests=block_stats[2], write_bytes=block_stats[3], errors=block_stats[4]) yield (disk, stats)
def __init__(self, cfg, transformer_manager): self.cfg = cfg try: self.name = cfg['name'] try: self.interval = int(cfg['interval']) except ValueError: raise PipelineException("Invalid interval value", cfg) # Support 'counters' for backward compatibility self.meters = cfg.get('meters', cfg.get('counters')) # It's legal to have no transformer specified self.transformer_cfg = cfg['transformers'] or [] except KeyError as err: raise PipelineException( "Required field %s not specified" % err.args[0], cfg) if self.interval <= 0: raise PipelineException("Interval value should > 0", cfg) self._check_meters() if not cfg.get('publishers'): raise PipelineException("No publisher specified", cfg) self.publishers = [] for p in cfg['publishers']: if '://' not in p: # Support old format without URL p = p + "://" try: self.publishers.append(publisher.get_publisher(p)) except Exception: LOG.exception(_("Unable to load publisher %s"), p) self.transformers = self._setup_transformers(cfg, transformer_manager) self.resources = cfg.get('resources') or [] if not isinstance(self.resources, list): raise PipelineException("Resources should be a list", cfg)
def _get_socket(self, host, port, backlog): # TODO(dims): eventlet's green dns/socket module does not actually # support IPv6 in getaddrinfo(). We need to get around this in the # future or monitor upstream for a fix info = socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM)[0] family = info[0] bind_addr = info[-1] sock = None retry_until = time.time() + 30 while not sock and time.time() < retry_until: try: sock = eventlet.listen(bind_addr, backlog=backlog, family=family) if sslutils.is_enabled(): sock = sslutils.wrap(sock) except socket.error as err: if err.args[0] != errno.EADDRINUSE: raise eventlet.sleep(0.1) if not sock: raise RuntimeError( _("Could not bind to %(host)s:%(port)s " "after trying for 30 seconds") % { 'host': host, 'port': port }) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # sockets can hang around forever without keepalive sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) # This option isn't available in the OS X version of eventlet if hasattr(socket, 'TCP_KEEPIDLE'): sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, CONF.tcp_keepidle) return sock
def get_samples(self, manager, cache, resources): for endpoint in resources: for ip in self._iter_floating_ips(manager.keystone, cache, endpoint): LOG.info(_("FLOATING IP USAGE: %s") % ip.ip) # FIXME (flwang) Now Nova API /os-floating-ips can't provide # those attributes were used by Ceilometer, such as project # id, host. In this fix, those attributes usage will be # removed temporarily. And they will be back after fix the # Nova bug 1174802. yield sample.Sample(name='ip.floating', type=sample.TYPE_GAUGE, unit='ip', volume=1, user_id=None, project_id=None, resource_id=ip.id, timestamp=timeutils.utcnow().isoformat(), resource_metadata={ 'address': ip.ip, 'pool': ip.pool })
def initialize_if_enabled(): backdoor_locals = { 'exit': _dont_use_this, # So we don't exit the entire process 'quit': _dont_use_this, # So we don't exit the entire process 'fo': _find_objects, 'pgt': _print_greenthreads, 'pnt': _print_nativethreads, } if CONF.backdoor_port is None: return None start_port, end_port = _parse_port_range(str(CONF.backdoor_port)) # NOTE(johannes): The standard sys.displayhook will print the value of # the last expression and set it to __builtin__._, which overwrites # the __builtin__._ that gettext sets. Let's switch to using pprint # since it won't interact poorly with gettext, and it's easier to # read the output too. def displayhook(val): if val is not None: pprint.pprint(val) sys.displayhook = displayhook sock = _listen('localhost', start_port, end_port, eventlet.listen) # In the case of backdoor port being zero, a port number is assigned by # listen(). In any case, pull the port number out here. port = sock.getsockname()[1] LOG.info( _('Eventlet backdoor listening on %(port)s for process %(pid)d') % { 'port': port, 'pid': os.getpid() }) eventlet.spawn_n(eventlet.backdoor.backdoor_server, sock, locals=backdoor_locals) return port
def db_version(abs_path, init_version): """Show the current version of the repository. :param abs_path: Absolute path to migrate repository :param version: Initial database version """ repository = _find_migrate_repo(abs_path) try: return versioning_api.db_version(get_engine(), repository) except versioning_exceptions.DatabaseNotControlledError: meta = sqlalchemy.MetaData() engine = get_engine() meta.reflect(bind=engine) tables = meta.tables if len(tables) == 0: db_version_control(abs_path, init_version) return versioning_api.db_version(get_engine(), repository) else: # Some pre-Essex DB's may not be version controlled. # Require them to upgrade using Essex first. raise exception.DbMigrationError( message=_("Upgrade DB using Essex release first."))
def get_meters(self, user=None, project=None, resource=None, source=None, metaquery={}, pagination=None): """Return an iterable of models.Meter instances :param user: Optional ID for user that owns the resource. :param project: Optional ID for project that owns the resource. :param resource: Optional resource filter. :param source: Optional source filter. :param metaquery: Optional dict with metadata to match on. :param pagination: Optional pagination query. """ if pagination: raise NotImplementedError(_('Pagination not implemented')) q = {} if user is not None: q['user_id'] = user if project is not None: q['project_id'] = project if resource is not None: q['_id'] = resource if source is not None: q['source'] = source q.update(metaquery) for r in self.db.resource.find(q): for r_meter in r['meter']: yield models.Meter( name=r_meter['counter_name'], type=r_meter['counter_type'], # Return empty string if 'counter_unit' is not valid for # backward compatibility. unit=r_meter.get('counter_unit', ''), resource_id=r['_id'], project_id=r['project_id'], source=r['source'], user_id=r['user_id'], )
def _lookup_by_uuid(self, instance): instance_name = util.instance_name(instance) try: return self._get_connection().lookupByUUIDString(instance.id) except Exception as ex: if not libvirt or not isinstance(ex, libvirt.libvirtError): raise virt_inspector.InspectorException(six.text_type(ex)) error_code = ex.get_error_code() if (error_code == libvirt.VIR_ERR_SYSTEM_ERROR and ex.get_error_domain() in (libvirt.VIR_FROM_REMOTE, libvirt.VIR_FROM_RPC)): raise msg = _("Error from libvirt while looking up instance " "<name=%(name)s, id=%(id)s>: " "[Error Code %(error_code)s] " "%(ex)s") % { 'name': instance_name, 'id': instance.id, 'error_code': error_code, 'ex': ex } raise virt_inspector.InstanceNotFoundException(msg)
def _message_to_event(self, body): """Convert message to Ceilometer Event. NOTE: this is currently based on the Nova notification format. We will need to make this driver-based to support other formats. NOTE: the rpc layer currently rips out the notification delivery_info, which is critical to determining the source of the notification. This will have to get added back later. """ event_name = body['event_type'] when = self._extract_when(body) LOG.debug('Saving event "%s"', event_name) message_id = body.get('message_id') # TODO(sandy) - check we have not already saved this notification. # (possible on retries) Use message_id to spot dups. publisher = body.get('publisher_id') request_id = body.get('_context_request_id') tenant_id = body.get('_context_tenant') text = models.Trait.TEXT_TYPE all_traits = [models.Trait('message_id', text, message_id), models.Trait('service', text, publisher), models.Trait('request_id', text, request_id), models.Trait('tenant_id', text, tenant_id), ] # Only store non-None value traits ... traits = [trait for trait in all_traits if trait.value is not None] event = models.Event(event_name, when, traits) try: self.storage_conn.record_events([event, ]) except Exception as err: LOG.exception(_("Unable to store events: %s"), err) # By re-raising we avoid ack()'ing the message. raise
def get_alarms(self, name=None, user=None, project=None, enabled=True, alarm_id=None, pagination=None): """Yields a lists of alarms that match filters :param user: Optional ID for user that owns the resource. :param project: Optional ID for project that owns the resource. :param enabled: Optional boolean to list disable alarm. :param alarm_id: Optional alarm_id to return one alarm. :param metaquery: Optional dict with metadata to match on. :param resource: Optional resource filter. :param pagination: Optional pagination query. """ if pagination: raise NotImplementedError(_('Pagination not implemented')) q = {} if user is not None: q['user_id'] = user if project is not None: q['project_id'] = project if name is not None: q['name'] = name if enabled is not None: q['enabled'] = enabled if alarm_id is not None: q['alarm_id'] = alarm_id for alarm in self.db.alarm.find(q): a = {} a.update(alarm) del a['_id'] a['matching_metadata'] = \ self._decode_matching_metadata(a['matching_metadata']) yield models.Alarm(**a)
def get_samples(self, sample_filter, limit=None): """Return an iterable of models.Sample instances. :param sample_filter: Filter. :param limit: Maximum number of results to return. """ with self.conn_pool.connection() as conn: meter_table = conn.table(self.METER_TABLE) q, start, stop = make_sample_query_from_filter(sample_filter, require_meter=False) LOG.debug(_("Query Meter Table: %s") % q) gen = meter_table.scan(filter=q, row_start=start, row_stop=stop) for ignored, meter in gen: if limit is not None: if limit == 0: break else: limit -= 1 d_meter = deserialize_entry(meter)[0] d_meter['message']['recorded_at'] = d_meter['recorded_at'] yield models.Sample(**d_meter['message'])
def _find_facility_from_conf(): facility_names = logging.handlers.SysLogHandler.facility_names facility = getattr(logging.handlers.SysLogHandler, CONF.syslog_log_facility, None) if facility is None and CONF.syslog_log_facility in facility_names: facility = facility_names.get(CONF.syslog_log_facility) if facility is None: valid_facilities = facility_names.keys() consts = ['LOG_AUTH', 'LOG_AUTHPRIV', 'LOG_CRON', 'LOG_DAEMON', 'LOG_FTP', 'LOG_KERN', 'LOG_LPR', 'LOG_MAIL', 'LOG_NEWS', 'LOG_AUTH', 'LOG_SYSLOG', 'LOG_USER', 'LOG_UUCP', 'LOG_LOCAL0', 'LOG_LOCAL1', 'LOG_LOCAL2', 'LOG_LOCAL3', 'LOG_LOCAL4', 'LOG_LOCAL5', 'LOG_LOCAL6', 'LOG_LOCAL7'] valid_facilities.extend(consts) raise TypeError(_('syslog facility must be one of: %s') % ', '.join("'%s'" % fac for fac in valid_facilities)) return facility
def db_version(engine, abs_path, init_version): """Show the current version of the repository. :param engine: SQLAlchemy engine instance for a given database :param abs_path: Absolute path to migrate repository :param version: Initial database version """ repository = _find_migrate_repo(abs_path) try: return versioning_api.db_version(engine, repository) except versioning_exceptions.DatabaseNotControlledError: meta = sqlalchemy.MetaData() meta.reflect(bind=engine) tables = meta.tables if len(tables) == 0 or 'alembic_version' in tables: db_version_control(engine, abs_path, version=init_version) return versioning_api.db_version(engine, repository) else: raise exception.DbMigrationError(message=_( "The database is not under version control, but has " "tables. Please stamp the current version of the schema " "manually."))
def get_samples(self, manager, cache, resources=None): resources = resources or [] for member in resources: LOG.debug("Load Balancer Member : %s" % member) status = self.get_status_id(member['status']) if status == -1: LOG.warn(_("Unknown status %(stat)s received on member %(id)s," "skipping sample") % {'stat': member['status'], 'id': member['id']}) continue yield sample.Sample( name='network.services.lb.member', type=sample.TYPE_GAUGE, unit='member', volume=status, user_id=None, project_id=member['tenant_id'], resource_id=member['id'], timestamp=timeutils.utcnow().isoformat(), resource_metadata=self.extract_metadata(member) )
def notify(self, alarm, previous, reason, reason_data): actions = getattr(alarm, models.Alarm.ALARM_ACTIONS_MAP[alarm.state]) if not actions: LOG.debug( _('alarm %(alarm_id)s has no action configured ' 'for state transition from %(previous)s to ' 'state %(state)s, skipping the notification.') % { 'alarm_id': alarm.alarm_id, 'previous': previous, 'state': alarm.state }) return self.client.cast(context.get_admin_context(), 'notify_alarm', data={ 'actions': actions, 'alarm_id': alarm.alarm_id, 'previous': previous, 'current': alarm.state, 'reason': six.text_type(reason), 'reason_data': reason_data })
def process_notification(self, message): LOG.info(_('network notification %r') % message) counter_name = getattr(self, 'counter_name', self.resource_name) unit_value = getattr(self, 'unit', self.resource_name) resource = message['payload'].get(self.resource_name) if resource: # NOTE(liusheng): In %s.update.start notifications, the id is in # message['payload'] instead of resource itself. if message['event_type'].endswith('update.start'): resource['id'] = message['payload']['id'] resources = [resource] else: resources = message['payload'].get(self.resource_name + 's') resource_message = message.copy() for resource in resources: resource_message['payload'] = resource yield sample.Sample.from_notification( name=counter_name, type=sample.TYPE_GAUGE, unit=unit_value, volume=1, user_id=resource_message['_context_user_id'], project_id=resource_message['_context_tenant_id'], resource_id=resource['id'], message=resource_message) event_type_split = resource_message['event_type'].split('.') if len(event_type_split) > 2: yield sample.Sample.from_notification( name=counter_name + "." + event_type_split[1], type=sample.TYPE_DELTA, unit=unit_value, volume=1, user_id=resource_message['_context_user_id'], project_id=resource_message['_context_tenant_id'], resource_id=resource['id'], message=resource_message)
def record_metering_data(self, data): """Write the data to the backend storage system. :param data: a dictionary such as returned by ceilometer.meter.meter_message_from_counter """ session = self._engine_facade.get_session() with session.begin(): # Record the raw data for the sample. rmetadata = data['resource_metadata'] meter = self._create_meter(session, data['counter_name'], data['counter_type'], data['counter_unit']) sample = models.Sample(meter_id=meter.id) session.add(sample) sample.resource_id = data['resource_id'] sample.project_id = data['project_id'] sample.user_id = data['user_id'] sample.timestamp = data['timestamp'] sample.resource_metadata = rmetadata sample.volume = data['counter_volume'] sample.message_signature = data['message_signature'] sample.message_id = data['message_id'] sample.source_id = data['source'] session.flush() if rmetadata: if isinstance(rmetadata, dict): for key, v in utils.dict_to_keyval(rmetadata): try: _model = sql_utils.META_TYPE_MAP[type(v)] except KeyError: LOG.warn( _("Unknown metadata type. Key (%s) will " "not be queryable."), key) else: session.add( _model(id=sample.id, meta_key=key, value=v))
def serialize_remote_exception(failure_info, log_failure=True): """Prepares exception data to be sent over rpc. Failure_info should be a sys.exc_info() tuple. """ tb = traceback.format_exception(*failure_info) failure = failure_info[1] if log_failure: LOG.error(_("Returning exception %s to caller"), six.text_type(failure)) LOG.error(tb) kwargs = {} if hasattr(failure, 'kwargs'): kwargs = failure.kwargs # NOTE(matiu): With cells, it's possible to re-raise remote, remote # exceptions. Lets turn it back into the original exception type. cls_name = str(failure.__class__.__name__) mod_name = str(failure.__class__.__module__) if (cls_name.endswith(_REMOTE_POSTFIX) and mod_name.endswith(_REMOTE_POSTFIX)): cls_name = cls_name[:-len(_REMOTE_POSTFIX)] mod_name = mod_name[:-len(_REMOTE_POSTFIX)] data = { 'class': cls_name, 'module': mod_name, 'message': six.text_type(failure), 'tb': tb, 'args': failure.args, 'kwargs': kwargs } json_data = jsonutils.dumps(data) return json_data
def reply(self, ctx, proxy, msg_id=None, context=None, topic=None, msg=None): """Reply to a casted call.""" # NOTE(ewindisch): context kwarg exists for Grizzly compat. # this may be able to be removed earlier than # 'I' if ConsumerBase.process were refactored. if type(msg) is list: payload = msg[-1] else: payload = msg response = ConsumerBase.normalize_reply( self._get_response(ctx, proxy, topic, payload), ctx.replies) LOG.debug(_("Sending reply")) _multi_send(_cast, ctx, topic, { 'method': '-process_reply', 'args': { 'msg_id': msg_id, # Include for Folsom compat. 'response': response } }, _msg_id=msg_id)
def _parse_text_rule(rule): """ Translates a policy written in the policy language into a tree of Check objects. """ # Empty rule means always accept if not rule: return TrueCheck() # Parse the token stream state = ParseState() for tok, value in _parse_tokenize(rule): state.shift(tok, value) try: return state.result except ValueError: # Couldn't parse the rule LOG.exception(_("Failed to understand rule %(rule)r") % locals()) # Fail closed return FalseCheck()
def notify_alarm(self, context, data): """Notify that alarm has been triggered. :param context: Request context. :param data: (dict): - actions, the URL of the action to run; this is mapped to extensions automatically - alarm_id, the ID of the alarm that has been triggered - previous, the previous state of the alarm - current, the new state the alarm has transitioned to - reason, the reason the alarm changed its state - reason_data, a dict representation of the reason """ actions = data.get('actions') if not actions: LOG.error(_("Unable to notify for an alarm with no action")) return for action in actions: self._handle_action(action, data.get('alarm_id'), data.get('previous'), data.get('current'), data.get('reason'), data.get('reason_data'))