Skip to content

Commit 21490a8

Browse files
authored
Merge pull request #144 from tzumainn/event-history
Added event object
2 parents a5480d4 + dffbc5d commit 21490a8

File tree

15 files changed

+586
-6
lines changed

15 files changed

+586
-6
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
2+
# not use this file except in compliance with the License. You may obtain
3+
# a copy of the License at
4+
#
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10+
# License for the specific language governing permissions and limitations
11+
# under the License.
12+
13+
import datetime
14+
import pecan
15+
from pecan import rest
16+
import wsme
17+
from wsme import types as wtypes
18+
import wsmeext.pecan as wsme_pecan
19+
20+
from esi_leap.api.controllers import base
21+
from esi_leap.api.controllers import types
22+
from esi_leap.api.controllers.v1 import utils
23+
from esi_leap.common import exception
24+
from esi_leap.common import keystone
25+
import esi_leap.conf
26+
from esi_leap.objects import event as event_obj
27+
from esi_leap.resource_objects import get_resource_object
28+
29+
CONF = esi_leap.conf.CONF
30+
31+
32+
class Event(base.ESILEAPBase):
33+
34+
id = wsme.wsattr(int, readonly=True)
35+
event_type = wsme.wsattr(wtypes.text, readonly=True)
36+
event_time = wsme.wsattr(datetime.datetime, readonly=True)
37+
object_type = wsme.wsattr(wtypes.text, readonly=True)
38+
object_uuid = wsme.wsattr(wtypes.text, readonly=True)
39+
resource_type = wsme.wsattr(wtypes.text, readonly=True)
40+
resource_uuid = wsme.wsattr(wtypes.text, readonly=True)
41+
lessee_id = wsme.wsattr(wtypes.text, readonly=True)
42+
owner_id = wsme.wsattr(wtypes.text, readonly=True)
43+
44+
def __init__(self, **kwargs):
45+
46+
self.fields = event_obj.Event.fields
47+
for field in self.fields:
48+
setattr(self, field, kwargs.get(field, wtypes.Unset))
49+
50+
51+
class EventCollection(types.Collection):
52+
events = [Event]
53+
54+
def __init__(self, **kwargs):
55+
self._type = 'events'
56+
57+
58+
class EventsController(rest.RestController):
59+
60+
@wsme_pecan.wsexpose(EventCollection, int, wtypes.text,
61+
datetime.datetime, wtypes.text, wtypes.text,
62+
wtypes.text, wtypes.text)
63+
def get_all(self, last_event_id=None, lessee_or_owner_id=None,
64+
last_event_time=None, event_type=None,
65+
resource_type=None, resource_uuid=None):
66+
request = pecan.request.context
67+
cdict = request.to_policy_values()
68+
69+
try:
70+
utils.policy_authorize('esi_leap:offer:offer_admin', cdict, cdict)
71+
except exception.HTTPForbidden:
72+
lessee_or_owner_id = cdict['project_id']
73+
74+
if lessee_or_owner_id is not None:
75+
lessee_or_owner_id = keystone.get_project_uuid_from_ident(
76+
lessee_or_owner_id)
77+
78+
if resource_uuid is not None:
79+
if resource_type is None:
80+
resource_type = CONF.api.default_resource_type
81+
resource = get_resource_object(resource_type, resource_uuid)
82+
resource_uuid = resource.get_uuid()
83+
84+
filters = {
85+
'last_event_id': last_event_id,
86+
'last_event_time': last_event_time,
87+
'lessee_or_owner_id': lessee_or_owner_id,
88+
'event_type': event_type,
89+
'resource_type': resource_type,
90+
'resource_uuid': resource_uuid,
91+
}
92+
93+
# unpack iterator to tuple so we can use 'del'
94+
for k, v in tuple(filters.items()):
95+
if v is None:
96+
del filters[k]
97+
98+
events = event_obj.Event.get_all(filters, request)
99+
event_collection = EventCollection()
100+
event_collection.events = []
101+
for event in events:
102+
e = Event(id=event.id,
103+
event_type=event.event_type,
104+
event_time=event.event_time,
105+
object_type=event.object_type,
106+
object_uuid=event.object_uuid,
107+
resource_type=event.resource_type,
108+
resource_uuid=event.resource_uuid,
109+
lessee_id=event.lessee_id,
110+
owner_id=event.owner_id)
111+
event_collection.events.append(e)
112+
113+
return event_collection

esi_leap/api/controllers/v1/root.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import pecan
1515
from pecan import rest
1616

