diff --git a/ceilometer/storage/impl_sqlalchemy.py b/ceilometer/storage/impl_sqlalchemy.py index 6e2b55978..2c39fe037 100644 --- a/ceilometer/storage/impl_sqlalchemy.py +++ b/ceilometer/storage/impl_sqlalchemy.py @@ -35,6 +35,7 @@ from ceilometer.storage import base from ceilometer.storage import models as api_models from ceilometer.storage.sqlalchemy import migration from ceilometer.storage.sqlalchemy.models import Alarm +from ceilometer.storage.sqlalchemy.models import AlarmChange from ceilometer.storage.sqlalchemy.models import Base from ceilometer.storage.sqlalchemy.models import Event from ceilometer.storage.sqlalchemy.models import Meter @@ -668,6 +669,17 @@ class Connection(base.Connection): session.query(Alarm).filter(Alarm.id == alarm_id).delete() session.flush() + @staticmethod + def _row_to_alarm_change_model(row): + return api_models.AlarmChange(event_id=row.event_id, + alarm_id=row.alarm_id, + type=row.type, + detail=row.detail, + user_id=row.user_id, + project_id=row.project_id, + on_behalf_of=row.on_behalf_of, + timestamp=row.timestamp) + def get_alarm_changes(self, alarm_id, on_behalf_of, user=None, project=None, type=None, start_timestamp=None, start_timestamp_op=None, @@ -695,12 +707,44 @@ class Connection(base.Connection): :param end_timestamp: Optional modified timestamp end range :param end_timestamp_op: Optional timestamp end range operation """ - raise NotImplementedError('Alarm history not implemented') + session = sqlalchemy_session.get_session() + query = session.query(AlarmChange) + query = query.filter(AlarmChange.alarm_id == alarm_id) + + if on_behalf_of is not None: + query = query.filter(AlarmChange.on_behalf_of == on_behalf_of) + if user is not None: + query = query.filter(AlarmChange.user_id == user) + if project is not None: + query = query.filter(AlarmChange.project_id == project) + if type is not None: + query = query.filter(AlarmChange.type == type) + if start_timestamp: + if start_timestamp_op == 'gt': + query = query.filter(AlarmChange.timestamp > start_timestamp) + else: + query = query.filter(AlarmChange.timestamp >= start_timestamp) + if end_timestamp: + if end_timestamp_op == 'le': + query = query.filter(AlarmChange.timestamp <= end_timestamp) + else: + query = query.filter(AlarmChange.timestamp < end_timestamp) + + query = query.order_by(desc(AlarmChange.timestamp)) + return (self._row_to_alarm_change_model(x) for x in query.all()) def record_alarm_change(self, alarm_change): """Record alarm change event. """ - raise NotImplementedError('Alarm history not implemented') + session = sqlalchemy_session.get_session() + with session.begin(): + session.merge(User(id=alarm_change['user_id'])) + session.merge(Project(id=alarm_change['project_id'])) + session.merge(Project(id=alarm_change['on_behalf_of'])) + alarm_change_row = AlarmChange(event_id=alarm_change['event_id']) + alarm_change_row.update(alarm_change) + session.add(alarm_change_row) + session.flush() @staticmethod def _get_unique(session, key): diff --git a/ceilometer/storage/sqlalchemy/migrate_repo/versions/015_add_alarm_history_table.py b/ceilometer/storage/sqlalchemy/migrate_repo/versions/015_add_alarm_history_table.py new file mode 100644 index 000000000..95338dc70 --- /dev/null +++ b/ceilometer/storage/sqlalchemy/migrate_repo/versions/015_add_alarm_history_table.py @@ -0,0 +1,72 @@ +# -*- encoding: utf-8 -*- +# +# Copyright © 2013 Red Hat, Inc. +# +# Author: Eoghan Glynn +# +# 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. + +from migrate import ForeignKeyConstraint +from sqlalchemy import MetaData, Table, Column, Index +from sqlalchemy import String, DateTime + +meta = MetaData() + + +def upgrade(migrate_engine): + meta.bind = migrate_engine + + project = Table('project', meta, autoload=True) + user = Table('user', meta, autoload=True) + + alarm_history = Table( + 'alarm_history', meta, + Column('event_id', String(255), primary_key=True, index=True), + Column('alarm_id', String(255)), + Column('on_behalf_of', String(255)), + Column('project_id', String(255)), + Column('user_id', String(255)), + Column('type', String(20)), + Column('detail', String(255)), + Column('timestamp', DateTime(timezone=False)), + mysql_engine='InnoDB') + + alarm_history.create() + + if migrate_engine.name in ['mysql', 'postgresql']: + indices = [Index('ix_alarm_history_alarm_id', + alarm_history.c.alarm_id), + Index('ix_alarm_history_on_behalf_of', + alarm_history.c.on_behalf_of), + Index('ix_alarm_history_project_id', + alarm_history.c.project_id), + Index('ix_alarm_history_on_user_id', + alarm_history.c.user_id)] + + for index in indices: + index.create(migrate_engine) + + fkeys = [ForeignKeyConstraint(columns=[alarm_history.c.on_behalf_of], + refcolumns=[project.c.id]), + ForeignKeyConstraint(columns=[alarm_history.c.project_id], + refcolumns=[project.c.id]), + ForeignKeyConstraint(columns=[alarm_history.c.user_id], + refcolumns=[user.c.id])] + for fkey in fkeys: + fkey.create(engine=migrate_engine) + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + alarm_history = Table('alarm_history', meta, autoload=True) + alarm_history.drop() diff --git a/ceilometer/storage/sqlalchemy/models.py b/ceilometer/storage/sqlalchemy/models.py index 55cbe734d..c43dc62df 100644 --- a/ceilometer/storage/sqlalchemy/models.py +++ b/ceilometer/storage/sqlalchemy/models.py @@ -204,6 +204,22 @@ class Alarm(Base): matching_metadata = Column(JSONEncodedDict) +class AlarmChange(Base): + """Define AlarmChange data.""" + __tablename__ = 'alarm_history' + __table_args__ = ( + Index('ix_alarm_history_alarm_id', 'alarm_id'), + ) + event_id = Column(String(255), primary_key=True) + alarm_id = Column(String(255)) + on_behalf_of = Column(String(255), ForeignKey('project.id')) + project_id = Column(String(255), ForeignKey('project.id')) + user_id = Column(String(255), ForeignKey('user.id')) + type = Column(String(20)) + detail = Column(String(255)) + timestamp = Column(DateTime, default=timeutils.utcnow) + + class UniqueName(Base): """Key names should only be stored once. """ diff --git a/tests/api/v2/test_alarm_scenarios.py b/tests/api/v2/test_alarm_scenarios.py index ddfccaa52..ea5196483 100644 --- a/tests/api/v2/test_alarm_scenarios.py +++ b/tests/api/v2/test_alarm_scenarios.py @@ -179,6 +179,7 @@ class TestAlarms(FunctionalTest, self.assertEqual(3, len(data)) self.delete('/alarms/%s' % data[0]['alarm_id'], + headers=self.auth_headers, status=200) alarms = list(self.conn.get_alarms()) self.assertEqual(2, len(alarms))