Add container action object and sql db api
Add container action object and sql db api for container actions. Change-Id: I62cd021d4fc4b69432cc67b3506b9b24a9e7c779 Implements: blueprint container-actions-api
This commit is contained in:
@@ -13,6 +13,8 @@
|
|||||||
import copy
|
import copy
|
||||||
from eventlet.green import threading
|
from eventlet.green import threading
|
||||||
from oslo_context import context
|
from oslo_context import context
|
||||||
|
from oslo_utils import timeutils
|
||||||
|
import six
|
||||||
|
|
||||||
from zun.common import exception
|
from zun.common import exception
|
||||||
from zun.common import policy
|
from zun.common import policy
|
||||||
@@ -27,7 +29,7 @@ class RequestContext(context.RequestContext):
|
|||||||
project_name=None, project_id=None, roles=None,
|
project_name=None, project_id=None, roles=None,
|
||||||
is_admin=None, read_only=False, show_deleted=False,
|
is_admin=None, read_only=False, show_deleted=False,
|
||||||
request_id=None, trust_id=None, auth_token_info=None,
|
request_id=None, trust_id=None, auth_token_info=None,
|
||||||
all_tenants=False, password=None, **kwargs):
|
all_tenants=False, password=None, timestamp=None, **kwargs):
|
||||||
"""Stores several additional request parameters:
|
"""Stores several additional request parameters:
|
||||||
|
|
||||||
:param domain_id: The ID of the domain.
|
:param domain_id: The ID of the domain.
|
||||||
@@ -65,6 +67,12 @@ class RequestContext(context.RequestContext):
|
|||||||
else:
|
else:
|
||||||
self.is_admin = is_admin
|
self.is_admin = is_admin
|
||||||
|
|
||||||
|
if not timestamp:
|
||||||
|
timestamp = timeutils.utcnow()
|
||||||
|
if isinstance(timestamp, six.string_types):
|
||||||
|
timestamp = timeutils.parse_strtime(timestamp)
|
||||||
|
self.timestamp = timestamp
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
value = super(RequestContext, self).to_dict()
|
value = super(RequestContext, self).to_dict()
|
||||||
value.update({'auth_token': self.auth_token,
|
value.update({'auth_token': self.auth_token,
|
||||||
@@ -85,7 +93,10 @@ class RequestContext(context.RequestContext):
|
|||||||
'trust_id': self.trust_id,
|
'trust_id': self.trust_id,
|
||||||
'auth_token_info': self.auth_token_info,
|
'auth_token_info': self.auth_token_info,
|
||||||
'password': self.password,
|
'password': self.password,
|
||||||
'all_tenants': self.all_tenants})
|
'all_tenants': self.all_tenants,
|
||||||
|
'timestamp': timeutils.strtime(self.timestamp) if
|
||||||
|
hasattr(self, 'timestamp') else None
|
||||||
|
})
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def to_policy_values(self):
|
def to_policy_values(self):
|
||||||
|
|||||||
@@ -655,3 +655,12 @@ class VolumeCreateFailed(Invalid):
|
|||||||
|
|
||||||
class VolumeDeleteFailed(Invalid):
|
class VolumeDeleteFailed(Invalid):
|
||||||
message = _("Volume Deletion failed: %(deletion_failed)s")
|
message = _("Volume Deletion failed: %(deletion_failed)s")
|
||||||
|
|
||||||
|
|
||||||
|
class ContainerActionNotFound(ZunException):
|
||||||
|
message = _("Action for request_id %(request_id)s on container"
|
||||||
|
" %(container_uuid)s not fount")
|
||||||
|
|
||||||
|
|
||||||
|
class ContainerActionEventNotFound(ZunException):
|
||||||
|
message = _("Event %(event)s not found for action id %(action_id)s")
|
||||||
|
|||||||
@@ -857,3 +857,40 @@ def destroy_pci_device(node_id, address):
|
|||||||
def update_pci_device(node_id, address, value):
|
def update_pci_device(node_id, address, value):
|
||||||
"""Update a pci device."""
|
"""Update a pci device."""
|
||||||
return _get_dbdriver_instance().update_pci_device(node_id, address, value)
|
return _get_dbdriver_instance().update_pci_device(node_id, address, value)
|
||||||
|
|
||||||
|
|
||||||
|
@profiler.trace("db")
|
||||||
|
def action_start(context, values):
|
||||||
|
"""Start an action for an container."""
|
||||||
|
return _get_dbdriver_instance().action_start(context, values)
|
||||||
|
|
||||||
|
|
||||||
|
@profiler.trace("db")
|
||||||
|
def actions_get(context, uuid):
|
||||||
|
"""Get all container actions for the provided container."""
|
||||||
|
return _get_dbdriver_instance().actions_get(context, uuid)
|
||||||
|
|
||||||
|
|
||||||
|
@profiler.trace("db")
|
||||||
|
def action_get_by_request_id(context, uuid, request_id):
|
||||||
|
"""Get the action by request_id and given container."""
|
||||||
|
return _get_dbdriver_instance().action_get_by_request_id(context, uuid,
|
||||||
|
request_id)
|
||||||
|
|
||||||
|
|
||||||
|
@profiler.trace("db")
|
||||||
|
def action_event_start(context, values):
|
||||||
|
"""Start an event on an container action."""
|
||||||
|
return _get_dbdriver_instance().action_event_start(context, values)
|
||||||
|
|
||||||
|
|
||||||
|
@profiler.trace("db")
|
||||||
|
def action_event_finish(context, values):
|
||||||
|
"""Finish an event on an container action."""
|
||||||
|
return _get_dbdriver_instance().action_event_finish(context, values)
|
||||||
|
|
||||||
|
|
||||||
|
@profiler.trace("db")
|
||||||
|
def action_events_get(context, action_id):
|
||||||
|
"""Get the events by action id."""
|
||||||
|
return _get_dbdriver_instance().action_events_get(context, action_id)
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import sqlalchemy as sa
|
|||||||
from sqlalchemy.orm import contains_eager
|
from sqlalchemy.orm import contains_eager
|
||||||
from sqlalchemy.orm.exc import MultipleResultsFound
|
from sqlalchemy.orm.exc import MultipleResultsFound
|
||||||
from sqlalchemy.orm.exc import NoResultFound
|
from sqlalchemy.orm.exc import NoResultFound
|
||||||
|
from sqlalchemy.sql.expression import desc
|
||||||
from sqlalchemy.sql import func
|
from sqlalchemy.sql import func
|
||||||
|
|
||||||
from zun.common import consts
|
from zun.common import consts
|
||||||
@@ -960,3 +961,109 @@ class Connection(object):
|
|||||||
device.update(values)
|
device.update(values)
|
||||||
device.save()
|
device.save()
|
||||||
return query.one()
|
return query.one()
|
||||||
|
|
||||||
|
def action_start(self, context, values):
|
||||||
|
action = models.ContainerAction()
|
||||||
|
action.update(values)
|
||||||
|
action.save()
|
||||||
|
return action
|
||||||
|
|
||||||
|
def actions_get(self, context, container_uuid):
|
||||||
|
"""Get all container actions for the provided uuid."""
|
||||||
|
query = model_query(models.ContainerAction).\
|
||||||
|
filter_by(container_uuid=container_uuid)
|
||||||
|
actions = _paginate_query(models.ContainerAction, sort_dir='desc',
|
||||||
|
sort_key='created_at', query=query)
|
||||||
|
|
||||||
|
return actions
|
||||||
|
|
||||||
|
def action_get_by_request_id(self, context, container_uuid, request_id):
|
||||||
|
"""Get the action by request_id and given container."""
|
||||||
|
action = self._action_get_by_request_id(context, container_uuid,
|
||||||
|
request_id)
|
||||||
|
return action
|
||||||
|
|
||||||
|
def _action_get_by_request_id(self, context, container_uuid, request_id):
|
||||||
|
result = model_query(models.ContainerAction).\
|
||||||
|
filter_by(container_uuid=container_uuid).\
|
||||||
|
filter_by(request_id=request_id).\
|
||||||
|
first()
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _action_get_last_created_by_container_uuid(self, context,
|
||||||
|
container_uuid):
|
||||||
|
result = model_query(models.ContainerAction).\
|
||||||
|
filter_by(container_uuid=container_uuid).\
|
||||||
|
order_by(desc("created_at"), desc("id")).\
|
||||||
|
first()
|
||||||
|
return result
|
||||||
|
|
||||||
|
def action_event_start(self, context, values):
|
||||||
|
"""Start an event on a container action."""
|
||||||
|
action = self._action_get_by_request_id(context,
|
||||||
|
values['container_uuid'],
|
||||||
|
values['request_id'])
|
||||||
|
|
||||||
|
# When zun-compute restarts, the request_id was different with
|
||||||
|
# request_id recorded in ContainerAction, so we can't get the original
|
||||||
|
# recode according to request_id. Try to get the last created action
|
||||||
|
# so that init_container can continue to finish the recovery action.
|
||||||
|
if not action and not context.project_id:
|
||||||
|
action = self._action_get_last_created_by_container_uuid(
|
||||||
|
context, values['container_uuid'])
|
||||||
|
|
||||||
|
if not action:
|
||||||
|
raise exception.ContainerActionNotFound(
|
||||||
|
request_id=values['request_id'],
|
||||||
|
container_uuid=values['container_uuid'])
|
||||||
|
|
||||||
|
values['action_id'] = action['id']
|
||||||
|
|
||||||
|
event = models.ContainerActionEvent()
|
||||||
|
event.update(values)
|
||||||
|
event.save()
|
||||||
|
|
||||||
|
return event
|
||||||
|
|
||||||
|
def action_event_finish(self, context, values):
|
||||||
|
"""Finish an event on a container action."""
|
||||||
|
action = self._action_get_by_request_id(context,
|
||||||
|
values['container_uuid'],
|
||||||
|
values['request_id'])
|
||||||
|
|
||||||
|
# When zun-compute restarts, the request_id was different with
|
||||||
|
# request_id recorded in ContainerAction, so we can't get the original
|
||||||
|
# recode according to request_id. Try to get the last created action
|
||||||
|
# so that init_container can continue to finish the recovery action.
|
||||||
|
if not action and not context.project_id:
|
||||||
|
action = self._action_get_last_created_by_container_uuid(
|
||||||
|
context, values['container_uuid'])
|
||||||
|
|
||||||
|
if not action:
|
||||||
|
raise exception.ContainerActionNotFound(
|
||||||
|
request_id=values['request_id'],
|
||||||
|
container_uuid=values['container_uuid'])
|
||||||
|
event = model_query(models.ContainerActionEvent).\
|
||||||
|
filter_by(action_id=action['id']).\
|
||||||
|
filter_by(event=values['event']).\
|
||||||
|
first()
|
||||||
|
|
||||||
|
if not event:
|
||||||
|
raise exception.ContainerActionEventNotFound(
|
||||||
|
action_id=action['id'], event=values['event'])
|
||||||
|
|
||||||
|
event.update(values)
|
||||||
|
event.save()
|
||||||
|
|
||||||
|
if values['result'].lower() == 'error':
|
||||||
|
action.update({'message': 'Error'})
|
||||||
|
action.save()
|
||||||
|
|
||||||
|
return event
|
||||||
|
|
||||||
|
def action_events_get(self, context, action_id):
|
||||||
|
query = model_query(models.ContainerActionEvent).\
|
||||||
|
filter_by(action_id=action_id)
|
||||||
|
events = _paginate_query(models.ContainerActionEvent, sort_dir='desc',
|
||||||
|
sort_key='created_at', query=query)
|
||||||
|
return events
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
from zun.objects import capsule
|
from zun.objects import capsule
|
||||||
from zun.objects import compute_node
|
from zun.objects import compute_node
|
||||||
from zun.objects import container
|
from zun.objects import container
|
||||||
|
from zun.objects import container_action
|
||||||
from zun.objects import container_pci_requests
|
from zun.objects import container_pci_requests
|
||||||
from zun.objects import image
|
from zun.objects import image
|
||||||
from zun.objects import numa
|
from zun.objects import numa
|
||||||
@@ -23,6 +24,7 @@ from zun.objects import resource_provider
|
|||||||
from zun.objects import volume_mapping
|
from zun.objects import volume_mapping
|
||||||
from zun.objects import zun_service
|
from zun.objects import zun_service
|
||||||
|
|
||||||
|
|
||||||
Container = container.Container
|
Container = container.Container
|
||||||
VolumeMapping = volume_mapping.VolumeMapping
|
VolumeMapping = volume_mapping.VolumeMapping
|
||||||
ZunService = zun_service.ZunService
|
ZunService = zun_service.ZunService
|
||||||
@@ -37,6 +39,8 @@ PciDevice = pci_device.PciDevice
|
|||||||
PciDevicePool = pci_device_pool.PciDevicePool
|
PciDevicePool = pci_device_pool.PciDevicePool
|
||||||
ContainerPCIRequest = container_pci_requests.ContainerPCIRequest
|
ContainerPCIRequest = container_pci_requests.ContainerPCIRequest
|
||||||
ContainerPCIRequests = container_pci_requests.ContainerPCIRequests
|
ContainerPCIRequests = container_pci_requests.ContainerPCIRequests
|
||||||
|
ContainerAction = container_action.ContainerAction
|
||||||
|
ContainerActionEvent = container_action.ContainerActionEvent
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
Container,
|
Container,
|
||||||
@@ -53,4 +57,6 @@ __all__ = (
|
|||||||
PciDevicePool,
|
PciDevicePool,
|
||||||
ContainerPCIRequest,
|
ContainerPCIRequest,
|
||||||
ContainerPCIRequests,
|
ContainerPCIRequests,
|
||||||
|
ContainerAction,
|
||||||
|
ContainerActionEvent,
|
||||||
)
|
)
|
||||||
|
|||||||
175
zun/objects/container_action.py
Normal file
175
zun/objects/container_action.py
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
from oslo_utils import timeutils
|
||||||
|
from oslo_versionedobjects import fields
|
||||||
|
import six
|
||||||
|
|
||||||
|
from zun.db import api as dbapi
|
||||||
|
from zun.objects import base
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@base.ZunObjectRegistry.register
|
||||||
|
class ContainerAction(base.ZunPersistentObject, base.ZunObject):
|
||||||
|
|
||||||
|
# Version 1.0: Initial version
|
||||||
|
VERSION = '1.0'
|
||||||
|
|
||||||
|
fields = {
|
||||||
|
'id': fields.IntegerField(),
|
||||||
|
'action': fields.StringField(nullable=True),
|
||||||
|
'container_uuid': fields.UUIDField(nullable=True),
|
||||||
|
'request_id': fields.StringField(nullable=True),
|
||||||
|
'user_id': fields.StringField(nullable=True),
|
||||||
|
'project_id': fields.StringField(nullable=True),
|
||||||
|
'start_time': fields.DateTimeField(nullable=True),
|
||||||
|
'finish_time': fields.DateTimeField(nullable=True),
|
||||||
|
'message': fields.StringField(nullable=True),
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _from_db_object(context, action, db_action):
|
||||||
|
for field in action.fields:
|
||||||
|
setattr(action, field, db_action[field])
|
||||||
|
|
||||||
|
action.obj_reset_changes()
|
||||||
|
return action
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _from_db_object_list(context, cls, db_objects):
|
||||||
|
"""Converts a list of database entities to a list of formal objects."""
|
||||||
|
return [ContainerAction._from_db_object(context, cls(context), obj)
|
||||||
|
for obj in db_objects]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def pack_action_start(context, container_uuid, action_name):
|
||||||
|
values = {'request_id': context.request_id,
|
||||||
|
'container_uuid': container_uuid,
|
||||||
|
'user_id': context.user_id,
|
||||||
|
'project_id': context.project_id,
|
||||||
|
'action': action_name,
|
||||||
|
'start_time': context.timestamp}
|
||||||
|
return values
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def pack_action_finish(context, container_uuid):
|
||||||
|
values = {'request_id': context.request_id,
|
||||||
|
'container_uuid': container_uuid,
|
||||||
|
'finish_time': timeutils.utcnow()}
|
||||||
|
return values
|
||||||
|
|
||||||
|
@base.remotable_classmethod
|
||||||
|
def get_by_request_id(cls, context, container_uuid, request_id):
|
||||||
|
db_action = dbapi.action_get_by_request_id(context, container_uuid,
|
||||||
|
request_id)
|
||||||
|
if db_action:
|
||||||
|
return cls._from_db_object(context, cls(context), db_action)
|
||||||
|
|
||||||
|
@base.remotable_classmethod
|
||||||
|
def action_start(cls, context, container_uuid, action_name,
|
||||||
|
want_result=True):
|
||||||
|
values = cls.pack_action_start(context, container_uuid, action_name)
|
||||||
|
db_action = dbapi.action_start(context, values)
|
||||||
|
if want_result:
|
||||||
|
return cls._from_db_object(context, cls(context), db_action)
|
||||||
|
|
||||||
|
@base.remotable_classmethod
|
||||||
|
def get_by_container_uuid(cls, context, instance_uuid):
|
||||||
|
db_actions = dbapi.actions_get(context, instance_uuid)
|
||||||
|
return ContainerAction._from_db_object_list(context, cls, db_actions)
|
||||||
|
|
||||||
|
|
||||||
|
@base.ZunObjectRegistry.register
|
||||||
|
class ContainerActionEvent(base.ZunPersistentObject, base.ZunObject):
|
||||||
|
# Version 1.0: Initial version
|
||||||
|
VERSION = '1.0'
|
||||||
|
fields = {
|
||||||
|
'id': fields.IntegerField(),
|
||||||
|
'event': fields.StringField(nullable=True),
|
||||||
|
'action_id': fields.IntegerField(nullable=True),
|
||||||
|
'start_time': fields.DateTimeField(nullable=True),
|
||||||
|
'finish_time': fields.DateTimeField(nullable=True),
|
||||||
|
'result': fields.StringField(nullable=True),
|
||||||
|
'traceback': fields.StringField(nullable=True),
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _from_db_object(context, event, db_event):
|
||||||
|
for field in event.fields:
|
||||||
|
setattr(event, field, db_event[field])
|
||||||
|
|
||||||
|
event.obj_reset_changes()
|
||||||
|
return event
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _from_db_object_list(context, cls, db_objects):
|
||||||
|
"""Converts a list of database entities to a list of formal objects."""
|
||||||
|
return [ContainerActionEvent._from_db_object(context, cls(context),
|
||||||
|
obj)
|
||||||
|
for obj in db_objects]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def pack_action_event_start(context, container_uuid, event_name):
|
||||||
|
values = {'event': event_name,
|
||||||
|
'container_uuid': container_uuid,
|
||||||
|
'request_id': context.request_id,
|
||||||
|
'start_time': timeutils.utcnow()}
|
||||||
|
return values
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def pack_action_event_finish(context, container_uuid, event_name,
|
||||||
|
exc_val=None, exc_tb=None):
|
||||||
|
values = {'event': event_name,
|
||||||
|
'container_uuid': container_uuid,
|
||||||
|
'request_id': context.request_id,
|
||||||
|
'finish_time': timeutils.utcnow()}
|
||||||
|
if exc_tb is None:
|
||||||
|
values['result'] = 'Success'
|
||||||
|
else:
|
||||||
|
values['result'] = 'Error'
|
||||||
|
values['message'] = exc_val
|
||||||
|
values['traceback'] = exc_tb
|
||||||
|
return values
|
||||||
|
|
||||||
|
@base.remotable_classmethod
|
||||||
|
def event_start(cls, context, container_uuid, event_name,
|
||||||
|
want_result=True):
|
||||||
|
values = cls.pack_action_event_start(context, container_uuid,
|
||||||
|
event_name)
|
||||||
|
db_event = dbapi.action_event_start(context, values)
|
||||||
|
if want_result:
|
||||||
|
return cls._from_db_object(context, cls(context), db_event)
|
||||||
|
|
||||||
|
@base.remotable_classmethod
|
||||||
|
def event_finish(cls, context, container_uuid, event_name, exc_val=None,
|
||||||
|
exc_tb=None, want_result=None):
|
||||||
|
if exc_val:
|
||||||
|
exc_val = six.text_type(exc_val)
|
||||||
|
if exc_tb and not isinstance(exc_tb, six.string_types):
|
||||||
|
exc_tb = ''.join(traceback.format_tb(exc_tb))
|
||||||
|
values = cls.pack_action_event_finish(context, container_uuid,
|
||||||
|
event_name, exc_val=exc_val,
|
||||||
|
exc_tb=exc_tb)
|
||||||
|
db_event = dbapi.action_event_finish(context, values)
|
||||||
|
if want_result:
|
||||||
|
return cls._from_db_object(context, cls(context), db_event)
|
||||||
|
|
||||||
|
@base.remotable_classmethod
|
||||||
|
def get_by_action(cls, context, action_id):
|
||||||
|
db_events = dbapi.action_events_get(context, action_id)
|
||||||
|
return ContainerActionEvent._from_db_object_list(context, cls,
|
||||||
|
db_events)
|
||||||
221
zun/tests/unit/db/test_container_action.py
Normal file
221
zun/tests/unit/db/test_container_action.py
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Tests for manipulating Container Actions via the DB API"""
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
from oslo_utils import timeutils
|
||||||
|
from oslo_utils import uuidutils
|
||||||
|
|
||||||
|
from zun.common import exception
|
||||||
|
import zun.conf
|
||||||
|
from zun.db import api as dbapi
|
||||||
|
from zun.tests.unit.db import base
|
||||||
|
from zun.tests.unit.db import utils
|
||||||
|
|
||||||
|
CONF = zun.conf.CONF
|
||||||
|
|
||||||
|
|
||||||
|
class DbContainerActionTestCase(base.DbTestCase,
|
||||||
|
base.ModelsObjectComparatorMixin):
|
||||||
|
IGNORED_FIELDS = [
|
||||||
|
'id',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
]
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
cfg.CONF.set_override('db_type', 'sql')
|
||||||
|
super(DbContainerActionTestCase, self).setUp()
|
||||||
|
|
||||||
|
def _create_action_values(self, uuid, action='create_container'):
|
||||||
|
|
||||||
|
utils.create_test_container(context=self.context,
|
||||||
|
name='cont1',
|
||||||
|
uuid=uuid)
|
||||||
|
|
||||||
|
values = {
|
||||||
|
'action': action,
|
||||||
|
'container_uuid': uuid,
|
||||||
|
'request_id': self.context.request_id,
|
||||||
|
'user_id': self.context.user_id,
|
||||||
|
'project_id': self.context.project_id,
|
||||||
|
'start_time': timeutils.utcnow(),
|
||||||
|
'message': 'action-message'
|
||||||
|
}
|
||||||
|
return values
|
||||||
|
|
||||||
|
def _create_event_values(self, uuid, event='do_create', extra=None):
|
||||||
|
values = {
|
||||||
|
'event': event,
|
||||||
|
'container_uuid': uuid,
|
||||||
|
'request_id': self.context.request_id,
|
||||||
|
'start_time': timeutils.utcnow(),
|
||||||
|
'details': 'fake-details',
|
||||||
|
}
|
||||||
|
if extra is not None:
|
||||||
|
values.update(extra)
|
||||||
|
return values
|
||||||
|
|
||||||
|
def _assertActionSaved(self, action, uuid):
|
||||||
|
"""Retrieve the action to ensure it was successfully added."""
|
||||||
|
actions = dbapi.actions_get(self.context, uuid)
|
||||||
|
self.assertEqual(1, len(actions))
|
||||||
|
self._assertEqualObjects(action, actions[0])
|
||||||
|
|
||||||
|
def _assertActionEventSaved(self, event, action_id):
|
||||||
|
"""Retrieve the event to ensure it was successfully added."""
|
||||||
|
events = dbapi.action_events_get(self.context, action_id)
|
||||||
|
self.assertEqual(1, len(events))
|
||||||
|
self._assertEqualObjects(event, events[0],
|
||||||
|
['container_uuid', 'request_id'])
|
||||||
|
|
||||||
|
def test_container_action_start(self):
|
||||||
|
"""Create a container action."""
|
||||||
|
uuid = uuidutils.generate_uuid()
|
||||||
|
action_values = self._create_action_values(uuid)
|
||||||
|
action = dbapi.action_start(self.context, action_values)
|
||||||
|
|
||||||
|
ignored_keys = self.IGNORED_FIELDS + ['finish_time']
|
||||||
|
self._assertEqualObjects(action_values, action, ignored_keys)
|
||||||
|
|
||||||
|
self._assertActionSaved(action, uuid)
|
||||||
|
|
||||||
|
def test_container_actions_get_by_container(self):
|
||||||
|
"""Ensure we can get actions by UUID."""
|
||||||
|
uuid1 = uuidutils.generate_uuid()
|
||||||
|
|
||||||
|
expected = []
|
||||||
|
|
||||||
|
action_values = self._create_action_values(uuid1)
|
||||||
|
action = dbapi.action_start(self.context, action_values)
|
||||||
|
expected.append(action)
|
||||||
|
|
||||||
|
action_values['action'] = 'test-action'
|
||||||
|
action = dbapi.action_start(self.context, action_values)
|
||||||
|
expected.append(action)
|
||||||
|
|
||||||
|
# Create an other container action.
|
||||||
|
uuid2 = uuidutils.generate_uuid()
|
||||||
|
action_values = self._create_action_values(uuid2, 'test-action')
|
||||||
|
dbapi.action_start(self.context, action_values)
|
||||||
|
|
||||||
|
actions = dbapi.actions_get(self.context, uuid1)
|
||||||
|
self._assertEqualListsOfObjects(expected, actions)
|
||||||
|
|
||||||
|
def test_container_action_get_by_container_and_request(self):
|
||||||
|
"""Ensure we can get an action by container UUID and request_id"""
|
||||||
|
uuid1 = uuidutils.generate_uuid()
|
||||||
|
|
||||||
|
action_values = self._create_action_values(uuid1)
|
||||||
|
dbapi.action_start(self.context, action_values)
|
||||||
|
request_id = action_values['request_id']
|
||||||
|
|
||||||
|
# An other action using a different req id
|
||||||
|
action_values['action'] = 'test-action'
|
||||||
|
action_values['request_id'] = 'req-00000000-7522-4d99-7ff-111111111111'
|
||||||
|
dbapi.action_start(self.context, action_values)
|
||||||
|
|
||||||
|
action = dbapi.action_get_by_request_id(self.context, uuid1,
|
||||||
|
request_id)
|
||||||
|
self.assertEqual('create_container', action['action'])
|
||||||
|
self.assertEqual(self.context.request_id, action['request_id'])
|
||||||
|
|
||||||
|
def test_container_action_event_start(self):
|
||||||
|
"""Create a container action event."""
|
||||||
|
uuid = uuidutils.generate_uuid()
|
||||||
|
|
||||||
|
action_values = self._create_action_values(uuid)
|
||||||
|
action = dbapi.action_start(self.context, action_values)
|
||||||
|
|
||||||
|
event_values = self._create_event_values(uuid)
|
||||||
|
event = dbapi.action_event_start(self.context, event_values)
|
||||||
|
|
||||||
|
event_values['action_id'] = action['id']
|
||||||
|
ignored_keys = self.IGNORED_FIELDS + ['finish_time', 'traceback',
|
||||||
|
'result']
|
||||||
|
self._assertEqualObjects(event_values, event, ignored_keys)
|
||||||
|
|
||||||
|
self._assertActionEventSaved(event, action['id'])
|
||||||
|
|
||||||
|
def test_container_action_event_start_without_action(self):
|
||||||
|
uuid = uuidutils.generate_uuid()
|
||||||
|
|
||||||
|
event_values = self._create_event_values(uuid)
|
||||||
|
self.assertRaises(exception.ContainerActionNotFound,
|
||||||
|
dbapi.action_event_start, self.context, event_values)
|
||||||
|
|
||||||
|
def test_container_action_event_finish_success(self):
|
||||||
|
"""Finish a container action event."""
|
||||||
|
uuid = uuidutils.generate_uuid()
|
||||||
|
|
||||||
|
action = dbapi.action_start(self.context,
|
||||||
|
self._create_action_values(uuid))
|
||||||
|
|
||||||
|
dbapi.action_event_start(self.context,
|
||||||
|
self._create_event_values(uuid))
|
||||||
|
|
||||||
|
event_values = {
|
||||||
|
'finish_time': timeutils.utcnow() + datetime.timedelta(seconds=5),
|
||||||
|
'result': 'Success'
|
||||||
|
}
|
||||||
|
|
||||||
|
event_values = self._create_event_values(uuid, extra=event_values)
|
||||||
|
event = dbapi.action_event_finish(self.context, event_values)
|
||||||
|
|
||||||
|
self._assertActionEventSaved(event, action['id'])
|
||||||
|
action = dbapi.action_get_by_request_id(self.context, uuid,
|
||||||
|
self.context.request_id)
|
||||||
|
self.assertNotEqual('Error', action['message'])
|
||||||
|
|
||||||
|
def test_container_action_event_finish_without_action(self):
|
||||||
|
uuid = uuidutils.generate_uuid()
|
||||||
|
|
||||||
|
event_values = {
|
||||||
|
'finish_time': timeutils.utcnow() + datetime.timedelta(seconds=5),
|
||||||
|
'result': 'Success'
|
||||||
|
}
|
||||||
|
event_values = self._create_event_values(uuid, extra=event_values)
|
||||||
|
self.assertRaises(exception.ContainerActionNotFound,
|
||||||
|
dbapi.action_event_finish,
|
||||||
|
self.context, event_values)
|
||||||
|
|
||||||
|
def test_container_action_events_get_in_order(self):
|
||||||
|
"""Ensure retrived action events are in order."""
|
||||||
|
uuid1 = uuidutils.generate_uuid()
|
||||||
|
|
||||||
|
action = dbapi.action_start(self.context,
|
||||||
|
self._create_action_values(uuid1))
|
||||||
|
|
||||||
|
extra1 = {
|
||||||
|
'created_at': timeutils.utcnow()
|
||||||
|
}
|
||||||
|
|
||||||
|
extra2 = {
|
||||||
|
'created_at': timeutils.utcnow() + datetime.timedelta(seconds=5)
|
||||||
|
}
|
||||||
|
|
||||||
|
event_val1 = self._create_event_values(uuid1, 'fake1', extra=extra1)
|
||||||
|
event_val2 = self._create_event_values(uuid1, 'fake2', extra=extra1)
|
||||||
|
event_val3 = self._create_event_values(uuid1, 'fake3', extra=extra2)
|
||||||
|
|
||||||
|
event1 = dbapi.action_event_start(self.context, event_val1)
|
||||||
|
event2 = dbapi.action_event_start(self.context, event_val2)
|
||||||
|
event3 = dbapi.action_event_start(self.context, event_val3)
|
||||||
|
|
||||||
|
events = dbapi.action_events_get(self.context, action['id'])
|
||||||
|
|
||||||
|
self.assertEqual(3, len(events))
|
||||||
|
|
||||||
|
self._assertEqualOrderedListOfObjects([event3, event2, event1], events,
|
||||||
|
['container_uuid', 'request_id'])
|
||||||
@@ -395,3 +395,34 @@ def create_test_capsule(**kwargs):
|
|||||||
del capsule['id']
|
del capsule['id']
|
||||||
dbapi = db_api._get_dbdriver_instance()
|
dbapi = db_api._get_dbdriver_instance()
|
||||||
return dbapi.create_capsule(kwargs['context'], capsule)
|
return dbapi.create_capsule(kwargs['context'], capsule)
|
||||||
|
|
||||||
|
|
||||||
|
def get_test_action(**kwargs):
|
||||||
|
return {
|
||||||
|
'created_at': kwargs.get('created_at'),
|
||||||
|
'updated_at': kwargs.get('updated_at'),
|
||||||
|
'id': kwargs.get('id', 123),
|
||||||
|
'action': kwargs.get('action', 'fake-action'),
|
||||||
|
'container_uuid': kwargs.get('container_uuid',
|
||||||
|
'ea8e2a25-2901-438d-8157-de7ffd68d051'),
|
||||||
|
'request_id': kwargs.get('request_id', 'fake-request'),
|
||||||
|
'user_id': kwargs.get('user_id', 'fake-user'),
|
||||||
|
'project_id': kwargs.get('project_id', 'fake-project'),
|
||||||
|
'start_time': kwargs.get('start_time'),
|
||||||
|
'finish_time': kwargs.get('finish_time'),
|
||||||
|
'message': kwargs.get('message', 'fake-message'),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_test_action_event(**kwargs):
|
||||||
|
return {
|
||||||
|
'created_at': kwargs.get('created_at'),
|
||||||
|
'updated_at': kwargs.get('updated_at'),
|
||||||
|
'id': kwargs.get('id', 123),
|
||||||
|
'event': kwargs.get('event', 'fake-event'),
|
||||||
|
'action_id': kwargs.get('action_id', 123),
|
||||||
|
'start_time': kwargs.get('start_time'),
|
||||||
|
'finish_time': kwargs.get('finish_time'),
|
||||||
|
'result': kwargs.get('result', 'Error'),
|
||||||
|
'traceback': kwargs.get('traceback', 'fake-tb'),
|
||||||
|
}
|
||||||
|
|||||||
128
zun/tests/unit/objects/test_container_action.py
Normal file
128
zun/tests/unit/objects/test_container_action.py
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
# Copyright 2015 OpenStack Foundation
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
|
from oslo_utils import fixture as utils_fixture
|
||||||
|
from oslo_utils import timeutils
|
||||||
|
|
||||||
|
from testtools.matchers import HasLength
|
||||||
|
|
||||||
|
from zun import objects
|
||||||
|
from zun.tests.unit.db import base
|
||||||
|
from zun.tests.unit.db import utils
|
||||||
|
|
||||||
|
NOW = timeutils.utcnow().replace(microsecond=0)
|
||||||
|
|
||||||
|
|
||||||
|
class TestContainerActionObject(base.DbTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestContainerActionObject, self).setUp()
|
||||||
|
self.fake_action = utils.get_test_action()
|
||||||
|
|
||||||
|
def test_get_by_request_id(self):
|
||||||
|
container_ident = self.fake_action['container_uuid']
|
||||||
|
request_id = self.fake_action['request_id']
|
||||||
|
with mock.patch.object(self.dbapi, 'action_get_by_request_id',
|
||||||
|
autospec=True) as mock_get_action:
|
||||||
|
mock_get_action.return_value = self.fake_action
|
||||||
|
action = objects.ContainerAction.get_by_request_id(
|
||||||
|
self.context, container_ident, request_id)
|
||||||
|
mock_get_action.assert_called_once_with(
|
||||||
|
self.context, container_ident, request_id)
|
||||||
|
self.assertEqual(self.context, action._context)
|
||||||
|
|
||||||
|
def test_get_by_container_uuid(self):
|
||||||
|
container_ident = self.fake_action['container_uuid']
|
||||||
|
with mock.patch.object(self.dbapi, 'actions_get', autospec=True) \
|
||||||
|
as mock_get_actions:
|
||||||
|
mock_get_actions.return_value = [self.fake_action]
|
||||||
|
actions = objects.ContainerAction.get_by_container_uuid(
|
||||||
|
self.context, container_ident)
|
||||||
|
mock_get_actions.assert_called_once_with(self.context,
|
||||||
|
container_ident)
|
||||||
|
|
||||||
|
self.assertThat(actions, HasLength(1))
|
||||||
|
self.assertIsInstance(actions[0], objects.ContainerAction)
|
||||||
|
self.assertEqual(self.context, actions[0]._context)
|
||||||
|
|
||||||
|
def test_action_start(self):
|
||||||
|
self.useFixture(utils_fixture.TimeFixture(NOW))
|
||||||
|
container_ident = self.fake_action['container_uuid']
|
||||||
|
action_name = self.fake_action['action']
|
||||||
|
test_class = objects.ContainerAction
|
||||||
|
expected_packed_values = test_class.pack_action_start(
|
||||||
|
self.context, container_ident, action_name)
|
||||||
|
with mock.patch.object(self.dbapi, 'action_start', autospec=True) \
|
||||||
|
as mock_action_start:
|
||||||
|
mock_action_start.return_value = self.fake_action
|
||||||
|
action = objects.ContainerAction.action_start(
|
||||||
|
self.context, container_ident, action_name, want_result=True)
|
||||||
|
mock_action_start.assert_called_once_with(
|
||||||
|
self.context, expected_packed_values)
|
||||||
|
self.assertEqual(self.context, action._context)
|
||||||
|
|
||||||
|
|
||||||
|
class TestContainerActionEventObject(base.DbTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestContainerActionEventObject, self).setUp()
|
||||||
|
self.fake_action = utils.get_test_action()
|
||||||
|
self.fake_event = utils.get_test_action_event()
|
||||||
|
|
||||||
|
def test_get_by_action(self):
|
||||||
|
action_id = self.fake_event['action_id']
|
||||||
|
with mock.patch.object(self.dbapi, 'action_events_get',
|
||||||
|
autospec=True) as mock_get_event:
|
||||||
|
mock_get_event.return_value = [self.fake_event]
|
||||||
|
events = objects.ContainerActionEvent.get_by_action(self.context,
|
||||||
|
action_id)
|
||||||
|
mock_get_event.assert_called_once_with(self.context, action_id)
|
||||||
|
self.assertThat(events, HasLength(1))
|
||||||
|
self.assertIsInstance(events[0], objects.ContainerActionEvent)
|
||||||
|
self.assertEqual(self.context, events[0]._context)
|
||||||
|
|
||||||
|
def test_event_start(self):
|
||||||
|
self.useFixture(utils_fixture.TimeFixture(NOW))
|
||||||
|
container_uuid = self.fake_action['container_uuid']
|
||||||
|
event_name = self.fake_event['event']
|
||||||
|
test_class = objects.ContainerActionEvent
|
||||||
|
expected_packed_values = test_class.pack_action_event_start(
|
||||||
|
self.context, container_uuid, event_name)
|
||||||
|
with mock.patch.object(self.dbapi, 'action_event_start',
|
||||||
|
autospec=True) as mock_event_start:
|
||||||
|
mock_event_start.return_value = self.fake_event
|
||||||
|
event = objects.ContainerActionEvent.event_start(
|
||||||
|
self.context, container_uuid, event_name, want_result=True)
|
||||||
|
mock_event_start.assert_called_once_with(self.context,
|
||||||
|
expected_packed_values)
|
||||||
|
self.assertEqual(self.context, event._context)
|
||||||
|
|
||||||
|
def test_event_finish(self):
|
||||||
|
self.useFixture(utils_fixture.TimeFixture(NOW))
|
||||||
|
container_uuid = self.fake_action['container_uuid']
|
||||||
|
event_name = self.fake_event['event']
|
||||||
|
test_class = objects.ContainerActionEvent
|
||||||
|
expected_packed_values = test_class.pack_action_event_finish(
|
||||||
|
self.context, container_uuid, event_name)
|
||||||
|
with mock.patch.object(self.dbapi, 'action_event_finish',
|
||||||
|
autospec=True) as mock_event_finish:
|
||||||
|
mock_event_finish.return_value = self.fake_event
|
||||||
|
event = objects.ContainerActionEvent.event_finish(
|
||||||
|
self.context, container_uuid, event_name, want_result=True)
|
||||||
|
mock_event_finish.assert_called_once_with(self.context,
|
||||||
|
expected_packed_values)
|
||||||
|
self.assertEqual(self.context, event._context)
|
||||||
@@ -359,7 +359,9 @@ object_data = {
|
|||||||
'PciDevicePool': '1.0-3f5ddc3ff7bfa14da7f6c7e9904cc000',
|
'PciDevicePool': '1.0-3f5ddc3ff7bfa14da7f6c7e9904cc000',
|
||||||
'PciDevicePoolList': '1.0-15ecf022a68ddbb8c2a6739cfc9f8f5e',
|
'PciDevicePoolList': '1.0-15ecf022a68ddbb8c2a6739cfc9f8f5e',
|
||||||
'ContainerPCIRequest': '1.0-b060f9f9f734bedde79a71a4d3112ee0',
|
'ContainerPCIRequest': '1.0-b060f9f9f734bedde79a71a4d3112ee0',
|
||||||
'ContainerPCIRequests': '1.0-7b8f7f044661fe4e24e6949c035af2c4'
|
'ContainerPCIRequests': '1.0-7b8f7f044661fe4e24e6949c035af2c4',
|
||||||
|
'ContainerAction': '1.0-8d6facdc65855c6c6afbed8531209279',
|
||||||
|
'ContainerActionEvent': '1.0-2974d0a6f5d4821fd4e223a88c10181a'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user