17+
from esi_leap.api.controllers.v1 import event
1718
from esi_leap.api.controllers.v1 import lease
1819
from esi_leap.api.controllers.v1 import node
1920
from esi_leap.api.controllers.v1 import offer
@@ -24,6 +25,7 @@ class Controller(rest.RestController):
2425
leases = lease.LeasesController()
2526
offers = offer.OffersController()
2627
nodes = node.NodesController()
28+
events = event.EventsController()
2729

2830
@pecan.expose(content_type='application/json')
2931
def index(self):

esi_leap/common/notification_utils.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ def _emit_notification(context, obj, action, level, status,
7070
"%(notification_method)s, payload method "
7171
"%(payload_method)s, error %(error)s"))
7272
payload = payload_method(obj, **extra_args)
73+
event_type = "esi_leap.%s.%s.%s" % (resource, action, status)
7374
notification_method(
7475
publisher=notification.NotificationPublisher(
7576
service='esi-leap-manager', host=CONF.host),
@@ -78,10 +79,10 @@ def _emit_notification(context, obj, action, level, status,
7879
level=level,
7980
payload=payload).emit(context)
8081
LOG.info("Emit esi_leap notification: host is %s "
81-
"event is esi_leap.%s.%s.%s ,"
82+
"event is %s ,"
8283
"level is %s ,"
8384
"notification method is %s",
84-
CONF.host, resource, action, status,
85+
CONF.host, event_type,
8586
level, notification_method)
8687
except (exception.NotificationSchemaObjectError,
8788
exception.NotificationSchemaKeyError,

esi_leap/db/api.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,13 @@ def resource_check_admin(resource_type, resource_uuid,
159159
return IMPL.resource_check_admin(
160160
resource_type, resource_uuid, start_time, end_time,
161161
default_admin_project_id, project_id)
162+
163+
164+
# Event
165+
@to_dict
166+
def event_get_all():
167+
return IMPL.event_get_all()
168+
169+
170+
def event_create(values):
171+
return IMPL.event_create(values)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
2+
# not use this file except in compliance with the License. You may obtain
3+
# a copy of the License at
4+
#
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10+
# License for the specific language governing permissions and limitations
11+
# under the License.
12+
13+
"""create events table
14+
15+
Revision ID: a1ea63fec697
16+
Revises:
17+
Create Date: 2023-06-26 14:22:34.822066
18+
19+
"""
20+
from alembic import op
21+
import sqlalchemy as sa
22+
23+
24+
# revision identifiers, used by Alembic.
25+
revision = 'a1ea63fec697'
26+
down_revision = None
27+
branch_labels = None
28+
depends_on = None
29+
30+
31+
def upgrade():
32+
op.create_table(
33+
'events',
34+
sa.Column('id', sa.Integer(), nullable=False),
35+
sa.Column('event_type', sa.String(length=255), nullable=False),
36+
sa.Column('event_time', sa.DateTime(), nullable=False),
37+
sa.Column('object_type', sa.String(length=255), nullable=True),
38+
sa.Column('object_uuid', sa.String(length=36), nullable=True),
39+
sa.Column('resource_type', sa.String(length=255), nullable=True),
40+
sa.Column('resource_uuid', sa.String(length=36), nullable=True),
41+
sa.Column('lessee_id', sa.String(length=36), nullable=True),
42+
sa.Column('owner_id', sa.String(length=36), nullable=True),
43+
sa.Column('created_at', sa.DateTime(), nullable=True),
44+
sa.Column('updated_at', sa.DateTime(), nullable=True),
45+
sa.PrimaryKeyConstraint('id'),
46+
)
47+
48+
op.create_index('event_type_idx', 'events', ['event_type'],
49+
unique=False)
50+
op.create_index('event_lessee_id_idx', 'events', ['lessee_id'],
51+
unique=False)
52+
op.create_index('event_owner_id_idx', 'events', ['owner_id'],
53+
unique=False)
54+
op.create_index('event_resource_idx', 'events',
55+
['resource_type', 'resource_uuid'],
56+
unique=False)
57+
58+
59+
def downgrade():
60+
pass

esi_leap/db/sqlalchemy/api.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,3 +446,37 @@ def resource_verify_availability(r_type, r_uuid, start, end):
446446
raise exception.ResourceTimeConflict(
447447
resource_uuid=r_uuid,
448448
resource_type=r_type)
449+
450+
451+
# Events
452+
453+
def event_get_all(filters):
454+
query = model_query(models.Event)
455+
456+
last_event_time = filters.pop('last_event_time', None)
457+
last_event_id = filters.pop('last_event_id', None)
458+
lessee_or_owner_id = filters.pop('lessee_or_owner_id', None)
459+
460+
query = query.filter_by(**filters)
461+
462+
if last_event_time:
463+
query = query.filter(
464+
last_event_time < models.Event.event_time)
465+
if last_event_id:
466+
query = query.filter(last_event_id < models.Event.id)
467+
if lessee_or_owner_id:
468+
query = query.filter(
469+
(lessee_or_owner_id == models.Event.lessee_id) |
470+
(lessee_or_owner_id == models.Event.owner_id))
471+
472+
return query
473+
474+
475+
def event_create(values):
476+
event_ref = models.Event()
477+
event_ref.update(values)
478+
479+
with _session_for_write() as session:
480+
session.add(event_ref)
481+
session.flush()
482+
return event_ref

esi_leap/db/sqlalchemy/models.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,26 @@ class Lease(Base):
112112
'Lease',
113113
backref=orm.backref('child_leases', remote_side=uuid),
114114
)
115+
116+
117+
class Event(Base):
118+
"""Represents an event."""
119+
120+
__tablename__ = 'events'
121+
__table_args__ = (
122+
Index('event_type_idx', 'event_type'),
123+
Index('event_lessee_id_idx', 'lessee_id'),
124+
Index('event_owner_id_idx', 'owner_id'),
125+
Index('event_resource_idx', 'resource_type', 'resource_uuid'),
126+
Index('event_time_idx', 'event_time'),
127+
)
128+
129+
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
130+
event_type = Column(String(36), nullable=False)
131+
event_time = Column(DateTime, nullable=False)
132+
object_type = Column(String(36), nullable=True)
133+
object_uuid = Column(String(36), nullable=True)
134+
resource_type = Column(String(36), nullable=True)
135+
resource_uuid = Column(String(36), nullable=True)
136+
lessee_id = Column(String(255), nullable=True)
137+
owner_id = Column(String(255), nullable=True)

esi_leap/objects/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
def register_all():
2+
__import__('esi_leap.objects.event')
23
__import__('esi_leap.objects.lease')
34
__import__('esi_leap.objects.offer')

esi_leap/objects/event.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
2+
# not use this file except in compliance with the License. You may obtain
3+
# a copy of the License at
4+
#
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10+
# License for the specific language governing permissions and limitations
11+
# under the License.
12+
13+
from esi_leap.db import api as dbapi
14+
from esi_leap.objects import base
15+
from esi_leap.objects import fields
16+
17+
from oslo_config import cfg
18+
from oslo_log import log as logging
19+
from oslo_versionedobjects import base as versioned_objects_base
20+
21+
CONF = cfg.CONF
22+
LOG = logging.getLogger(__name__)
23+
24+
25+
@versioned_objects_base.VersionedObjectRegistry.register
26+
class Event(base.ESILEAPObject):
27+
dbapi = dbapi.get_instance()
28+
29+
fields = {
30+
'id': fields.IntegerField(),
31+
'event_type': fields.StringField(),
32+
'event_time': fields.DateTimeField(),
33+
'object_type': fields.StringField(nullable=True),
34+
'object_uuid': fields.StringField(nullable=True),
35+
'resource_type': fields.StringField(nullable=True),
36+
'resource_uuid': fields.StringField(nullable=True),
37+
'lessee_id': fields.StringField(nullable=True),
38+
'owner_id': fields.StringField(nullable=True),
39+
}
40+
41+
@classmethod
42+
def get_all(cls, filters, context=None):
43+
db_events = cls.dbapi.event_get_all(filters)
44+
return cls._from_db_object_list(context, db_events)
45+
46+
def create(self, context=None):
47+
updates = self.obj_get_changes()
48+
49+
LOG.info('Creating event')
50+
db_event = self.dbapi.event_create(updates)
51+
self._from_db_object(context, self, db_event)

esi_leap/objects/lease.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,18 @@ def __init__(self, lease, node):
104104

105105
self.populate_schema(lease=lease, node=node)
106106

107+
def get_event_dict(self, event_type):
108+
event_dict = super().get_event_dict(event_type)
109+
event_dict.update({
110+
'object_type': 'lease',
111+
'object_uuid': self.uuid,
112+
'resource_type': self.resource_type,
113+
'resource_uuid': self.resource_uuid,
114+
'lessee_id': self.project_id,
115+
'owner_id': self.owner_id,
116+
})
117+
return event_dict
118+
107119

108120
CRUD_NOTIFY_OBJ = {
109121
'lease': (LeaseCRUDNotification, LeaseCRUDPayload),

0 commit comments

Comments
 (0)