Add preparation for asynchronous instance faults

Add InstanceFault model
Migration for new instance_faults table
Functions to add and get instance faults
A single example of how faults should be added

Change-Id: I439e2419240de24a728045046153451eb8a0d267
This commit is contained in:
Alex Meade
2011-12-08 14:00:45 -05:00
committed by Brian Waldon
parent 8920181a0d
commit cfe6fe374f
7 changed files with 262 additions and 2 deletions

View File

@@ -323,10 +323,11 @@ class ComputeManager(manager.SchedulerDependentManager):
except exception.InstanceNotFound: except exception.InstanceNotFound:
LOG.exception(_("Instance %s not found.") % instance_uuid) LOG.exception(_("Instance %s not found.") % instance_uuid)
return # assuming the instance was already deleted return # assuming the instance was already deleted
except Exception: except Exception as e:
with utils.save_and_reraise_exception(): with utils.save_and_reraise_exception():
self._instance_update(context, instance_uuid, self._instance_update(context, instance_uuid,
vm_state=vm_states.ERROR) vm_state=vm_states.ERROR)
self.add_instance_fault_from_exc(context, instance_uuid, e)
def _check_instance_not_already_created(self, context, instance): def _check_instance_not_already_created(self, context, instance):
"""Ensure an instance with the same name is not already present.""" """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"), LOG.info(_("Reclaiming deleted instance %(instance_id)s"),
locals()) locals())
self._delete_instance(context, instance) 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)

View File

@@ -1756,3 +1756,16 @@ def sm_volume_get(context, volume_id):
def sm_volume_get_all(context): def sm_volume_get_all(context):
"""Get all child Zones.""" """Get all child Zones."""
return IMPL.sm_volume_get_all(context) 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)

View File

@@ -4024,3 +4024,22 @@ def sm_volume_get(context, volume_id):
def sm_volume_get_all(context): def sm_volume_get_all(context):
return model_query(context, models.SMVolume, read_deleted="yes").all() 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()

View File

@@ -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()

View File

@@ -912,6 +912,17 @@ class SMVolume(BASE, NovaBase):
vdi_uuid = Column(String(255)) 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(): def register_models():
"""Register Models and create metadata. """Register Models and create metadata.
@@ -927,7 +938,8 @@ def register_models():
Project, Certificate, ConsolePool, Console, Zone, Project, Certificate, ConsolePool, Console, Zone,
VolumeMetadata, VolumeTypes, VolumeTypeExtraSpecs, VolumeMetadata, VolumeTypes, VolumeTypeExtraSpecs,
AgentBuild, InstanceMetadata, InstanceTypeExtraSpecs, Migration, AgentBuild, InstanceMetadata, InstanceTypeExtraSpecs, Migration,
VirtualStorageArray, SMFlavors, SMBackendConf, SMVolume) VirtualStorageArray, SMFlavors, SMBackendConf, SMVolume,
InstanceFault)
engine = create_engine(FLAGS.sql_connection, echo=False) engine = create_engine(FLAGS.sql_connection, echo=False)
for model in models: for model in models:
model.metadata.create_all(engine) model.metadata.create_all(engine)

View File

@@ -21,6 +21,7 @@ Tests For Compute
""" """
from copy import copy from copy import copy
from webob import exc
import mox import mox
@@ -1063,6 +1064,60 @@ class ComputeTestCase(BaseTestCase):
self.assertEqual(len(instances), 1) self.assertEqual(len(instances), 1)
self.assertEqual(power_state.NOSTATE, instances[0]['power_state']) 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): class ComputeAPITestCase(BaseTestCase):

View File

@@ -24,6 +24,7 @@ from nova import test
from nova import context from nova import context
from nova import db from nova import db
from nova import flags from nova import flags
from nova import utils
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
@@ -192,3 +193,74 @@ class DbApiTestCase(test.TestCase):
# Retrieve the metadata to ensure it was successfully updated # Retrieve the metadata to ensure it was successfully updated
instance_meta = db.instance_metadata_get(ctxt, instance.id) instance_meta = db.instance_metadata_get(ctxt, instance.id)
self.assertEqual('bar', instance_meta['host']) 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'])