From b63c5cc36dc1022f65ba79a0b43805d30f93e883 Mon Sep 17 00:00:00 2001 From: Roman Dobosz Date: Thu, 4 Jan 2018 11:59:34 +0100 Subject: [PATCH] Allow compute nodes to be associated with host agg patch --- ...nodes-to-be-associated-with-host-agg.patch | 247 ++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100644 affinity_ocata/0001-allow-compute-nodes-to-be-associated-with-host-agg.patch diff --git a/affinity_ocata/0001-allow-compute-nodes-to-be-associated-with-host-agg.patch b/affinity_ocata/0001-allow-compute-nodes-to-be-associated-with-host-agg.patch new file mode 100644 index 0000000..2bbfcbd --- /dev/null +++ b/affinity_ocata/0001-allow-compute-nodes-to-be-associated-with-host-agg.patch @@ -0,0 +1,247 @@ +From cc4070f78b764af439b429908a97f82e620803c9 Mon Sep 17 00:00:00 2001 +From: Roman Dobosz +Date: Wed, 27 Dec 2017 13:51:25 +0100 +Subject: [PATCH] allow compute nodes to be associated with host agg + +This is basically an Ocata backport patch from Jay Pipes: +https://review.openstack.org/#/c/526753 +--- + nova/compute/api.py | 37 +++++- + nova/tests/functional/compute/__init__.py | 0 + .../tests/functional/compute/test_aggregate_api.py | 139 +++++++++++++++++++++ + nova/tests/unit/compute/test_compute.py | 4 +- + 4 files changed, 174 insertions(+), 6 deletions(-) + create mode 100644 nova/tests/functional/compute/__init__.py + create mode 100644 nova/tests/functional/compute/test_aggregate_api.py + +diff --git a/nova/compute/api.py b/nova/compute/api.py +index 36de3abc34..b064f26a45 100644 +--- a/nova/compute/api.py ++++ b/nova/compute/api.py +@@ -4540,6 +4540,32 @@ class AggregateAPI(base.Base): + availability_zones.update_host_availability_zone_cache(context, + host_name) + ++ def _service_or_compute_node_exists(self, ctx, host_or_node): ++ """ ++ Returns True if a service host or compute node record could be found ++ for the supplied host_or_node string. We first check to see if a ++ service record can be found with the host matching the host_or_node ++ parameter by looking at the host mapping records in the API database. ++ If we don't find a service record there, we then ask all cell databases ++ to find a compute node with a hypervisor_hostname matching the supplied ++ host_or_node parameter. ++ """ ++ # NOTE(gryf): we don't handle cells in Ocata yet ++ try: ++ objects.Service.get_by_compute_host(ctx, host_or_node) ++ return True ++ except exception.HostMappingNotFound: ++ pass ++ ++ found_nodes = (len(objects.ComputeNodeList ++ .get_by_hypervisor(ctx, host_or_node))) ++ ++ if found_nodes > 1: ++ LOG.debug("Searching for compute nodes matching %s " ++ "found %d results but expected 1 result.", ++ host_or_node, len(found_nodes)) ++ return found_nodes == 1 ++ + @wrap_exception() + def add_host_to_aggregate(self, context, aggregate_id, host_name): + """Adds the host to an aggregate.""" +@@ -4548,8 +4574,9 @@ class AggregateAPI(base.Base): + compute_utils.notify_about_aggregate_update(context, + "addhost.start", + aggregate_payload) +- # validates the host; ComputeHostNotFound is raised if invalid +- objects.Service.get_by_compute_host(context, host_name) ++ ++ if not self._service_or_compute_node_exists(context, host_name): ++ raise exception.ComputeHostNotFound(host=host_name) + + aggregate = objects.Aggregate.get_by_id(context, aggregate_id) + self.is_safe_to_update_az(context, aggregate.metadata, +@@ -4575,8 +4602,10 @@ class AggregateAPI(base.Base): + compute_utils.notify_about_aggregate_update(context, + "removehost.start", + aggregate_payload) +- # validates the host; ComputeHostNotFound is raised if invalid +- objects.Service.get_by_compute_host(context, host_name) ++ ++ if not self._service_or_compute_node_exists(context, host_name): ++ raise exception.ComputeHostNotFound(host=host_name) ++ + aggregate = objects.Aggregate.get_by_id(context, aggregate_id) + aggregate.delete_host(host_name) + self.scheduler_client.update_aggregates(context, [aggregate]) +diff --git a/nova/tests/functional/compute/__init__.py b/nova/tests/functional/compute/__init__.py +new file mode 100644 +index 0000000000..e69de29bb2 +diff --git a/nova/tests/functional/compute/test_aggregate_api.py b/nova/tests/functional/compute/test_aggregate_api.py +new file mode 100644 +index 0000000000..aeaf3958f2 +--- /dev/null ++++ b/nova/tests/functional/compute/test_aggregate_api.py +@@ -0,0 +1,139 @@ ++# 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 nova.compute import api as compute_api ++from nova import context ++from nova import exception ++from nova import objects ++from nova import test ++from nova.tests import fixtures as nova_fixtures ++from nova.tests import uuidsentinel as uuids ++ ++ ++class ComputeAggregateAPIMultiCellTestCase(test.NoDBTestCase): ++ """Tests for the AggregateAPI with multiple cells allowing either service ++ hosts or compute nodes to be associated with an aggregate. ++ """ ++ ++ USES_DB_SELF = True ++ ++ def setUp(self): ++ super(ComputeAggregateAPIMultiCellTestCase, self).setUp() ++ self.agg_api = compute_api.AggregateAPI() ++ self.useFixture(nova_fixtures.Database(database='api')) ++ celldbs = nova_fixtures.CellDatabases() ++ celldbs.add_cell_database(objects.CellMapping.CELL0_UUID) ++ celldbs.add_cell_database(uuids.cell1, default=True) ++ celldbs.add_cell_database(uuids.cell2) ++ self.useFixture(celldbs) ++ ++ self.ctxt = context.get_admin_context() ++ cell0 = objects.CellMapping( ++ context=self.ctxt, uuid=objects.CellMapping.CELL0_UUID, ++ database_connection=objects.CellMapping.CELL0_UUID, ++ transport_url='none:///') ++ cell0.create() ++ cell1 = objects.CellMapping( ++ context=self.ctxt, uuid=uuids.cell1, ++ database_connection=uuids.cell1, transport_url='none:///') ++ cell1.create() ++ cell2 = objects.CellMapping( ++ context=self.ctxt, uuid=uuids.cell2, ++ database_connection=uuids.cell2, transport_url='none:///') ++ cell2.create() ++ self.cell_mappings = (cell0, cell1, cell2) ++ ++ # create two Ironic nodes managed by a single nova-compute service host ++ # in each of the non-cell0 cells ++ for cell_id, cell in enumerate(self.cell_mappings[1:]): ++ with context.target_cell(self.ctxt, cell) as cctxt: ++ hostname = 'ironic_host_cell%s' % (cell_id + 1) ++ svc = objects.Service(cctxt, host=hostname, ++ binary='nova-compute', ++ topic='nova-compute') ++ svc.create() ++ for node_id in (1, 2): ++ nodename = 'ironic_node_cell%s_%s' % (cell_id + 1, node_id) ++ compute_node_uuid = getattr(uuids, nodename) ++ node = objects.ComputeNode( ++ cctxt, uuid=compute_node_uuid, host=hostname, ++ vcpus=2, memory_mb=2048, local_gb=128, vcpus_used=0, ++ memory_mb_used=0, local_gb_used=0, cpu_info='{}', ++ hypervisor_type='ironic', hypervisor_version=10, ++ hypervisor_hostname=nodename) ++ node.create() ++ ++ # create a compute node for VMs along with a corresponding nova-compute ++ # service host in cell1 ++ with context.target_cell(self.ctxt, cell1) as cctxt: ++ hostname = 'vm_host_cell1_1' ++ svc = objects.Service(cctxt, host=hostname, ++ binary='nova-compute', ++ topic='nova-compute') ++ svc.create() ++ compute_node_uuid = getattr(uuids, hostname) ++ node = objects.ComputeNode( ++ cctxt, uuid=compute_node_uuid, host=hostname, ++ vcpus=2, memory_mb=2048, local_gb=128, vcpus_used=0, ++ memory_mb_used=0, local_gb_used=0, cpu_info='{}', ++ hypervisor_type='libvirt', hypervisor_version=10, ++ hypervisor_hostname=hostname) ++ node.create() ++ ++ def test_service_hostname(self): ++ """Test to make sure we can associate and disassociate an aggregate ++ with a service host. ++ """ ++ agg = objects.Aggregate(self.ctxt, name="rack1_baremetal") ++ agg.create() ++ ++ agg_id = agg.id ++ ++ # There is no such service host called unknown_host_cell1, so should ++ # get back a ComputeHostNotFound ++ self.assertRaises(exception.ComputeHostNotFound, ++ self.agg_api.add_host_to_aggregate, self.ctxt, ++ agg_id, 'unknown_host_cell1') ++ self.assertRaises(exception.ComputeHostNotFound, ++ self.agg_api.remove_host_from_aggregate, self.ctxt, ++ agg_id, 'unknown_host_cell1') ++ ++ hosts = ('ironic_host_cell1', 'ironic_host_cell2', 'vm_host_cell1_1') ++ for service_host in hosts: ++ self.agg_api.add_host_to_aggregate(self.ctxt, agg_id, service_host) ++ self.agg_api.remove_host_from_aggregate(self.ctxt, agg_id, ++ service_host) ++ ++ def test_compute_nodename(self): ++ """Test to make sure we can associate and disassociate an aggregate ++ with a compute node by its hypervisor_hostname. ++ """ ++ agg = objects.Aggregate(self.ctxt, name="rack1_baremetal") ++ agg.create() ++ ++ agg_id = agg.id ++ ++ # There is no such compute node called unknown_host_cell1, so should ++ # get back a ComputeHostNotFound ++ self.assertRaises(exception.ComputeHostNotFound, ++ self.agg_api.add_host_to_aggregate, self.ctxt, ++ agg_id, getattr(uuids, 'unknown_node_cell1')) ++ self.assertRaises(exception.ComputeHostNotFound, ++ self.agg_api.remove_host_from_aggregate, self.ctxt, ++ agg_id, getattr(uuids, 'unknown_host_cell1')) ++ ++ nodenames = ('ironic_node_cell1_2', 'ironic_node_cell2_1', ++ 'vm_host_cell1_1') ++ for nodename in nodenames: ++ self.agg_api.add_host_to_aggregate(self.ctxt, agg_id, nodename) ++ self.agg_api.remove_host_from_aggregate(self.ctxt, agg_id, ++ nodename) +diff --git a/nova/tests/unit/compute/test_compute.py b/nova/tests/unit/compute/test_compute.py +index a71f337eb5..a1c26d6e4d 100644 +--- a/nova/tests/unit/compute/test_compute.py ++++ b/nova/tests/unit/compute/test_compute.py +@@ -11193,11 +11193,11 @@ class ComputeAPIAggrTestCase(BaseTestCase): + mock_az.assert_called_with(self.context, host_to_remove) + + def test_remove_host_from_aggregate_raise_not_found(self): +- # Ensure ComputeHostNotFound is raised when removing invalid host. ++ # Ensure HostMappingNotFound is raised when removing invalid host. + _create_service_entries(self.context, [['fake_zone', ['fake_host']]]) + aggr = self.api.create_aggregate(self.context, 'fake_aggregate', + 'fake_zone') +- self.assertRaises(exception.ComputeHostNotFound, ++ self.assertRaises(exception.HostMappingNotFound, + self.api.remove_host_from_aggregate, + self.context, aggr.id, 'invalid_host') + +-- +2.13.6 +