From 5f2981e1aee669654ddced5c8d169d4747592ee9 Mon Sep 17 00:00:00 2001 From: Frode Nordahl Date: Thu, 13 Jan 2022 06:25:38 +0100 Subject: [PATCH] test_utils: Ensure diverse placement of test instances Use the Nova Server Groups API to influence placement of test instances. Fixes #641 --- zaza/openstack/charm_tests/test_utils.py | 23 +++++++++++++++--- zaza/openstack/configure/guest.py | 31 ++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/zaza/openstack/charm_tests/test_utils.py b/zaza/openstack/charm_tests/test_utils.py index b6a06fffa..6bfc6a8b9 100644 --- a/zaza/openstack/charm_tests/test_utils.py +++ b/zaza/openstack/charm_tests/test_utils.py @@ -733,7 +733,7 @@ def setUpClass(cls, application_name=None, model_alias=None): def resource_cleanup(self): """Remove test resources.""" try: - logging.info('Removing instances launched by test ({}*)' + logging.info('Removing resources created by test ({}*)' .format(self.RESOURCE_PREFIX)) for server in self.nova_client.servers.list(): if server.name.startswith(self.RESOURCE_PREFIX): @@ -741,6 +741,12 @@ def resource_cleanup(self): self.nova_client.servers, server.id, msg="server") + for server_group in self.nova_client.server_groups.list(): + if server_group.name.startswith(self.RESOURCE_PREFIX): + openstack_utils.delete_resource( + self.nova_client.server_groups, + server_group.id, + msg="server group") except AssertionError as e: # Resource failed to be removed within the expected time frame, # log this fact and carry on. @@ -752,7 +758,8 @@ def resource_cleanup(self): def launch_guest(self, guest_name, userdata=None, use_boot_volume=False, instance_key=None, flavor_name=None, - attach_to_external_network=False): + attach_to_external_network=False, + scheduler_hints=None): """Launch one guest to use in tests. Note that it is up to the caller to have set the RESOURCE_PREFIX class @@ -772,6 +779,9 @@ def launch_guest(self, guest_name, userdata=None, use_boot_volume=False, :param attach_to_external_network: Attach instance directly to external network. :type attach_to_external_network: bool + :param scheduler_hints: arbitrary key-value pairs specified by the + client to help boot an instance. + :type scheduler_hints: Optional[Dict[str,str]] :returns: Nova instance objects :rtype: Server """ @@ -801,7 +811,8 @@ def launch_guest(self, guest_name, userdata=None, use_boot_volume=False, use_boot_volume=use_boot_volume, userdata=userdata, flavor_name=flavor_name, - attach_to_external_network=attach_to_external_network) + attach_to_external_network=attach_to_external_network, + scheduler_hints=scheduler_hints) def launch_guests(self, userdata=None, attach_to_external_network=False, flavor_name=None): @@ -818,6 +829,9 @@ def launch_guests(self, userdata=None, attach_to_external_network=False, :returns: List of launched Nova instance objects :rtype: List[Server] """ + server_group = configure_guest.create_server_group( + self.RESOURCE_PREFIX, policy='anti-affinity') + launched_instances = [] for guest_number in range(1, 2+1): launched_instances.append( @@ -825,7 +839,8 @@ def launch_guests(self, userdata=None, attach_to_external_network=False, guest_name='ins-{}'.format(guest_number), userdata=userdata, attach_to_external_network=attach_to_external_network, - flavor_name=flavor_name)) + flavor_name=flavor_name, + scheduler_hints={'group': server_group.id})) return launched_instances def retrieve_guest(self, guest_name): diff --git a/zaza/openstack/configure/guest.py b/zaza/openstack/configure/guest.py index d5034b36d..65bfd735c 100644 --- a/zaza/openstack/configure/guest.py +++ b/zaza/openstack/configure/guest.py @@ -50,10 +50,33 @@ 'bootstring': 'finished at'}} +def create_server_group(name, policy=None): + """Create a server group for influencing instance placement. + + :param name: Name of server group. + :type name: str + :param policy: Policy for group, one of ('affinity', 'anti-affinity', + 'soft-affinity', 'soft-anti-affinity'). Default: 'affinity' + :type policy: Optional[str] + :param rules + :returns: ServerGroup resource. + :rtype: novaclient.v2.server_groups.ServerGroup + :raises: ValueError + """ + if policy and policy not in ('affinity', 'anti-affinity', 'soft-affinity', + 'soft-anti-affinity'): + raise ValueError + + keystone_session = openstack_utils.get_overcloud_keystone_session() + nova_client = openstack_utils.get_nova_session_client(keystone_session) + return nova_client.server_groups.create(name, policy) + + def launch_instance(instance_key, use_boot_volume=False, vm_name=None, private_network_name=None, image_name=None, flavor_name=None, external_network_name=None, meta=None, - userdata=None, attach_to_external_network=False): + userdata=None, attach_to_external_network=False, + scheduler_hints=None): """Launch an instance. :param instance_key: Key to collect associated config data with. @@ -79,6 +102,9 @@ def launch_instance(instance_key, use_boot_volume=False, vm_name=None, :param attach_to_external_network: Attach instance directly to external network. :type attach_to_external_network: bool + :param scheduler_hints: arbitrary key-value pairs specified by the client + to help boot an instance. + :type scheduler_hints: Optional[Dict[str,str]] :returns: the created instance :rtype: novaclient.Server """ @@ -131,7 +157,8 @@ def launch_instance(instance_key, use_boot_volume=False, vm_name=None, key_name=nova_utils.KEYPAIR_NAME, meta=meta, nics=nics, - userdata=userdata) + userdata=userdata, + scheduler_hints=scheduler_hints) # Test Instance is ready. logging.info('Checking instance is active')