deb-ceilometer/ceilometer/storage/models.py
Sandy Walsh 5f35319dfa Reject duplicate events
When ack_on_error=False, there is a possibility that we
could receieve the same message more than once. Reject those events.

Change-Id: I3814a4222298d2fbc56a25e6e4540d01066ee42f
2013-09-04 12:45:26 -03:00

377 lines
14 KiB
Python

# -*- encoding: utf-8 -*-
#
# Copyright © 2013 New Dream Network, LLC (DreamHost)
#
# Author: Doug Hellmann <doug.hellmann@dreamhost.com>
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Model classes for use in the storage API.
"""
class Model(object):
"""Base class for storage API models.
"""
def __init__(self, **kwds):
self.fields = list(kwds)
for k, v in kwds.iteritems():
setattr(self, k, v)
def as_dict(self):
d = {}
for f in self.fields:
v = getattr(self, f)
if isinstance(v, Model):
v = v.as_dict()
elif isinstance(v, list) and v and isinstance(v[0], Model):
v = [sub.as_dict() for sub in v]
d[f] = v
return d
def __eq__(self, other):
return self.as_dict() == other.as_dict()
class Event(Model):
"""A raw event from the source system. Events have Traits.
Metrics will be derived from one or more Events.
"""
DUPLICATE = 1
UNKNOWN_PROBLEM = 2
def __init__(self, message_id, event_name, generated, traits):
"""Create a new event.
:param message_id: Unique ID for the message this event
stemmed from. This is different than
the Event ID, which comes from the
underlying storage system.
:param event_name: Name of the event.
:param generated: UTC time for when the event occured.
:param traits: list of Traits on this Event.
"""
Model.__init__(self, message_id=message_id, event_name=event_name,
generated=generated, traits=traits)
def append_trait(self, trait_model):
self.traits.append(trait_model)
def __repr__(self):
trait_list = []
if self.traits:
trait_list = [str(trait) for trait in self.traits]
return "<Event: %s, %s, %s, %s>" % \
(self.message_id, self.event_name, self.generated,
" ".join(trait_list))
class Trait(Model):
"""A Trait is a key/value pair of data on an Event. The value is variant
record of basic data types (int, date, float, etc).
"""
TEXT_TYPE = 1
INT_TYPE = 2
FLOAT_TYPE = 3
DATETIME_TYPE = 4
def __init__(self, name, dtype, value):
Model.__init__(self, name=name, dtype=dtype, value=value)
def __repr__(self):
return "<Trait: %s %d %s>" % (self.name, self.dtype, self.value)
class Resource(Model):
"""Something for which sample data has been collected.
"""
def __init__(self, resource_id, project_id,
first_sample_timestamp,
last_sample_timestamp,
source, user_id, metadata,
meter):
"""Create a new resource.
:param resource_id: UUID of the resource
:param project_id: UUID of project owning the resource
:param first_sample_timestamp: first sample timestamp captured
:param last_sample_timestamp: last sample timestamp captured
:param source: the identifier for the user/project id definition
:param user_id: UUID of user owning the resource
:param metadata: most current metadata for the resource (a dict)
:param meter: list of the meters reporting data for the resource,
"""
Model.__init__(self,
resource_id=resource_id,
first_sample_timestamp=first_sample_timestamp,
last_sample_timestamp=last_sample_timestamp,
project_id=project_id,
source=source,
user_id=user_id,
metadata=metadata,
meter=meter,
)
class ResourceMeter(Model):
"""The definitions of the meters for which data has been collected
for a resource.
See Resource.meter field.
"""
def __init__(self, counter_name, counter_type, counter_unit):
"""Create a new resource meter.
:param counter_name: the name of the counter updating the resource
:param counter_type: one of gauge, delta, cumulative
:param counter_unit: official units name for the sample data
"""
Model.__init__(self,
counter_name=counter_name,
counter_type=counter_type,
counter_unit=counter_unit,
)
class Meter(Model):
"""Definition of a meter for which sample data has been collected.
"""
def __init__(self, name, type, unit, resource_id, project_id, source,
user_id):
"""Create a new meter.
:param name: name of the meter
:param type: type of the meter (guage, counter)
:param unit: unit of the meter
:param resource_id: UUID of the resource
:param project_id: UUID of project owning the resource
:param source: the identifier for the user/project id definition
:param user_id: UUID of user owning the resource
"""
Model.__init__(self,
name=name,
type=type,
unit=unit,
resource_id=resource_id,
project_id=project_id,
source=source,
user_id=user_id,
)
class Sample(Model):
"""One collected data point.
"""
def __init__(self,
source,
counter_name, counter_type, counter_unit, counter_volume,
user_id, project_id, resource_id,
timestamp, resource_metadata,
message_id,
message_signature,
):
"""Create a new sample.
:param source: the identifier for the user/project id definition
:param counter_name: the name of the measurement being taken
:param counter_type: the type of the measurement
:param counter_unit: the units for the measurement
:param counter_volume: the measured value
:param user_id: the user that triggered the measurement
:param project_id: the project that owns the resource
:param resource_id: the thing on which the measurement was taken
:param timestamp: the time of the measurement
:param resource_metadata: extra details about the resource
:param message_id: a message identifier
:param message_signature: a hash created from the rest of the
message data
"""
Model.__init__(self,
source=source,
counter_name=counter_name,
counter_type=counter_type,
counter_unit=counter_unit,
counter_volume=counter_volume,
user_id=user_id,
project_id=project_id,
resource_id=resource_id,
timestamp=timestamp,
resource_metadata=resource_metadata,
message_id=message_id,
message_signature=message_signature)
class Statistics(Model):
"""Computed statistics based on a set of sample data.
"""
def __init__(self, unit,
min, max, avg, sum, count,
period, period_start, period_end,
duration, duration_start, duration_end,
groupby):
"""Create a new statistics object.
:param unit: The unit type of the data set
:param min: The smallest volume found
:param max: The largest volume found
:param avg: The average of all volumes found
:param sum: The total of all volumes found
:param count: The number of samples found
:param period: The length of the time range covered by these stats
:param period_start: The timestamp for the start of the period
:param period_end: The timestamp for the end of the period
:param duration: The total time for the matching samples
:param duration_start: The earliest time for the matching samples
:param duration_end: The latest time for the matching samples
:param groupby: The fields used to group the samples.
"""
Model.__init__(self, unit=unit,
min=min, max=max, avg=avg, sum=sum, count=count,
period=period, period_start=period_start,
period_end=period_end, duration=duration,
duration_start=duration_start,
duration_end=duration_end,
groupby=groupby)
class Alarm(Model):
ALARM_INSUFFICIENT_DATA = 'insufficient data'
ALARM_OK = 'ok'
ALARM_ALARM = 'alarm'
ALARM_ACTIONS_MAP = {
ALARM_INSUFFICIENT_DATA: 'insufficient_data_actions',
ALARM_OK: 'ok_actions',
ALARM_ALARM: 'alarm_actions',
}
"""
An alarm to monitor.
:param alarm_id: UUID of the alarm
:param name: The Alarm name
:param description: User friendly description of the alarm
:param enabled: Is the alarm enabled
:param state: Alarm state (alarm/nodata/ok)
:param meter_name: The counter that the alarm is based on
:param comparison_operator: How to compare the samples and the threshold
:param threshold: the value to compare to the samples
:param statistic: the function from Statistic (min/max/avg/count)
:param user_id: the owner/creator of the alarm
:param project_id: the project_id of the creator
:param evaluation_periods: the number of periods
:param period: the time period in seconds
:param timestamp: the timestamp when the alarm was last updated
:param state_timestamp: the timestamp of the last state change
:param ok_actions: the list of webhooks to call when entering the ok state
:param alarm_actions: the list of webhooks to call when entering the
alarm state
:param insufficient_data_actions: the list of webhooks to call when
entering the insufficient data state
:param matching_metadata: the key/values of metadata to match on.
:param repeat_actions: Is the actions should be triggered on each
alarm evaluation.
"""
def __init__(self, alarm_id, name, meter_name,
comparison_operator, threshold, statistic,
user_id, project_id,
evaluation_periods=1,
period=60,
enabled=True,
description='',
timestamp=None,
state=ALARM_INSUFFICIENT_DATA,
state_timestamp=None,
ok_actions=[],
alarm_actions=[],
insufficient_data_actions=[],
matching_metadata={},
repeat_actions=False
):
if not description:
# make a nice user friendly description by default
description = 'Alarm when %s is %s a %s of %s over %s seconds' % (
meter_name, comparison_operator,
statistic, threshold, period)
Model.__init__(
self,
alarm_id=alarm_id,
enabled=enabled,
name=name,
description=description,
timestamp=timestamp,
meter_name=meter_name,
user_id=user_id,
project_id=project_id,
comparison_operator=comparison_operator,
threshold=threshold,
statistic=statistic,
evaluation_periods=evaluation_periods,
period=period,
state=state,
state_timestamp=state_timestamp,
ok_actions=ok_actions,
alarm_actions=alarm_actions,
insufficient_data_actions=
insufficient_data_actions,
repeat_actions=repeat_actions,
matching_metadata=matching_metadata)
class AlarmChange(Model):
"""Record of an alarm change.
:param event_id: UUID of the change event
:param alarm_id: UUID of the alarm
:param type: The type of change
:param detail: JSON fragment describing change
:param user_id: the user ID of the initiating identity
:param project_id: the project ID of the initiating identity
:param on_behalf_of: the tenant on behalf of which the change
is being made
:param timestamp: the timestamp of the change
"""
CREATION = 'creation'
RULE_CHANGE = 'rule change'
STATE_TRANSITION = 'state transition'
DELETION = 'deletion'
def __init__(self,
event_id,
alarm_id,
type,
detail,
user_id,
project_id,
on_behalf_of,
timestamp=None
):
Model.__init__(
self,
event_id=event_id,
alarm_id=alarm_id,
type=type,
detail=detail,
user_id=user_id,
project_id=project_id,
on_behalf_of=on_behalf_of,
timestamp=timestamp)