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") % 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) self.limit = (None if self.original_query.limit is wtypes.Unset else self.original_query.limit) self.limit = v2_utils.enforce_limit(self.limit)
def _event_query_to_event_filter(q): evt_model_filter = { 'event_type': None, 'message_id': None, 'start_timestamp': None, 'end_timestamp': None } filters = _build_rbac_query_filters() traits_filter = filters['t_filter'] admin_proj = filters['admin_proj'] for i in q: if not i.op: i.op = 'eq' elif i.op not in base.operation_kind: error = (_('Operator %(operator)s is not supported. The supported' ' operators are: %(supported)s') % {'operator': i.op, 'supported': base.operation_kind}) raise base.ClientSideError(error) if i.field in evt_model_filter: if i.op != 'eq': error = (_('Operator %(operator)s is not supported. Only' ' equality operator is available for field' ' %(field)s') % {'operator': i.op, 'field': i.field}) raise base.ClientSideError(error) evt_model_filter[i.field] = i.value else: trait_type = i.type or 'string' traits_filter.append({"key": i.field, trait_type: i._get_value_as_type(), "op": i.op}) return storage.EventFilter(traits_filter=traits_filter, admin_proj=admin_proj, **evt_model_filter)
def post(self, data): """Create a new alarm. :param data: an alarm within the request body. """ rbac.enforce('create_alarm', pecan.request) 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) 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) data.update_actions() change = data.as_dict(alarm_models.Alarm) # 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 = alarm_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) return Alarm.from_db_model(alarm)
def put(self, data): """Modify this alarm. :param data: an alarm within the request body. """ rbac.enforce('change_alarm', pecan.request) # Ensure alarm exists alarm_in = self._alarm() now = timeutils.utcnow() data.alarm_id = self._id user, project = rbac.get_limited_to(pecan.request.headers) 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 alarm_in.severity = data.severity # 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_alarm = Alarm.from_db_model(alarm_in).as_dict(alarm_models.Alarm) updated_alarm = data.as_dict(alarm_models.Alarm) try: alarm_in = alarm_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 statistics(self, q=None, groupby=None, period=None, aggregate=None): """Computes the statistics of the samples in the time range given. :param q: Filter rules for the data to be returned. :param groupby: Fields for group by aggregation :param period: Returned result will be an array of statistics for a period long of that number of seconds. :param aggregate: The selectable aggregation functions to be applied. """ rbac.enforce('compute_statistics', pecan.request) q = q or [] groupby = groupby or [] aggregate = aggregate or [] if period and period < 0: raise base.ClientSideError(_("Period must be positive.")) kwargs = v2_utils.query_to_kwargs(q, storage.SampleFilter.__init__) kwargs['meter'] = self.meter_name f = storage.SampleFilter(**kwargs) g = _validate_groupby_fields(groupby) aggregate = utils.uniq(aggregate, ['func', 'param']) # Find the original timestamp in the query to use for clamping # the duration returned in the statistics. start = end = None for i in q: if i.field == 'timestamp' and i.op in ('lt', 'le'): end = timeutils.parse_isotime(i.value).replace(tzinfo=None) elif i.field == 'timestamp' and i.op in ('gt', 'ge'): start = timeutils.parse_isotime(i.value).replace(tzinfo=None) try: computed = pecan.request.storage_conn.get_meter_statistics( f, period, g, aggregate) LOG.debug(_('computed value coming from %r'), pecan.request.storage_conn) return [ Statistics(start_timestamp=start, end_timestamp=end, **c.as_dict()) for c in computed ] except OverflowError as e: params = dict(period=period, err=e) raise base.ClientSideError( _("Invalid period %(period)s: %(err)s") % params)
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 _event_query_to_event_filter(q): evt_model_filter = { 'event_type': None, 'message_id': None, 'start_timestamp': None, 'end_timestamp': None } traits_filter = [] for i in q: if not i.op: i.op = 'eq' elif i.op not in base.operation_kind: error = _("operator {} is incorrect").format(i.op) raise base.ClientSideError(error) if i.field in evt_model_filter: evt_model_filter[i.field] = i.value else: trait_type = i.type or 'string' traits_filter.append({ "key": i.field, trait_type: i._get_value_as_type(), "op": i.op }) return storage.EventFilter(traits_filter=traits_filter, **evt_model_filter)
def _event_query_to_event_filter(q): evt_model_filter = { 'event_type': None, 'message_id': None, 'start_timestamp': None, 'end_timestamp': None } traits_filter = [] for i in q: if not i.op: i.op = 'eq' elif i.op not in base.operation_kind: error = (_('operator %(operator)s is not supported. the supported' ' operators are: %(supported)s') % { 'operator': i.op, 'supported': base.operation_kind }) raise base.ClientSideError(error) if i.field in evt_model_filter: evt_model_filter[i.field] = i.value else: trait_type = i.type or 'string' traits_filter.append({ "key": i.field, trait_type: i._get_value_as_type(), "op": i.op }) return storage.EventFilter(traits_filter=traits_filter, **evt_model_filter)
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_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 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(tc): if tc.timezone: try: pytz.timezone(tc.timezone) except Exception: raise base.ClientSideError( _("Timezone %s is not valid") % tc.timezone) return tc
def check_alarm_actions(alarm): actions_schema = ceilometer_alarm.NOTIFIER_SCHEMAS for state in state_kind: actions_name = state.replace(" ", "_") + '_actions' actions = getattr(alarm, actions_name) if not actions: continue 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)
def validate(aggregate): valid_agg = (storage_base.Connection.CAPABILITIES.get( 'statistics', {}).get('aggregation', {}).get('selectable', {}).keys()) if aggregate.func not in valid_agg: msg = _('Invalid aggregation function: %s') % aggregate.func raise base.ClientSideError(msg) return aggregate
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 enforce_limit(limit): """Ensure limit is defined and is valid. if not, set a default.""" if limit is None: limit = pecan.request.cfg.api.default_api_return_limit LOG.info('No limit value provided, result set will be' ' limited to %(limit)d.', {'limit': limit}) if not limit or limit <= 0: raise base.ClientSideError(_("Limit must be positive")) return limit
def enforce_limit(limit): """Ensure limit is defined and is valid. if not, set a default.""" if limit is None: limit = cfg.CONF.api.default_api_return_limit LOG.info( _LI('No limit value provided, result set will be' 'limited to %(limit)d.'), {limit: limit}) if limit and limit < 0: raise base.ClientSideError(_("Limit must be positive")) return limit
def check_alarm_actions(alarm): actions_schema = ceilometer_alarm.NOTIFIER_SCHEMAS max_actions = cfg.CONF.alarm.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( _LI('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) 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_all(self, q=None, meter=None, groupby=None, period=None, aggregate=None): """Retrieve all statistics for all meters :param q: Filter rules for the statistics to be returned. """ rbac.enforce('compute_statistics', pecan.request) q = q or [] meter = meter or [] groupby = groupby or [] aggregate = aggregate or [] if period and period < 0: raise base.ClientSideError(_("Period must be positive.")) g = meters._validate_groupby_fields(groupby) # TO DO: break out the meter names and invoke multiple calls kwargs = v2_utils.query_to_kwargs(q, storage.SampleFilter.__init__) aggregate = utils.uniq(aggregate, ['func', 'param']) # Find the original timestamp in the query to use for clamping # the duration returned in the statistics. start = end = None for i in q: if i.field == 'timestamp' and i.op in ('lt', 'le'): end = timeutils.parse_isotime(i.value).replace(tzinfo=None) elif i.field == 'timestamp' and i.op in ('gt', 'ge'): start = timeutils.parse_isotime(i.value).replace(tzinfo=None) ret = [] kwargs['meter'] = meter f = storage.SampleFilter(**kwargs) try: computed = pecan.request.storage_conn.get_meter_statistics( f, period, g, aggregate) dbStats = [ ScopedStatistics(start_timestamp=start, end_timestamp=end, **c.as_dict()) for c in computed ] ret += dbStats except OverflowError: LOG.exception("Problem processing meters %s" % meter) return ret
def validate(alarm): Alarm.check_rule(alarm) Alarm.check_alarm_actions(alarm) ALARMS_RULES[alarm.type].plugin.validate_alarm(alarm) 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 get_all(self, q=None, limit=None): """Return all known samples, based on the data recorded so far. :param q: Filter rules for the samples to be returned. :param limit: Maximum number of samples to be returned. """ rbac.enforce('get_samples', pecan.request) q = q or [] if limit and limit < 0: raise base.ClientSideError(_("Limit must be positive")) kwargs = utils.query_to_kwargs(q, storage.SampleFilter.__init__) f = storage.SampleFilter(**kwargs) return map(Sample.from_db_model, pecan.request.storage_conn.get_samples(f, limit=limit))
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() request = { 'url': "%s/v1/aggregation/resource/%s/metric/%s" % (cfg.CONF.alarms.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)
def get_all(self, q=None, limit=None): """Return samples for the meter. :param q: Filter rules for the data to be returned. :param limit: Maximum number of samples to return. """ rbac.enforce('get_samples', pecan.request) q = q or [] if limit and limit < 0: raise base.ClientSideError(_("Limit must be positive")) kwargs = v2_utils.query_to_kwargs(q, storage.SampleFilter.__init__) kwargs['meter'] = self.meter_name f = storage.SampleFilter(**kwargs) return [ OldSample.from_db_model(e) for e in pecan.request.storage_conn.get_samples(f, limit=limit) ]
def put_state(self, state): """Set the state of this alarm. :param state: an alarm state within the request body. """ rbac.enforce('change_alarm_state', pecan.request) # 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 = self._alarm() alarm.state = state alarm.state_timestamp = now alarm = self.conn.update_alarm(alarm) change = {'state': alarm.state} self._record_change(change, now, on_behalf_of=alarm.project_id, type=alarm_models.AlarmChange.STATE_TRANSITION) return alarm.state
def validate_alarm(cls, alarm): super(MetricOfResourceRule, cls).validate_alarm(alarm) rule = alarm.gnocchi_resources_threshold_rule ks_client = keystone_client.get_client() gnocchi_url = cfg.CONF.alarms.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 post(self, direct='', samples=None): """Post a list of new Samples to Telemetry. :param direct: a flag indicates whether the samples will be posted directly to storage or not. :param samples: a list of samples within the request body. """ rbac.enforce('create_samples', pecan.request) direct = strutils.bool_from_string(direct) if not samples: msg = _('Samples should be included in request body') raise base.ClientSideError(msg) now = timeutils.utcnow() auth_project = rbac.get_limited_to_project(pecan.request.headers) def_source = pecan.request.cfg.sample_source def_project_id = pecan.request.headers.get('X-Project-Id') def_user_id = pecan.request.headers.get('X-User-Id') published_samples = [] for s in samples: if self.meter_name != s.counter_name: raise wsme.exc.InvalidInput('counter_name', s.counter_name, 'should be %s' % self.meter_name) if s.message_id: raise wsme.exc.InvalidInput('message_id', s.message_id, 'The message_id must not be set') if s.counter_type not in sample.TYPES: raise wsme.exc.InvalidInput( 'counter_type', s.counter_type, 'The counter type must be: ' + ', '.join(sample.TYPES)) s.user_id = (s.user_id or def_user_id) s.project_id = (s.project_id or def_project_id) s.source = '%s:%s' % (s.project_id, (s.source or def_source)) s.timestamp = (s.timestamp or now) if auth_project and auth_project != s.project_id: # non admin user trying to cross post to another project_id auth_msg = 'can not post samples to other projects' raise wsme.exc.InvalidInput('project_id', s.project_id, auth_msg) published_sample = sample.Sample( name=s.counter_name, type=s.counter_type, unit=s.counter_unit, volume=s.counter_volume, user_id=s.user_id, project_id=s.project_id, resource_id=s.resource_id, timestamp=s.timestamp.isoformat(), resource_metadata=utils.restore_nesting(s.resource_metadata, separator='.'), source=s.source) s.message_id = published_sample.id sample_dict = publisher_utils.meter_message_from_counter( published_sample, cfg.CONF.publisher.telemetry_secret) if direct: ts = timeutils.parse_isotime(sample_dict['timestamp']) sample_dict['timestamp'] = timeutils.normalize_time(ts) pecan.request.storage_conn.record_metering_data(sample_dict) else: published_samples.append(sample_dict) if not direct: pecan.request.notifier.sample( { 'user': def_user_id, 'tenant': def_project_id, 'is_admin': True }, 'telemetry.api', {'samples': published_samples}) return samples
def post(self, samples): """Post a list of new Samples to Telemetry. :param samples: a list of samples within the request body. """ rbac.enforce('create_samples', pecan.request) now = timeutils.utcnow() auth_project = rbac.get_limited_to_project(pecan.request.headers) def_source = pecan.request.cfg.sample_source def_project_id = pecan.request.headers.get('X-Project-Id') def_user_id = pecan.request.headers.get('X-User-Id') published_samples = [] for s in samples: for p in pecan.request.pipeline_manager.pipelines: if p.support_meter(s.counter_name): break else: message = _("The metric %s is not supported by metering " "pipeline configuration.") % s.counter_name raise base.ClientSideError(message, status_code=409) if self.meter_name != s.counter_name: raise wsme.exc.InvalidInput('counter_name', s.counter_name, 'should be %s' % self.meter_name) if s.message_id: raise wsme.exc.InvalidInput('message_id', s.message_id, 'The message_id must not be set') if s.counter_type not in sample.TYPES: raise wsme.exc.InvalidInput( 'counter_type', s.counter_type, 'The counter type must be: ' + ', '.join(sample.TYPES)) s.user_id = (s.user_id or def_user_id) s.project_id = (s.project_id or def_project_id) s.source = '%s:%s' % (s.project_id, (s.source or def_source)) s.timestamp = (s.timestamp or now) if auth_project and auth_project != s.project_id: # non admin user trying to cross post to another project_id auth_msg = 'can not post samples to other projects' raise wsme.exc.InvalidInput('project_id', s.project_id, auth_msg) published_sample = sample.Sample( name=s.counter_name, type=s.counter_type, unit=s.counter_unit, volume=s.counter_volume, user_id=s.user_id, project_id=s.project_id, resource_id=s.resource_id, timestamp=s.timestamp.isoformat(), resource_metadata=utils.restore_nesting(s.resource_metadata, separator='.'), source=s.source) published_samples.append(published_sample) s.message_id = published_sample.id with pecan.request.pipeline_manager.publisher( context.get_admin_context()) as publisher: publisher(published_samples) return samples