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
This commit is contained in:
Matt Crees 2024-07-03 15:34:37 +01:00
parent 3999bf1fb7
commit c80d519998
4 changed files with 112 additions and 15 deletions

View File

@ -317,7 +317,8 @@ class VirtualInstancePlugin(base.BasePlugin, nova.NovaClientWrapper):
return {'added': added_host_ids, 'removed': removed_host_ids} 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 = { flavor_details = {
'flavorid': reservation_id, 'flavorid': reservation_id,
'name': RESERVATION_PREFIX + ":" + 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 reservation_rc = "resources:CUSTOM_RESERVATION_" + rsv_id_rc_format
extra_specs = { extra_specs = {
FLAVOR_EXTRA_SPEC: reservation_id, FLAVOR_EXTRA_SPEC: reservation_id,
"affinity_id": group_id,
reservation_rc: "1" reservation_rc: "1"
} }
if group_id is not None:
extra_specs["affinity_id"] = group_id
reserved_flavor.set_keys(extra_specs) reserved_flavor.set_keys(extra_specs)
return reserved_flavor return reserved_flavor
@ -346,23 +348,31 @@ class VirtualInstancePlugin(base.BasePlugin, nova.NovaClientWrapper):
ctx = context.current() ctx = context.current()
user_client = nova.NovaClientWrapper() user_client = nova.NovaClientWrapper()
reserved_group = user_client.nova.server_groups.create( flavor_args = {
RESERVATION_PREFIX + ':' + reservation_id, 'reservation_id': reservation_id,
'affinity' if inst_reservation['affinity'] else 'anti-affinity' '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 = { pool_metadata = {
RESERVATION_PREFIX: reservation_id, RESERVATION_PREFIX: reservation_id,
'filter_tenant_id': ctx.project_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) agg = pool.create(name=reservation_id, metadata=pool_metadata)
self.placement_client.create_reservation_class(reservation_id) self.placement_client.create_reservation_class(reservation_id)
@ -483,9 +493,10 @@ class VirtualInstancePlugin(base.BasePlugin, nova.NovaClientWrapper):
self.cleanup_resources(instance_reservation) self.cleanup_resources(instance_reservation)
raise mgr_exceptions.NovaClientError() raise mgr_exceptions.NovaClientError()
server_group_id = group.id if group is not None else None
db_api.instance_reservation_update(instance_reservation['id'], db_api.instance_reservation_update(instance_reservation['id'],
{'flavor_id': flavor.id, {'flavor_id': flavor.id,
'server_group_id': group.id, 'server_group_id': server_group_id,
'aggregate_id': pool.id}) 'aggregate_id': pool.id})
return instance_reservation['id'] return instance_reservation['id']

View File

@ -410,6 +410,61 @@ class TestVirtualInstancePlugin(tests.TestCase):
expected_query = ['vcpus >= 2', 'memory_mb >= 2048', 'local_gb >= 100'] expected_query = ['vcpus >= 2', 'memory_mb >= 2048', 'local_gb >= 100']
mock_host_get_query.assert_called_once_with(expected_query) 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) @ddt.data('None', 'none', None)
def test_pickup_host_with_no_affinity(self, value): def test_pickup_host_with_no_affinity(self, value):
def fake_get_reservation_by_host(host_id, start, end): def fake_get_reservation_by_host(host_id, start, end):

View File

@ -166,3 +166,20 @@ Result:
openstack server create --flavor db83d6fd-c69c-4259-92cf-012db2e55a58 --image <image> --network <network> <server-name> openstack server create --flavor db83d6fd-c69c-4259-92cf-012db2e55a58 --image <image> --network <network> <server-name>
.. ..
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.

View File

@ -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 <https://launchpad.net/bugs/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 <https://launchpad.net/bugs/2071832>`__