diff --git a/nova/db/api.py b/nova/db/api.py index 694e9ba9cf7e..1102268210f4 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -146,6 +146,16 @@ def service_get_all_by_binary(context, binary, include_disabled=False): include_disabled=include_disabled) +def service_get_all_computes_by_hv_type(context, hv_type, + include_disabled=False): + """Get all compute services for a given hypervisor type. + + Includes disabled services if 'include_disabled' parameter is True. + """ + return IMPL.service_get_all_computes_by_hv_type(context, hv_type, + include_disabled=include_disabled) + + def service_get_all_by_host(context, host): """Get all services for a given host.""" return IMPL.service_get_all_by_host(context, host) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 80e5cdcd059d..ed482b9b77fa 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -494,6 +494,20 @@ def service_get_all_by_binary(context, binary, include_disabled=False): return query.all() +@pick_context_manager_reader +def service_get_all_computes_by_hv_type(context, hv_type, + include_disabled=False): + query = model_query(context, models.Service, read_deleted="no").\ + filter_by(binary='nova-compute') + if not include_disabled: + query = query.filter_by(disabled=False) + query = query.join(models.ComputeNode, + models.Service.host == models.ComputeNode.host).\ + filter(models.ComputeNode.hypervisor_type == hv_type).\ + distinct('host') + return query.all() + + @pick_context_manager_reader def service_get_by_host_and_binary(context, host, binary): result = model_query(context, models.Service, read_deleted="no").\ diff --git a/nova/objects/service.py b/nova/objects/service.py index a3d998b808bc..8375ce1300da 100644 --- a/nova/objects/service.py +++ b/nova/objects/service.py @@ -410,7 +410,8 @@ class ServiceList(base.ObjectListBase, base.NovaObject): # Version 1.16: Service version 1.18 # Version 1.17: Service version 1.19 # Version 1.18: Added include_disabled parameter to get_by_binary() - VERSION = '1.18' + # Version 1.19: Added get_all_computes_by_hv_type() + VERSION = '1.19' fields = { 'objects': fields.ListOfObjectsField('Service'), @@ -445,3 +446,10 @@ class ServiceList(base.ObjectListBase, base.NovaObject): context, db_services) return base.obj_make_list(context, cls(context), objects.Service, db_services) + + @base.remotable_classmethod + def get_all_computes_by_hv_type(cls, context, hv_type): + db_services = db.service_get_all_computes_by_hv_type( + context, hv_type, include_disabled=False) + return base.obj_make_list(context, cls(context), objects.Service, + db_services) diff --git a/nova/tests/unit/db/test_db_api.py b/nova/tests/unit/db/test_db_api.py index b1aa61ee66ce..7b3ec229f7dd 100644 --- a/nova/tests/unit/db/test_db_api.py +++ b/nova/tests/unit/db/test_db_api.py @@ -86,6 +86,34 @@ def _reservation_get(context, uuid): return result +def _make_compute_node(host, node, hv_type, service_id): + compute_node_dict = dict(vcpus=2, memory_mb=1024, local_gb=2048, + uuid=uuidsentinel.fake_compute_node, + vcpus_used=0, memory_mb_used=0, + local_gb_used=0, free_ram_mb=1024, + free_disk_gb=2048, hypervisor_type=hv_type, + hypervisor_version=1, cpu_info="", + running_vms=0, current_workload=0, + service_id=service_id, + host=host, + disk_available_least=100, + hypervisor_hostname=node, + host_ip='127.0.0.1', + supported_instances='', + pci_stats='', + metrics='', + extra_resources='', + cpu_allocation_ratio=16.0, + ram_allocation_ratio=1.5, + disk_allocation_ratio=1.0, + stats='', numa_topology='') + # add some random stats + stats = dict(num_instances=3, num_proj_12345=2, + num_proj_23456=2, num_vm_building=3) + compute_node_dict['stats'] = jsonutils.dumps(stats) + return compute_node_dict + + def _quota_reserve(context, project_id, user_id): """Create sample Quota, QuotaUsage and Reservation objects. @@ -3510,6 +3538,50 @@ class ServiceTestCase(test.TestCase, ModelsObjectComparatorMixin): include_disabled=True) self._assertEqualListsOfObjects(expected, real) + def test_service_get_all_computes_by_hv_type(self): + values = [ + {'host': 'host1', 'binary': 'nova-compute'}, + {'host': 'host2', 'binary': 'nova-compute', 'disabled': True}, + {'host': 'host3', 'binary': 'nova-compute'}, + {'host': 'host4', 'binary': 'b2'} + ] + services = [self._create_service(vals) for vals in values] + compute_nodes = [ + _make_compute_node('host1', 'node1', 'ironic', services[0]['id']), + _make_compute_node('host1', 'node2', 'ironic', services[0]['id']), + _make_compute_node('host2', 'node3', 'ironic', services[1]['id']), + _make_compute_node('host3', 'host3', 'kvm', services[2]['id']), + ] + [db.compute_node_create(self.ctxt, cn) for cn in compute_nodes] + + expected = services[:1] + real = db.service_get_all_computes_by_hv_type(self.ctxt, + 'ironic', + include_disabled=False) + self._assertEqualListsOfObjects(expected, real) + + def test_service_get_all_computes_by_hv_type_include_disabled(self): + values = [ + {'host': 'host1', 'binary': 'nova-compute'}, + {'host': 'host2', 'binary': 'nova-compute', 'disabled': True}, + {'host': 'host3', 'binary': 'nova-compute'}, + {'host': 'host4', 'binary': 'b2'} + ] + services = [self._create_service(vals) for vals in values] + compute_nodes = [ + _make_compute_node('host1', 'node1', 'ironic', services[0]['id']), + _make_compute_node('host1', 'node2', 'ironic', services[0]['id']), + _make_compute_node('host2', 'node3', 'ironic', services[1]['id']), + _make_compute_node('host3', 'host3', 'kvm', services[2]['id']), + ] + [db.compute_node_create(self.ctxt, cn) for cn in compute_nodes] + + expected = services[:2] + real = db.service_get_all_computes_by_hv_type(self.ctxt, + 'ironic', + include_disabled=True) + self._assertEqualListsOfObjects(expected, real) + def test_service_get_all_by_host(self): values = [ {'host': 'host1', 'topic': 't11', 'binary': 'b11'}, diff --git a/nova/tests/unit/objects/test_objects.py b/nova/tests/unit/objects/test_objects.py index f87e279c5ae6..aab394cf7308 100644 --- a/nova/tests/unit/objects/test_objects.py +++ b/nova/tests/unit/objects/test_objects.py @@ -1191,7 +1191,7 @@ object_data = { 'SecurityGroupRule': '1.1-ae1da17b79970012e8536f88cb3c6b29', 'SecurityGroupRuleList': '1.2-0005c47fcd0fb78dd6d7fd32a1409f5b', 'Service': '1.20-0f9c0bf701e68640b78638fd09e2cddc', - 'ServiceList': '1.18-6c52cb616621c1af2415dcc11faf5c1a', + 'ServiceList': '1.19-5325bce13eebcbf22edc9678285270cc', 'TaskLog': '1.0-78b0534366f29aa3eebb01860fbe18fe', 'TaskLogList': '1.0-cc8cce1af8a283b9d28b55fcd682e777', 'Tag': '1.1-8b8d7d5b48887651a0e01241672e2963', diff --git a/nova/tests/unit/objects/test_service.py b/nova/tests/unit/objects/test_service.py index 6a20dcfae2ba..971506d5e4df 100644 --- a/nova/tests/unit/objects/test_service.py +++ b/nova/tests/unit/objects/test_service.py @@ -274,6 +274,16 @@ class _TestServiceObject(object): # Make sure it doesn't re-fetch this service_obj.compute_node + @mock.patch.object(db, 'service_get_all_computes_by_hv_type') + def test_get_all_computes_by_hv_type(self, mock_get_all): + mock_get_all.return_value = [fake_service] + services = service.ServiceList.get_all_computes_by_hv_type( + self.context, 'hv-type') + self.assertEqual(1, len(services)) + self.compare_obj(services[0], fake_service, allow_missing=OPTIONAL) + mock_get_all.assert_called_once_with(self.context, 'hv-type', + include_disabled=False) + def test_load_when_orphaned(self): service_obj = service.Service() service_obj.id = 123