diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index f31efc23828a..6aa6ba6e17f7 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -103,6 +103,15 @@ class Service(BASE, NovaBase, models.SoftDeleteMixin): forced_down = Column(Boolean, default=False) version = Column(Integer, default=0) + instance = orm.relationship( + "Instance", + backref='services', + primaryjoin='and_(Service.host == Instance.host,' + 'Service.binary == "nova-compute",' + 'Instance.deleted == 0)', + foreign_keys=host, + ) + class ComputeNode(BASE, NovaBase, models.SoftDeleteMixin): """Represents a running compute service on a host.""" diff --git a/nova/objects/fields.py b/nova/objects/fields.py index 9ad70c4f829b..991c3d7f2f55 100644 --- a/nova/objects/fields.py +++ b/nova/objects/fields.py @@ -420,6 +420,20 @@ class MonitorMetricType(Enum): valid_values=MonitorMetricType.ALL) +class HostStatus(Enum): + + UP = "UP" # The nova-compute is up. + DOWN = "DOWN" # The nova-compute is forced_down. + MAINTENANCE = "MAINTENANCE" # The nova-compute is disabled. + UNKNOWN = "UNKNOWN" # The nova-compute has not reported. + + ALL = (UP, DOWN, MAINTENANCE, UNKNOWN) + + def __init__(self): + super(HostStatus, self).__init__( + valid_values=HostStatus.ALL) + + class PciDeviceStatus(Enum): AVAILABLE = "available" diff --git a/nova/objects/instance.py b/nova/objects/instance.py index e3e49b31b32d..0e4af1c490f9 100644 --- a/nova/objects/instance.py +++ b/nova/objects/instance.py @@ -19,6 +19,7 @@ from oslo_db import exception as db_exc from oslo_log import log as logging from oslo_serialization import jsonutils from oslo_utils import timeutils +from oslo_utils import versionutils from nova.cells import opts as cells_opts from nova.cells import rpcapi as cells_rpcapi @@ -40,7 +41,7 @@ LOG = logging.getLogger(__name__) # List of fields that can be joined in DB layer. _INSTANCE_OPTIONAL_JOINED_FIELDS = ['metadata', 'system_metadata', 'info_cache', 'security_groups', - 'pci_devices', 'tags'] + 'pci_devices', 'tags', 'services'] # These are fields that are optional but don't translate to db columns _INSTANCE_OPTIONAL_NON_COLUMN_FIELDS = ['fault', 'flavor', 'old_flavor', 'new_flavor', 'ec2_ids'] @@ -86,7 +87,8 @@ _NO_DATA_SENTINEL = object() class Instance(base.NovaPersistentObject, base.NovaObject, base.NovaObjectDictCompat): # Version 2.0: Initial version - VERSION = '2.0' + # Version 2.1: Added services + VERSION = '2.1' fields = { 'id': fields.IntegerField(), @@ -107,6 +109,8 @@ class Instance(base.NovaPersistentObject, base.NovaObject, 'vm_state': fields.StringField(nullable=True), 'task_state': fields.StringField(nullable=True), + 'services': fields.ObjectField('ServiceList'), + 'memory_mb': fields.IntegerField(nullable=True), 'vcpus': fields.IntegerField(nullable=True), 'root_gb': fields.IntegerField(nullable=True), @@ -187,6 +191,12 @@ class Instance(base.NovaPersistentObject, base.NovaObject, obj_extra_fields = ['name'] + def obj_make_compatible(self, primitive, target_version): + super(Instance, self).obj_make_compatible(primitive, target_version) + target_version = versionutils.convert_version_to_tuple(target_version) + if target_version < (2, 1) and 'services' in primitive: + del primitive['services'] + def __init__(self, *args, **kwargs): super(Instance, self).__init__(*args, **kwargs) self._reset_metadata_tracking() @@ -360,6 +370,12 @@ class Instance(base.NovaPersistentObject, base.NovaObject, objects.Tag, db_inst['tags']) instance['tags'] = tags + if 'services' in expected_attrs: + services = base.obj_make_list( + context, objects.ServiceList(context), + objects.Service, db_inst['services']) + instance['services'] = services + instance.obj_reset_changes() return instance diff --git a/nova/tests/unit/fake_instance.py b/nova/tests/unit/fake_instance.py index e41540215e11..b5ff4a8d9e87 100644 --- a/nova/tests/unit/fake_instance.py +++ b/nova/tests/unit/fake_instance.py @@ -74,7 +74,8 @@ def fake_db_instance(**updates): 'numa_topology': None, 'vcpu_model': None, }, - 'tags': [] + 'tags': [], + 'services': [] } for name, field in objects.Instance.fields.items(): diff --git a/nova/tests/unit/objects/test_instance.py b/nova/tests/unit/objects/test_instance.py index 2fc8bcd09b33..579eb7873fba 100644 --- a/nova/tests/unit/objects/test_instance.py +++ b/nova/tests/unit/objects/test_instance.py @@ -153,7 +153,16 @@ class _TestInstanceObject(object): test_vcpu_model.fake_vcpumodel.obj_to_primitive()) fake_mig_context = jsonutils.dumps( test_mig_ctxt.fake_migration_context_obj.obj_to_primitive()) + fake_service = {'created_at': None, 'updated_at': None, + 'deleted_at': None, 'deleted': False, 'id': 123, + 'host': 'fake-host', 'binary': 'nova-fake', + 'topic': 'fake-service-topic', 'report_count': 1, + 'forced_down': False, 'disabled': False, + 'disabled_reason': None, 'last_seen_up': None, + 'version': 1, + } fake_instance = dict(self.fake_instance, + services=[fake_service], extra={ 'numa_topology': fake_topology, 'pci_requests': fake_requests, @@ -177,6 +186,7 @@ class _TestInstanceObject(object): expected_attrs=instance.INSTANCE_OPTIONAL_ATTRS) for attr in instance.INSTANCE_OPTIONAL_ATTRS: self.assertTrue(inst.obj_attr_is_set(attr)) + self.assertEqual(123, inst.services[0].id) def test_get_by_id(self): self.mox.StubOutWithMock(db, 'instance_get') diff --git a/nova/tests/unit/objects/test_objects.py b/nova/tests/unit/objects/test_objects.py index 95d567bfd0ea..ddff931eaf8a 100644 --- a/nova/tests/unit/objects/test_objects.py +++ b/nova/tests/unit/objects/test_objects.py @@ -1134,7 +1134,7 @@ object_data = { 'HVSpec': '1.2-db672e73304da86139086d003f3977e7', 'ImageMeta': '1.8-642d1b2eb3e880a367f37d72dd76162d', 'ImageMetaProps': '1.11-96aa14a8ba226701bbd22e63557a63ea', - 'Instance': '2.0-ff56804dce87d81d9a04834d4bd1e3d2', + 'Instance': '2.1-416fdd0dfc33dfa12ff2cfdd8cc32e17', 'InstanceAction': '1.1-f9f293e526b66fca0d05c3b3a2d13914', 'InstanceActionEvent': '1.1-e56a64fa4710e43ef7af2ad9d6028b33', 'InstanceActionEventList': '1.1-13d92fb953030cdbfee56481756e02be',