From c80d519998e7d8697f897b13374acd7984fe12a1 Mon Sep 17 00:00:00 2001 From: Matt Crees Date: Wed, 3 Jul 2024 15:34:37 +0100 Subject: [PATCH] Only create a server group when affinity is set Also adds docs so a user can find this behaviour easily. Closes-Bug: #2071832 Change-Id: Iea3c4164f3e893c355ec21653f2e8f0e848941f0 --- blazar/plugins/instances/instance_plugin.py | 41 +++++++++----- .../plugins/instances/test_instance_plugin.py | 55 +++++++++++++++++++ doc/source/cli/instance-reservation.rst | 17 ++++++ ...e-affinity-behaviour-a03231e461bd2b21.yaml | 14 +++++ 4 files changed, 112 insertions(+), 15 deletions(-) create mode 100644 releasenotes/notes/update-affinity-behaviour-a03231e461bd2b21.yaml diff --git a/blazar/plugins/instances/instance_plugin.py b/blazar/plugins/instances/instance_plugin.py index 97e88364..f4c79611 100644 --- a/blazar/plugins/instances/instance_plugin.py +++ b/blazar/plugins/instances/instance_plugin.py @@ -317,7 +317,8 @@ class VirtualInstancePlugin(base.BasePlugin, nova.NovaClientWrapper): return {'added': added_host_ids, 'removed': removed_host_ids} - def _create_flavor(self, reservation_id, vcpus, memory, disk, group_id): + def _create_flavor(self, reservation_id, vcpus, memory, disk, + group_id=None): flavor_details = { 'flavorid': reservation_id, 'name': RESERVATION_PREFIX + ":" + reservation_id, @@ -333,9 +334,10 @@ class VirtualInstancePlugin(base.BasePlugin, nova.NovaClientWrapper): reservation_rc = "resources:CUSTOM_RESERVATION_" + rsv_id_rc_format extra_specs = { FLAVOR_EXTRA_SPEC: reservation_id, - "affinity_id": group_id, reservation_rc: "1" } + if group_id is not None: + extra_specs["affinity_id"] = group_id reserved_flavor.set_keys(extra_specs) return reserved_flavor @@ -346,23 +348,31 @@ class VirtualInstancePlugin(base.BasePlugin, nova.NovaClientWrapper): ctx = context.current() user_client = nova.NovaClientWrapper() - reserved_group = user_client.nova.server_groups.create( - RESERVATION_PREFIX + ':' + reservation_id, - 'affinity' if inst_reservation['affinity'] else 'anti-affinity' - ) + flavor_args = { + 'reservation_id': reservation_id, + 'vcpus': inst_reservation['vcpus'], + 'memory': inst_reservation['memory_mb'], + 'disk': inst_reservation['disk_gb'] + } - reserved_flavor = self._create_flavor(reservation_id, - inst_reservation['vcpus'], - inst_reservation['memory_mb'], - inst_reservation['disk_gb'], - reserved_group.id) - - pool = nova.ReservationPool() pool_metadata = { RESERVATION_PREFIX: reservation_id, 'filter_tenant_id': ctx.project_id, - 'affinity_id': reserved_group.id } + + if inst_reservation['affinity'] is not None: + reserved_group = user_client.nova.server_groups.create( + RESERVATION_PREFIX + ':' + reservation_id, + 'affinity' if inst_reservation['affinity'] else 'anti-affinity' + ) + flavor_args['group_id'] = reserved_group.id + pool_metadata['affinity_id'] = reserved_group.id + else: + reserved_group = None + + reserved_flavor = self._create_flavor(**flavor_args) + + pool = nova.ReservationPool() agg = pool.create(name=reservation_id, metadata=pool_metadata) self.placement_client.create_reservation_class(reservation_id) @@ -483,9 +493,10 @@ class VirtualInstancePlugin(base.BasePlugin, nova.NovaClientWrapper): self.cleanup_resources(instance_reservation) raise mgr_exceptions.NovaClientError() + server_group_id = group.id if group is not None else None db_api.instance_reservation_update(instance_reservation['id'], {'flavor_id': flavor.id, - 'server_group_id': group.id, + 'server_group_id': server_group_id, 'aggregate_id': pool.id}) return instance_reservation['id'] diff --git a/blazar/tests/plugins/instances/test_instance_plugin.py b/blazar/tests/plugins/instances/test_instance_plugin.py index ed9001bf..6af28ac5 100644 --- a/blazar/tests/plugins/instances/test_instance_plugin.py +++ b/blazar/tests/plugins/instances/test_instance_plugin.py @@ -410,6 +410,61 @@ class TestVirtualInstancePlugin(tests.TestCase): expected_query = ['vcpus >= 2', 'memory_mb >= 2048', 'local_gb >= 100'] mock_host_get_query.assert_called_once_with(expected_query) + def test_pickup_host_with_anti_affinity(self): + def fake_get_reservation_by_host(host_id, start, end): + if host_id in ['host-1', 'host-3']: + return [ + {'id': '1', + 'resource_type': instances.RESOURCE_TYPE}, + {'id': '2', + 'resource_type': instances.RESOURCE_TYPE} + ] + else: + return [] + + plugin = instance_plugin.VirtualInstancePlugin() + + mock_host_allocation_get = self.patch( + db_api, 'host_allocation_get_all_by_values') + mock_host_allocation_get.return_value = [] + + mock_host_get_query = self.patch(db_api, + 'reservable_host_get_all_by_queries') + hosts_list = [self.generate_host_info('host-1', 8, 8192, 1000), + self.generate_host_info('host-2', 2, 2048, 500)] + mock_host_get_query.return_value = hosts_list + + mock_get_reservations = self.patch(db_utils, + 'get_reservations_by_host_id') + + mock_get_reservations.side_effect = fake_get_reservation_by_host + + mock_max_usages = self.patch(plugin, 'max_usages') + mock_max_usages.return_value = (0, 0, 0) + + mock_reservation_get = self.patch(db_api, 'reservation_get') + mock_reservation_get.return_value = { + 'status': 'pending' + } + + params = { + 'vcpus': 2, + 'memory_mb': 2048, + 'disk_gb': 100, + 'amount': 2, + 'affinity': False, + 'resource_properties': '', + 'start_date': datetime.datetime(2030, 1, 1, 8, 00), + 'end_date': datetime.datetime(2030, 1, 1, 12, 00) + } + + expected = {'added': ['host-1', 'host-2'], 'removed': []} + ret = plugin.pickup_hosts('reservation-id1', params) + + self.assertEqual(expected, ret) + expected_query = ['vcpus >= 2', 'memory_mb >= 2048', 'local_gb >= 100'] + mock_host_get_query.assert_called_once_with(expected_query) + @ddt.data('None', 'none', None) def test_pickup_host_with_no_affinity(self, value): def fake_get_reservation_by_host(host_id, start, end): diff --git a/doc/source/cli/instance-reservation.rst b/doc/source/cli/instance-reservation.rst index fa4b580e..c85982dd 100644 --- a/doc/source/cli/instance-reservation.rst +++ b/doc/source/cli/instance-reservation.rst @@ -166,3 +166,20 @@ Result: openstack server create --flavor db83d6fd-c69c-4259-92cf-012db2e55a58 --image --network .. + + +Affinity +-------- + +A lease can be created with the optional ``--affinity`` parameter. This +provides the following behavior: + +* ``affinity=True``: Instances will be deployed on the same host for this + reservation, by adding them to a server group with an affinity policy. + +* ``affinity=False``: Instances will be deployed on different hosts for this + reservation, by adding them to a server group with an anti-affinity policy. + +* ``affinity=None`` (default): Instances can be deployed on any host, + regardless of other instances in this reservation. No server group is + created. diff --git a/releasenotes/notes/update-affinity-behaviour-a03231e461bd2b21.yaml b/releasenotes/notes/update-affinity-behaviour-a03231e461bd2b21.yaml new file mode 100644 index 00000000..e87ae752 --- /dev/null +++ b/releasenotes/notes/update-affinity-behaviour-a03231e461bd2b21.yaml @@ -0,0 +1,14 @@ +--- +upgrade: + - | + Instance reservations will no longer create a server group when the + ``affinity`` parameter is not set. This means that instances can be + deployed on any host. To achieve the old behaviour, you must now explicity + set ``--affinity=false``. + `LP#2071832 `__ +fixes: + - | + Instance reservations will no longer create a server group when the + ``affinity`` parameter is not set. This means that instances can be + deployed on any host. + `LP#2071832 `__