summaryrefslogtreecommitdiffstats
path: root/nova
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2011-12-12 00:54:38 +0000
committerGerrit Code Review <review@openstack.org>2011-12-12 00:54:38 +0000
commit1b4a0f859e76de4c6e5cb7b51a12e9bf2fb3e295 (patch)
tree9d4d44b70adcd992af1bfe9dbe5a5731dd655ae8 /nova
parenta4e2ed8cb9ba71e96dfadb96f8171054a0d30914 (diff)
parentcfe6fe374fa04b9c6150256c9a760b6b340ce697 (diff)
Merge "Add preparation for asynchronous instance faults"
Diffstat (limited to 'nova')
-rw-r--r--nova/compute/manager.py29
-rw-r--r--nova/db/api.py13
-rw-r--r--nova/db/sqlalchemy/api.py19
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/063_add_instance_faults_table.py62
-rw-r--r--nova/db/sqlalchemy/models.py14
-rw-r--r--nova/tests/test_compute.py55
-rw-r--r--nova/tests/test_db_api.py72
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'])