def check_rule(alarm): if (not pecan.request.cfg.api.enable_combination_alarms and alarm.type == 'combination'): raise base.ClientSideError("Unavailable alarm type") rule = '%s_rule' % alarm.type if getattr(alarm, rule) in (wtypes.Unset, None): error = _("%(rule)s must be set for %(type)s" " type alarm") % { "rule": rule, "type": alarm.type } raise base.ClientSideError(error) rule_set = None for ext in ALARMS_RULES: name = "%s_rule" % ext.name if getattr(alarm, name): if rule_set is None: rule_set = name else: error = _("%(rule1)s and %(rule2)s cannot be set at the " "same time") % { 'rule1': rule_set, 'rule2': name } raise base.ClientSideError(error)
def post(self, data): """Create a new alarm. :param data: an alarm within the request body. """ rbac.enforce('create_alarm', pecan.request.headers, pecan.request.enforcer) conn = pecan.request.alarm_storage_conn now = timeutils.utcnow() data.alarm_id = str(uuid.uuid4()) user_limit, project_limit = rbac.get_limited_to( pecan.request.headers, pecan.request.enforcer) def _set_ownership(aspect, owner_limitation, header): attr = '%s_id' % aspect requested_owner = getattr(data, attr) explicit_owner = requested_owner != wtypes.Unset caller = pecan.request.headers.get(header) if (owner_limitation and explicit_owner and requested_owner != caller): raise base.ProjectNotAuthorized(requested_owner, aspect) actual_owner = (owner_limitation or requested_owner if explicit_owner else caller) setattr(data, attr, actual_owner) _set_ownership('user', user_limit, 'X-User-Id') _set_ownership('project', project_limit, 'X-Project-Id') # Check if there's room for one more alarm if is_over_quota(conn, data.project_id, data.user_id): raise OverQuota(data) data.timestamp = now data.state_timestamp = now ALARMS_RULES[data.type].plugin.create_hook(data) change = data.as_dict(models.Alarm) data.update_actions() # make sure alarms are unique by name per project. alarms = list(conn.get_alarms(name=data.name, project=data.project_id)) if alarms: raise base.ClientSideError(_("Alarm with name='%s' exists") % data.name, status_code=409) try: alarm_in = models.Alarm(**change) except Exception: LOG.exception(_("Error while posting alarm: %s") % change) raise base.ClientSideError(_("Alarm incorrect")) alarm = conn.create_alarm(alarm_in) self._record_creation(conn, change, alarm.alarm_id, now) v2_utils.set_resp_location_hdr("/v2/alarms/" + alarm.alarm_id) return Alarm.from_db_model(alarm)
def valid_composite_rule(rules): if isinstance(rules, dict) and len(rules) == 1: and_or_key = list(rules)[0] if and_or_key not in ('and', 'or'): raise base.ClientSideError( _('Threshold rules should be combined with "and" or "or"')) if isinstance(rules[and_or_key], list): for sub_rule in rules[and_or_key]: CompositeRule.valid_composite_rule(sub_rule) else: raise InvalidCompositeRule(rules) elif isinstance(rules, dict): rule_type = rules.pop('type', None) if not rule_type: raise base.ClientSideError(_('type must be set in every rule')) if rule_type not in CompositeRule.threshold_plugins: plugins = sorted(CompositeRule.threshold_plugins.names()) err = _('Unsupported sub-rule type :%(rule)s in composite ' 'rule, should be one of: %(plugins)s') % { 'rule': rule_type, 'plugins': plugins } raise base.ClientSideError(err) plugin = CompositeRule.threshold_plugins[rule_type].plugin wjson.fromjson(plugin, rules) rule_dict = plugin(**rules).as_dict() rules.update(rule_dict) rules.update(type=rule_type) else: raise InvalidCompositeRule(rules)
def put(self, data): """Modify this alarm. :param data: an alarm within the request body. """ rbac.enforce('change_alarm', pecan.request.headers, pecan.request.enforcer) # Ensure alarm exists alarm_in = self._alarm() now = timeutils.utcnow() data.alarm_id = self._id user, project = rbac.get_limited_to(pecan.request.headers, pecan.request.enforcer) if user: data.user_id = user elif data.user_id == wtypes.Unset: data.user_id = alarm_in.user_id if project: data.project_id = project elif data.project_id == wtypes.Unset: data.project_id = alarm_in.project_id data.timestamp = now if alarm_in.state != data.state: data.state_timestamp = now else: data.state_timestamp = alarm_in.state_timestamp # make sure alarms are unique by name per project. if alarm_in.name != data.name: alarms = list( self.conn.get_alarms(name=data.name, project=data.project_id)) if alarms: raise base.ClientSideError(_("Alarm with name=%s exists") % data.name, status_code=409) ALARMS_RULES[data.type].plugin.update_hook(data) old_data = Alarm.from_db_model(alarm_in) old_alarm = old_data.as_dict(models.Alarm) data.update_actions(old_data) updated_alarm = data.as_dict(models.Alarm) try: alarm_in = models.Alarm(**updated_alarm) except Exception: LOG.exception(_("Error while putting alarm: %s") % updated_alarm) raise base.ClientSideError(_("Alarm incorrect")) alarm = self.conn.update_alarm(alarm_in) change = dict( (k, v) for k, v in updated_alarm.items() if v != old_alarm[k] and k not in ['timestamp', 'state_timestamp']) self._record_change(change, now, on_behalf_of=alarm.project_id) return Alarm.from_db_model(alarm)
def _check_ceilometer_sample_api(cls): # Check it only once if cls.ceilometer_sample_api_is_supported is None: auth_config = pecan.request.cfg.service_credentials client = ceiloclient.get_client( version=2, session=keystone_client.get_session(pecan.request.cfg), # ceiloclient adapter options region_name=auth_config.region_name, interface=auth_config.interface, ) try: client.statistics.list( meter_name="idontthinkthatexistsbutwhatever") except Exception as e: if isinstance(e, ceiloexc.HTTPException): if e.code == 410: cls.ceilometer_sample_api_is_supported = False elif e.code < 500: cls.ceilometer_sample_api_is_supported = True else: raise else: raise else: # I don't think this meter can exist but how known cls.ceilometer_sample_api_is_supported = True if cls.ceilometer_sample_api_is_supported is False: raise base.ClientSideError( "This telemetry installation is not configured to support" "alarm of type 'threshold")
def validate_alarm(cls, alarm): alarm_rule = getattr(alarm, "%s_rule" % alarm.type) aggregation_method = alarm_rule.aggregation_method if aggregation_method not in cls._get_aggregation_methods(): raise base.ClientSideError( 'aggregation_method should be in %s not %s' % ( cls._get_aggregation_methods(), aggregation_method))
def update_hook(alarm): # should check if there is any circle in the dependency, but for # efficiency reason, here only check alarm cannot depend on itself if alarm.alarm_id in alarm.combination_rule.alarm_ids: raise base.ClientSideError( _('Cannot specify alarm %s itself in combination rule') % alarm.alarm_id)
def validate(rule): rule.alarm_ids = sorted(set(rule.alarm_ids), key=rule.alarm_ids.index) if len(rule.alarm_ids) <= 1: raise base.ClientSideError(_('Alarm combination rule should ' 'contain at least two different ' 'alarm ids.')) return rule
def validate(tc): if tc.timezone: try: pytz.timezone(tc.timezone) except Exception: raise base.ClientSideError( _("Timezone %s is not valid") % tc.timezone) return tc
def validate_alarm(cls, alarm): super(AlarmEventRule, cls).validate_alarm(alarm) for i in alarm.event_rule.query: i._get_value_as_type() try: _q_validator({"field": i.field, "op": i.op, "value": i.type}) except voluptuous.MultipleInvalid as e: raise base.ClientSideError( _("Query value or traits invalid: %s") % str(e))
def _convert_to_datetime(isotime): try: date_time = timeutils.parse_isotime(isotime) date_time = date_time.replace(tzinfo=None) return date_time except ValueError: LOG.exception("String %s is not a valid isotime", isotime) msg = _('Failed to parse the timestamp value %s') % isotime raise base.ClientSideError(msg)
def validate(value): try: json.dumps(value) except TypeError: raise base.ClientSideError( _('%s is not JSON serializable') % value) else: CompositeRule.valid_composite_rule(value) return value
def validate_alarm(cls, alarm): super(AggregationMetricByResourcesLookupRule, cls).validate_alarm(alarm) rule = alarm.gnocchi_aggregation_by_resources_threshold_rule # check the query string is a valid json try: query = json.loads(rule.query) except ValueError: raise wsme.exc.InvalidInput('rule/query', rule.query) conf = pecan.request.cfg # Scope the alarm to the project id if needed auth_project = v2_utils.get_auth_project(alarm.project_id) if auth_project: perms_filter = {"=": {"created_by_project_id": auth_project}} external_project_owner = cls.get_external_project_owner() if external_project_owner: perms_filter = {"or": [ perms_filter, {"and": [ {"=": {"created_by_project_id": external_project_owner}}, {"=": {"project_id": auth_project}}]} ]} query = {"and": [perms_filter, query]} rule.query = json.dumps(query) gnocchi_client = client.Client( '1', keystone_client.get_session(conf), adapter_options={ 'interface': conf.service_credentials.interface, 'region_name': conf.service_credentials.region_name}) try: gnocchi_client.metric.aggregation( metrics=rule.metric, query=query, aggregation=rule.aggregation_method, needed_overlap=0, start="-1 day", stop="now", resource_type=rule.resource_type) except exceptions.ClientException as e: if e.code == 404: # NOTE(sileht): We are fine here, we just want to ensure the # 'query' payload is valid for Gnocchi If the metric # doesn't exists yet, it doesn't matter return raise base.ClientSideError(e.message, status_code=e.code) except Exception as e: raise GnocchiUnavailable(e)
def check_alarm_actions(alarm): max_actions = pecan.request.cfg.api.alarm_max_actions for state in state_kind: actions_name = state.replace(" ", "_") + '_actions' actions = getattr(alarm, actions_name) if not actions: continue action_set = set(actions) if len(actions) != len(action_set): LOG.info( 'duplicate actions are found: %s, ' 'remove duplicate ones', actions) actions = list(action_set) setattr(alarm, actions_name, actions) if 0 < max_actions < len(actions): error = _('%(name)s count exceeds maximum value ' '%(maximum)d') % { "name": actions_name, "maximum": max_actions } raise base.ClientSideError(error) limited = rbac.get_limited_to_project(pecan.request.headers, pecan.request.enforcer) for action in actions: try: url = netutils.urlsplit(action) except Exception: error = _("Unable to parse action %s") % action raise base.ClientSideError(error) if url.scheme not in ACTIONS_SCHEMA: error = _("Unsupported action %s") % action raise base.ClientSideError(error) if limited and url.scheme in ('log', 'test'): error = _('You are not authorized to create ' 'action: %s') % action raise base.ClientSideError(error, status_code=401)
def _get_aggregation_methods(): conf = pecan.request.cfg gnocchi_client = client.Client( '1', keystone_client.get_session(conf), interface=conf.service_credentials.interface, region_name=conf.service_credentials.region_name) try: return gnocchi_client.capabilities.list().get( 'aggregation_methods', []) except exceptions.ClientException as e: raise base.ClientSideError(e.message, status_code=e.code) except Exception as e: raise GnocchiUnavailable(e)
def put(self, data): """Modify this alarm. :param data: an alarm within the request body. """ # Ensure alarm exists alarm_in = self._enforce_rbac('change_alarm') now = timeutils.utcnow() data.alarm_id = self._id user, project = rbac.get_limited_to(pecan.request.headers, pecan.request.enforcer) if user: data.user_id = user elif data.user_id == wtypes.Unset: data.user_id = alarm_in.user_id if project: data.project_id = project elif data.project_id == wtypes.Unset: data.project_id = alarm_in.project_id data.timestamp = now if alarm_in.state != data.state: data.state_timestamp = now data.state_reason = ALARM_REASON_MANUAL else: data.state_timestamp = alarm_in.state_timestamp data.state_reason = alarm_in.state_reason ALARMS_RULES[data.type].plugin.update_hook(data) old_data = Alarm.from_db_model(alarm_in) old_alarm = old_data.as_dict(models.Alarm) data.update_actions(old_data) updated_alarm = data.as_dict(models.Alarm) try: alarm_in = models.Alarm(**updated_alarm) except Exception: LOG.exception("Error while putting alarm: %s", updated_alarm) raise base.ClientSideError(_("Alarm incorrect")) alarm = pecan.request.storage.update_alarm(alarm_in) change = dict( (k, v) for k, v in updated_alarm.items() if v != old_alarm[k] and k not in ['timestamp', 'state_timestamp']) self._record_change(change, now, on_behalf_of=alarm.project_id) return Alarm.from_db_model(alarm)
def validate(alarm): Alarm.check_rule(alarm) Alarm.check_alarm_actions(alarm) ALARMS_RULES[alarm.type].plugin.validate_alarm(alarm) if alarm.time_constraints: tc_names = [tc.name for tc in alarm.time_constraints] if len(tc_names) > len(set(tc_names)): error = _("Time constraint names must be " "unique for a given alarm.") raise base.ClientSideError(error) return alarm
def check_rule(alarm): rule = '%s_rule' % alarm.type if getattr(alarm, rule) in (wtypes.Unset, None): error = _("%(rule)s must be set for %(type)s" " type alarm") % { "rule": rule, "type": alarm.type } raise base.ClientSideError(error) rule_set = None for ext in ALARMS_RULES: name = "%s_rule" % ext.name if getattr(alarm, name): if rule_set is None: rule_set = name else: error = _("%(rule1)s and %(rule2)s cannot be set at the " "same time") % { 'rule1': rule_set, 'rule2': name } raise base.ClientSideError(error)
def validate(self, visibility_field): """Validates the query content and does the necessary conversions.""" if self.original_query.filter is wtypes.Unset: self.filter_expr = None else: try: self.filter_expr = json.loads(self.original_query.filter) self._validate_filter(self.filter_expr) except (ValueError, jsonschema.exceptions.ValidationError) as e: raise base.ClientSideError( _("Filter expression not valid: %s") % str(e)) self._replace_isotime_with_datetime(self.filter_expr) self._convert_operator_to_lower_case(self.filter_expr) self._normalize_field_names_for_db_model(self.filter_expr) self._force_visibility(visibility_field) if self.original_query.orderby is wtypes.Unset: self.orderby = None else: try: self.orderby = json.loads(self.original_query.orderby) self._validate_orderby(self.orderby) except (ValueError, jsonschema.exceptions.ValidationError) as e: raise base.ClientSideError( _("Order-by expression not valid: %s") % e) self._convert_orderby_to_lower_case(self.orderby) self._normalize_field_names_in_orderby(self.orderby) if self.original_query.limit is wtypes.Unset: self.limit = None else: self.limit = self.original_query.limit if self.limit is not None and self.limit <= 0: msg = _('Limit should be positive') raise base.ClientSideError(msg)
def update_actions(self, old_alarm=None): trustor_user_id = pecan.request.headers.get('X-User-Id') trustor_project_id = pecan.request.headers.get('X-Project-Id') roles = pecan.request.headers.get('X-Roles', '') if roles: roles = roles.split(',') else: roles = [] auth_plugin = pecan.request.environ.get('keystone.token_auth') if old_alarm: prev_trust_ids = set(old_alarm._get_existing_trust_ids()) else: prev_trust_ids = set() trust_id = prev_trust_ids.pop() if prev_trust_ids else None trust_id_used = False for actions in (self.ok_actions, self.alarm_actions, self.insufficient_data_actions): if actions is not None: for index, action in enumerate(actions[:]): url = netutils.urlsplit(action) if self._is_trust_url(url): if '@' in url.netloc: errmsg = _("trust URL cannot contain a trust ID.") raise base.ClientSideError(errmsg) if trust_id is None: # We have a trust action without a trust ID, # create it trust_id = keystone_client.create_trust_id( pecan.request.cfg, trustor_user_id, trustor_project_id, roles, auth_plugin) if trust_id_used: pw = '' else: pw = ':delete' trust_id_used = True netloc = '%s%s@%s' % (trust_id, pw, url.netloc) url = urlparse.SplitResult(url.scheme, netloc, url.path, url.query, url.fragment) actions[index] = url.geturl() if trust_id is not None and not trust_id_used: prev_trust_ids.add(trust_id) for old_trust_id in prev_trust_ids: keystone_client.delete_trust_id(pecan.request.cfg, old_trust_id, auth_plugin)
def validate_alarm(cls, alarm): super(MetricOfResourceRule, cls).validate_alarm(alarm) conf = pecan.request.cfg gnocchi_client = client.Client( '1', keystone_client.get_session(conf), interface=conf.service_credentials.interface, region_name=conf.service_credentials.region_name) rule = alarm.gnocchi_resources_threshold_rule try: gnocchi_client.resource.get(rule.resource_type, rule.resource_id) except exceptions.ClientException as e: raise base.ClientSideError(e.message, status_code=e.code) except Exception as e: raise GnocchiUnavailable(e)
def validate_alarm(cls, alarm): super(AggregationMetricByResourcesLookupRule, cls).validate_alarm(alarm) rule = alarm.gnocchi_aggregation_by_resources_threshold_rule # check the query string is a valid json try: query = jsonutils.loads(rule.query) except ValueError: raise wsme.exc.InvalidInput('rule/query', rule.query) # Scope the alarm to the project id if needed auth_project = v2_utils.get_auth_project(alarm.project_id) if auth_project: query = { "and": [{ "=": { "created_by_project_id": auth_project } }, query] } rule.query = jsonutils.dumps(query) conf = pecan.request.cfg gnocchi_client = client.Client( '1', keystone_client.get_session(conf), interface=conf.service_credentials.interface, region_name=conf.service_credentials.region_name, endpoint_override=conf.gnocchi_url) try: gnocchi_client.metric.aggregation( metrics=rule.metric, query=query, aggregation=rule.aggregation_method, needed_overlap=0, resource_type=rule.resource_type) except exceptions.ClientException as e: raise base.ClientSideError(e.message, status_code=e.code) except Exception as e: raise GnocchiUnavailable(e)
def validate(alarm): if alarm.type == 'threshold': warnings.simplefilter("always") debtcollector.deprecate( "Ceilometer's API is deprecated as of Ocata. Therefore, " " threshold rule alarms are no longer supported.", version="5.0.0") Alarm.check_rule(alarm) Alarm.check_alarm_actions(alarm) ALARMS_RULES[alarm.type].plugin.validate_alarm(alarm) if alarm.time_constraints: tc_names = [tc.name for tc in alarm.time_constraints] if len(tc_names) > len(set(tc_names)): error = _("Time constraint names must be " "unique for a given alarm.") raise base.ClientSideError(error) return alarm
def validate_alarm(cls, alarm): super(MetricOfResourceRule, cls).validate_alarm(alarm) rule = alarm.gnocchi_resources_threshold_rule ks_client = keystone_client.get_client(pecan.request.cfg) gnocchi_url = pecan.request.cfg.gnocchi_url headers = {'Content-Type': "application/json", 'X-Auth-Token': ks_client.auth_token} try: r = requests.get("%s/v1/resource/%s/%s" % ( gnocchi_url, rule.resource_type, rule.resource_id), headers=headers) except requests.ConnectionError as e: raise GnocchiUnavailable(e) if r.status_code == 404: raise base.EntityNotFound('gnocchi resource', rule.resource_id) elif r.status_code // 200 != 1: raise base.ClientSideError(r.content, status_code=r.status_code)
def put_state(self, state): """Set the state of this alarm. :param state: an alarm state within the request body. """ alarm = self._enforce_rbac('change_alarm_state') # note(sileht): body are not validated by wsme # Workaround for https://bugs.launchpad.net/wsme/+bug/1227229 if state not in state_kind: raise base.ClientSideError(_("state invalid")) now = timeutils.utcnow() alarm.state = state alarm.state_timestamp = now alarm = pecan.request.storage.update_alarm(alarm) change = {'state': alarm.state} self._record_change(change, now, on_behalf_of=alarm.project_id, type=models.AlarmChange.STATE_TRANSITION) return alarm.state
def validate_alarm(cls, alarm): super(AggregationMetricByResourcesLookupRule, cls).validate_alarm(alarm) rule = alarm.gnocchi_aggregation_by_resources_threshold_rule # check the query string is a valid json try: query = jsonutils.loads(rule.query) except ValueError: raise wsme.exc.InvalidInput('rule/query', rule.query) # Scope the alarm to the project id if needed auth_project = v2_utils.get_auth_project(alarm.project_id) if auth_project: rule.query = jsonutils.dumps({ "and": [{"=": {"created_by_project_id": auth_project}}, query]}) # Delegate the query validation to gnocchi ks_client = keystone_client.get_client(pecan.request.cfg) request = { 'url': "%s/v1/aggregation/resource/%s/metric/%s" % ( pecan.request.cfg.gnocchi_url, rule.resource_type, rule.metric), 'headers': {'Content-Type': "application/json", 'X-Auth-Token': ks_client.auth_token}, 'params': {'aggregation': rule.aggregation_method}, 'data': rule.query, } try: r = requests.post(**request) except requests.ConnectionError as e: raise GnocchiUnavailable(e) if r.status_code // 200 != 1: raise base.ClientSideError(r.content, status_code=r.status_code)