diff options
| author | Jenkins <jenkins@review.openstack.org> | 2011-12-12 00:54:38 +0000 |
|---|---|---|
| committer | Gerrit Code Review <review@openstack.org> | 2011-12-12 00:54:38 +0000 |
| commit | 1b4a0f859e76de4c6e5cb7b51a12e9bf2fb3e295 (patch) | |
| tree | 9d4d44b70adcd992af1bfe9dbe5a5731dd655ae8 /nova | |
| parent | a4e2ed8cb9ba71e96dfadb96f8171054a0d30914 (diff) | |
| parent | cfe6fe374fa04b9c6150256c9a760b6b340ce697 (diff) | |
Merge "Add preparation for asynchronous instance faults"
Diffstat (limited to 'nova')
| -rw-r--r-- | nova/compute/manager.py | 29 | ||||
| -rw-r--r-- | nova/db/api.py | 13 | ||||
| -rw-r--r-- | nova/db/sqlalchemy/api.py | 19 | ||||
| -rw-r--r-- | nova/db/sqlalchemy/migrate_repo/versions/063_add_instance_faults_table.py | 62 | ||||
| -rw-r--r-- | nova/db/sqlalchemy/models.py | 14 | ||||
| -rw-r--r-- | nova/tests/test_compute.py | 55 | ||||
| -rw-r--r-- | nova/tests/test_db_api.py | 72 |
7 files changed, 262 insertions, 2 deletions
diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 897a8bc5e..8d9076b33 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -323,10 +323,11 @@ class ComputeManager(manager.SchedulerDependentManager): except exception.InstanceNotFound: LOG.exception(_("Instance %s not found.") % instance_uuid) return # assuming the instance was already deleted - except Exception: + except Exception as e: with utils.save_and_reraise_exception(): self._instance_update(context, instance_uuid, vm_state=vm_states.ERROR) + self.add_instance_fault_from_exc(context, instance_uuid, e) def _check_instance_not_already_created(self, context, instance): """Ensure an instance with the same name is not already present.""" @@ -1917,3 +1918,29 @@ class ComputeManager(manager.SchedulerDependentManager): LOG.info(_("Reclaiming deleted instance %(instance_id)s"), locals()) self._delete_instance(context, instance) + + def add_instance_fault_from_exc(self, context, instance_uuid, fault): + """Adds the specified fault to the database.""" + if hasattr(fault, "code"): + code = fault.code + else: + code = 500 + + values = { + 'instance_uuid': instance_uuid, + 'code': code, + 'message': fault.__class__.__name__, + 'details': fault.message, + } + self.db.instance_fault_create(context, values) + + def add_instance_fault(self, context, instance_uuid, code=500, + message='', details=''): + """Adds a fault to the database using the specified values.""" + values = { + 'instance_uuid': instance_uuid, + 'code': code, + 'message': message, + 'details': details, + } + self.db.instance_fault_create(context, values) diff --git a/nova/db/api.py b/nova/db/api.py index bc413319e..0e459ae67 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -1756,3 +1756,16 @@ def sm_volume_get(context, volume_id): def sm_volume_get_all(context): """Get all child Zones.""" return IMPL.sm_volume_get_all(context) + + +#################### + + +def instance_fault_create(context, values): + """Create a new Instance Fault.""" + return IMPL.instance_fault_create(context, values) + + +def instance_fault_get_by_instance(context, instance_uuid): + """Get first instance fault with the given instance uuid.""" + return IMPL.instance_fault_get_by_instance(context, instance_uuid) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 2619c247f..04f59256e 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -4024,3 +4024,22 @@ def sm_volume_get(context, volume_id): def sm_volume_get_all(context): return model_query(context, models.SMVolume, read_deleted="yes").all() + + +################ + + +def instance_fault_create(context, values): + """Create a new Instance Fault.""" + fault_ref = models.InstanceFault() + fault_ref.update(values) + fault_ref.save() + return fault_ref + + +def instance_fault_get_by_instance(context, instance_uuid): + """Get first instance fault with the given instance uuid.""" + return model_query(context, models.InstanceFault, read_deleted='no').\ + filter_by(instance_uuid=instance_uuid).\ + order_by(desc("created_at")).\ + first() diff --git a/nova/db/sqlalchemy/migrate_repo/versions/063_add_instance_faults_table.py b/nova/db/sqlalchemy/migrate_repo/versions/063_add_instance_faults_table.py new file mode 100644 index 000000000..b8c6debbd --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/063_add_instance_faults_table.py @@ -0,0 +1,62 @@ +# Copyright 2011 OpenStack LLC. +# 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. + +from sqlalchemy import Boolean, Column, DateTime, Integer, ForeignKey +from sqlalchemy import MetaData, String, Table, Text +from nova import log as logging + +meta = MetaData() + +# +# New Tables +# +instance_faults = Table('instance_faults', meta, + Column('created_at', DateTime(timezone=False)), + Column('updated_at', DateTime(timezone=False)), + Column('deleted_at', DateTime(timezone=False)), + Column('deleted', Boolean(create_constraint=True, name=None), + default=False), + Column('id', Integer(), primary_key=True, nullable=False), + Column('instance_uuid', String(36, ForeignKey('instances.uuid'))), + Column('code', Integer(), nullable=False), + Column('message', + String(length=255, convert_unicode=False, assert_unicode=None, + unicode_error=None, _warn_on_bytestring=False)), + Column('details', + Text(length=None, convert_unicode=False, assert_unicode=None, + unicode_error=None, _warn_on_bytestring=False)), + ) + + +# +# Tables to alter +# + +# (none currently) + + +def upgrade(migrate_engine): + # Upgrade operations go here. Don't create your own engine; + # bind migrate_engine to your metadata + meta.bind = migrate_engine + try: + instance_faults.create() + except Exception: + logging.info(repr(instance_faults)) + + +def downgrade(migrate_engine): + # Operations to reverse the above upgrade go here. + instance_faults.drop() diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 1774e9ea8..2070789ce 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -912,6 +912,17 @@ class SMVolume(BASE, NovaBase): vdi_uuid = Column(String(255)) +class InstanceFault(BASE, NovaBase): + __tablename__ = 'instance_faults' + id = Column(Integer(), primary_key=True, autoincrement=True) + instance_uuid = Column(String(36), + ForeignKey('instances.uuid'), + nullable=False) + code = Column(Integer(), nullable=False) + message = Column(String(255)) + details = Column(Text) + + def register_models(): """Register Models and create metadata. @@ -927,7 +938,8 @@ def register_models(): Project, Certificate, ConsolePool, Console, Zone, VolumeMetadata, VolumeTypes, VolumeTypeExtraSpecs, AgentBuild, InstanceMetadata, InstanceTypeExtraSpecs, Migration, - VirtualStorageArray, SMFlavors, SMBackendConf, SMVolume) + VirtualStorageArray, SMFlavors, SMBackendConf, SMVolume, + InstanceFault) engine = create_engine(FLAGS.sql_connection, echo=False) for model in models: model.metadata.create_all(engine) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index e4c60e068..e6167dc51 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -21,6 +21,7 @@ Tests For Compute """ from copy import copy +from webob import exc import mox @@ -1063,6 +1064,60 @@ class ComputeTestCase(BaseTestCase): self.assertEqual(len(instances), 1) self.assertEqual(power_state.NOSTATE, instances[0]['power_state']) + def test_add_instance_fault(self): + instance_uuid = str(utils.gen_uuid()) + + def fake_db_fault_create(ctxt, values): + expected = { + 'code': 404, + 'message': 'HTTPNotFound', + 'details': 'Error Details', + 'instance_uuid': instance_uuid, + } + self.assertEquals(expected, values) + + self.stubs.Set(nova.db, 'instance_fault_create', fake_db_fault_create) + + ctxt = context.get_admin_context() + self.compute.add_instance_fault(ctxt, instance_uuid, 404, + 'HTTPNotFound', 'Error Details') + + def test_add_instance_fault_error(self): + instance_uuid = str(utils.gen_uuid()) + + def fake_db_fault_create(ctxt, values): + expected = { + 'code': 500, + 'message': 'NotImplementedError', + 'details': '', + 'instance_uuid': instance_uuid, + } + self.assertEquals(expected, values) + + self.stubs.Set(nova.db, 'instance_fault_create', fake_db_fault_create) + + ctxt = context.get_admin_context() + self.compute.add_instance_fault_from_exc(ctxt, instance_uuid, + NotImplementedError()) + + def test_add_instance_fault_http_exception(self): + instance_uuid = str(utils.gen_uuid()) + + def fake_db_fault_create(ctxt, values): + expected = { + 'code': 404, + 'message': 'HTTPNotFound', + 'details': 'Error Details', + 'instance_uuid': instance_uuid, + } + self.assertEquals(expected, values) + + self.stubs.Set(nova.db, 'instance_fault_create', fake_db_fault_create) + + ctxt = context.get_admin_context() + self.compute.add_instance_fault_from_exc(ctxt, instance_uuid, + exc.HTTPNotFound("Error Details")) + class ComputeAPITestCase(BaseTestCase): diff --git a/nova/tests/test_db_api.py b/nova/tests/test_db_api.py index 10c758412..36b5eced4 100644 --- a/nova/tests/test_db_api.py +++ b/nova/tests/test_db_api.py @@ -24,6 +24,7 @@ from nova import test from nova import context from nova import db from nova import flags +from nova import utils FLAGS = flags.FLAGS @@ -192,3 +193,74 @@ class DbApiTestCase(test.TestCase): # Retrieve the metadata to ensure it was successfully updated instance_meta = db.instance_metadata_get(ctxt, instance.id) self.assertEqual('bar', instance_meta['host']) + + def test_instance_fault_create(self): + """Ensure we can create an instance fault""" + ctxt = context.get_admin_context() + uuid = str(utils.gen_uuid()) + + # Create a fault + fault_values = { + 'message': 'message', + 'details': 'detail', + 'instance_uuid': uuid, + 'code': 404, + } + db.instance_fault_create(ctxt, fault_values) + + # Retrieve the fault to ensure it was successfully added + instance_fault = db.instance_fault_get_by_instance(ctxt, uuid) + self.assertEqual(404, instance_fault['code']) + + def test_instance_fault_get_by_instance(self): + """ ensure we can retrieve an instance fault by instance UUID """ + ctxt = context.get_admin_context() + + # Create faults + uuid = str(utils.gen_uuid()) + fault_values = { + 'message': 'message', + 'details': 'detail', + 'instance_uuid': uuid, + 'code': 404, + } + db.instance_fault_create(ctxt, fault_values) + + uuid2 = str(utils.gen_uuid()) + fault_values = { + 'message': 'message', + 'details': 'detail', + 'instance_uuid': uuid2, + 'code': 500, + } + db.instance_fault_create(ctxt, fault_values) + + # Retrieve the fault to ensure it was successfully added + instance_fault = db.instance_fault_get_by_instance(ctxt, uuid2) + self.assertEqual(500, instance_fault['code']) + + def test_instance_fault_get_by_instance_first_fault(self): + """Instance_fault_get_by_instance should return the latest fault """ + ctxt = context.get_admin_context() + + # Create faults + uuid = str(utils.gen_uuid()) + fault_values = { + 'message': 'message', + 'details': 'detail', + 'instance_uuid': uuid, + 'code': 404, + } + db.instance_fault_create(ctxt, fault_values) + + fault_values = { + 'message': 'message', + 'details': 'detail', + 'instance_uuid': uuid, + 'code': 500, + } + db.instance_fault_create(ctxt, fault_values) + + # Retrieve the fault to ensure it was successfully added + instance_fault = db.instance_fault_get_by_instance(ctxt, uuid) + self.assertEqual(500, instance_fault['code']) |
