From 607162ffe86d7d2b5bd9eb6f16a6ee4405892fc6 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 8 Sep 2010 01:53:07 -0700 Subject: make timestamps for instances and volumes, includes additions to get deleted objects from db using deleted flag. --- nova/compute/manager.py | 5 +++++ nova/db/sqlalchemy/api.py | 42 ++++++++++++++++++++++++++---------------- nova/db/sqlalchemy/models.py | 20 ++++++++++++-------- nova/tests/compute_unittest.py | 20 ++++++++++++++++++++ 4 files changed, 63 insertions(+), 24 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 878205a36..7f6b49f90 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -21,6 +21,7 @@ Handles all code relating to instances (guest vms) """ import base64 +import datetime import logging import os @@ -83,6 +84,8 @@ class ComputeManager(manager.Manager): try: yield self.driver.spawn(instance_ref) + now = datetime.datetime.now() + self.db.instance_update(None, instance_id, {'launched_at': now}) except Exception: # pylint: disable-msg=W0702 logging.exception("instance %s: Failed to spawn", instance_ref['name']) @@ -107,6 +110,8 @@ class ComputeManager(manager.Manager): power_state.NOSTATE, 'shutting_down') yield self.driver.destroy(instance_ref) + now = datetime.datetime.now() + self.db.instance_update(None, instance_id, {'terminated_at': now}) # TODO(ja): should we keep it in a terminated state for a bit? self.db.instance_destroy(context, instance_id) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 391892214..fa9c77181 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -28,9 +28,19 @@ from sqlalchemy import or_ FLAGS = flags.FLAGS + # NOTE(vish): disabling docstring pylint because the docstrings are # in the interface definition # pylint: disable-msg=C0111 +def _deleted(context): + """Calcultates whether to include deleted objects based on context. + + Currently just looks for a flag called deleted in the context dict. + """ + if not context: + return False + return context.get('deleted', False) + ################### @@ -236,19 +246,19 @@ def instance_destroy(_context, instance_id): instance_ref.delete(session=session) -def instance_get(_context, instance_id): - return models.Instance.find(instance_id) +def instance_get(context, instance_id): + return models.Instance.find(instance_id, deleted=_deleted(context)) -def instance_get_all(_context): - return models.Instance.all() +def instance_get_all(context): + return models.Instance.all(deleted=_deleted(context)) -def instance_get_by_project(_context, project_id): +def instance_get_by_project(context, project_id): session = get_session() return session.query(models.Instance ).filter_by(project_id=project_id - ).filter_by(deleted=False + ).filter_by(deleted=_deleted(context) ).all() @@ -260,8 +270,8 @@ def instance_get_by_reservation(_context, reservation_id): ).all() -def instance_get_by_str(_context, str_id): - return models.Instance.find_by_str(str_id) +def instance_get_by_str(context, str_id): + return models.Instance.find_by_str(str_id, deleted=_deleted(context)) def instance_get_fixed_address(_context, instance_id): @@ -562,24 +572,24 @@ def volume_detached(_context, volume_id): volume_ref.save(session=session) -def volume_get(_context, volume_id): - return models.Volume.find(volume_id) +def volume_get(context, volume_id): + return models.Volume.find(volume_id, deleted=_deleted(context)) -def volume_get_all(_context): - return models.Volume.all() +def volume_get_all(context): + return models.Volume.all(deleted=_deleted(context)) -def volume_get_by_project(_context, project_id): +def volume_get_by_project(context, project_id): session = get_session() return session.query(models.Volume ).filter_by(project_id=project_id - ).filter_by(deleted=False + ).filter_by(deleted=_deleted(context) ).all() -def volume_get_by_str(_context, str_id): - return models.Volume.find_by_str(str_id) +def volume_get_by_str(context, str_id): + return models.Volume.find_by_str(str_id, deleted=_deleted(context)) def volume_get_host(context, volume_id): diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index fe3a77a52..064894e97 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -48,45 +48,46 @@ class NovaBase(object): __prefix__ = 'none' created_at = Column(DateTime, default=func.now()) updated_at = Column(DateTime, onupdate=datetime.datetime.now) + deleted_at = Column(DateTime) deleted = Column(Boolean, default=False) @classmethod - def all(cls, session=None): + def all(cls, session=None, deleted=False): """Get all objects of this type""" if not session: session = get_session() return session.query(cls) \ - .filter_by(deleted=False) \ + .filter_by(deleted=deleted) \ .all() @classmethod - def count(cls, session=None): + def count(cls, session=None, deleted=False): """Count objects of this type""" if not session: session = get_session() return session.query(cls) \ - .filter_by(deleted=False) \ + .filter_by(deleted=deleted) \ .count() @classmethod - def find(cls, obj_id, session=None): + def find(cls, obj_id, session=None, deleted=False): """Find object by id""" if not session: session = get_session() try: return session.query(cls) \ .filter_by(id=obj_id) \ - .filter_by(deleted=False) \ + .filter_by(deleted=deleted) \ .one() except exc.NoResultFound: new_exc = exception.NotFound("No model for id %s" % obj_id) raise new_exc.__class__, new_exc, sys.exc_info()[2] @classmethod - def find_by_str(cls, str_id, session=None): + def find_by_str(cls, str_id, session=None, deleted=False): """Find object by str_id""" int_id = int(str_id.rpartition('-')[2]) - return cls.find(int_id, session=session) + return cls.find(int_id, session=session, deleted=deleted) @property def str_id(self): @@ -103,6 +104,7 @@ class NovaBase(object): def delete(self, session=None): """Delete this object""" self.deleted = True + self.deleted_at = datetime.datetime.now() self.save(session=session) def __setitem__(self, key, value): @@ -230,6 +232,8 @@ class Instance(BASE, NovaBase): reservation_id = Column(String(255)) mac_address = Column(String(255)) + launched_at = Column(DateTime) + terminated_at = Column(DateTime) # TODO(vish): see Ewan's email about state improvements, probably # should be in a driver base class or some such # vmstate_state = running, halted, suspended, paused diff --git a/nova/tests/compute_unittest.py b/nova/tests/compute_unittest.py index 746c035d6..e5da6b054 100644 --- a/nova/tests/compute_unittest.py +++ b/nova/tests/compute_unittest.py @@ -18,6 +18,8 @@ """ Tests For Compute """ + +import datetime import logging from twisted.internet import defer @@ -79,6 +81,24 @@ class ComputeTestCase(test.TrialTestCase): logging.info("After terminating instances: %s", instances) self.assertEqual(len(instances), 0) + @defer.inlineCallbacks + def test_run_terminate_timestamps(self): + """Make sure it is possible to run and terminate instance""" + instance_id = self._create_instance() + instance_ref = db.instance_get(self.context, instance_id) + self.assertEqual(instance_ref['launched_at'], None) + self.assertEqual(instance_ref['terminated_at'], None) + launch = datetime.datetime.now() + yield self.compute.run_instance(self.context, instance_id) + instance_ref = db.instance_get(self.context, instance_id) + self.assert_(instance_ref['launched_at'] > launch) + self.assertEqual(instance_ref['terminated_at'], None) + terminate = datetime.datetime.now() + yield self.compute.terminate_instance(self.context, instance_id) + instance_ref = db.instance_get({'deleted': True}, instance_id) + self.assert_(instance_ref['launched_at'] < terminate) + self.assert_(instance_ref['terminated_at'] > terminate) + @defer.inlineCallbacks def test_reboot(self): """Ensure instance can be rebooted""" -- cgit