diff --git a/unit_tests/utilities/test_zaza_utilities_juju.py b/unit_tests/utilities/test_zaza_utilities_juju.py index 5848557..304120a 100644 --- a/unit_tests/utilities/test_zaza_utilities_juju.py +++ b/unit_tests/utilities/test_zaza_utilities_juju.py @@ -119,6 +119,18 @@ class TestJujuUtils(ut_utils.BaseTestCase): self.subordinate_application), [self.machine]) + def test_get_unit_name_from_host_name(self): + unit_mock1 = mock.MagicMock() + unit_mock1.data = {'machine-id': 12} + unit_mock1.entity_id = 'myapp/2' + unit_mock2 = mock.MagicMock() + unit_mock2.data = {'machine-id': 15} + unit_mock2.entity_id = 'myapp/5' + self.model.get_units.return_value = [unit_mock1, unit_mock2] + self.assertEqual( + juju_utils.get_unit_name_from_host_name('juju-model-12', 'myapp'), + 'myapp/2') + def test_get_machine_status(self): self.patch_object(juju_utils, "get_full_juju_status") self.get_full_juju_status.return_value = self.juju_status diff --git a/unit_tests/utilities/test_zaza_utilities_openstack.py b/unit_tests/utilities/test_zaza_utilities_openstack.py index 6b00fbc..6a9cbf0 100644 --- a/unit_tests/utilities/test_zaza_utilities_openstack.py +++ b/unit_tests/utilities/test_zaza_utilities_openstack.py @@ -811,3 +811,78 @@ class TestOpenStackUtils(ut_utils.BaseTestCase): ksclient, project_name, domain_name=domain_name), project_id) ksclient.domains.list.assert_called_once_with(name=domain_name) ksclient.projects.list.assert_called_once_with(domain=domain_id) + + def test_wait_for_server_migration(self): + openstack_utils.wait_for_server_migration.retry.stop = \ + tenacity.stop_after_attempt(1) + novaclient = mock.MagicMock() + servermock = mock.MagicMock() + setattr(servermock, 'OS-EXT-SRV-ATTR:host', 'newhypervisor') + servermock.status = 'ACTIVE' + novaclient.servers.find.return_value = servermock + # Implicit assertion that exception is not raised. + openstack_utils.wait_for_server_migration( + novaclient, + 'myvm', + 'org-hypervisor') + + def test_wait_for_server_migration_fail_no_host_change(self): + openstack_utils.wait_for_server_migration.retry.stop = \ + tenacity.stop_after_attempt(1) + novaclient = mock.MagicMock() + servermock = mock.MagicMock() + setattr(servermock, 'OS-EXT-SRV-ATTR:host', 'org-hypervisor') + servermock.status = 'ACTIVE' + novaclient.servers.find.return_value = servermock + with self.assertRaises(exceptions.NovaGuestMigrationFailed): + openstack_utils.wait_for_server_migration( + novaclient, + 'myvm', + 'org-hypervisor') + + def test_wait_for_server_migration_fail_not_active(self): + openstack_utils.wait_for_server_migration.retry.stop = \ + tenacity.stop_after_attempt(1) + novaclient = mock.MagicMock() + servermock = mock.MagicMock() + setattr(servermock, 'OS-EXT-SRV-ATTR:host', 'newhypervisor') + servermock.status = 'NOTACTIVE' + novaclient.servers.find.return_value = servermock + with self.assertRaises(exceptions.NovaGuestMigrationFailed): + openstack_utils.wait_for_server_migration( + novaclient, + 'myvm', + 'org-hypervisor') + + def test_enable_all_nova_services(self): + novaclient = mock.MagicMock() + svc_mock1 = mock.MagicMock() + svc_mock1.status = 'disabled' + svc_mock1.binary = 'nova-compute' + svc_mock1.host = 'juju-bb659c-zaza-ad7c662d7f1d-13' + svc_mock2 = mock.MagicMock() + svc_mock2.status = 'enabled' + svc_mock2.binary = 'nova-compute' + svc_mock2.host = 'juju-bb659c-zaza-ad7c662d7f1d-14' + svc_mock3 = mock.MagicMock() + svc_mock3.status = 'disabled' + svc_mock3.binary = 'nova-compute' + svc_mock3.host = 'juju-bb659c-zaza-ad7c662d7f1d-15' + novaclient.services.list.return_value = [ + svc_mock1, + svc_mock2, + svc_mock3] + openstack_utils.enable_all_nova_services(novaclient) + expected_calls = [ + mock.call('juju-bb659c-zaza-ad7c662d7f1d-13', 'nova-compute'), + mock.call('juju-bb659c-zaza-ad7c662d7f1d-15', 'nova-compute')] + novaclient.services.enable.assert_has_calls(expected_calls) + + def test_get_hypervisor_for_guest(self): + novaclient = mock.MagicMock() + servermock = mock.MagicMock() + setattr(servermock, 'OS-EXT-SRV-ATTR:host', 'newhypervisor') + novaclient.servers.find.return_value = servermock + self.assertEqual( + openstack_utils.get_hypervisor_for_guest(novaclient, 'vmname'), + 'newhypervisor') diff --git a/zaza/utilities/exceptions.py b/zaza/utilities/exceptions.py index 2886d83..dc52cca 100644 --- a/zaza/utilities/exceptions.py +++ b/zaza/utilities/exceptions.py @@ -160,3 +160,9 @@ class CephPoolNotFound(Exception): """Ceph pool not found.""" pass + + +class NovaGuestMigrationFailed(Exception): + """Nova guest migration failed.""" + + pass diff --git a/zaza/utilities/juju.py b/zaza/utilities/juju.py index 1a01a99..b4d758a 100644 --- a/zaza/utilities/juju.py +++ b/zaza/utilities/juju.py @@ -98,6 +98,23 @@ def get_machines_for_application(application): return machines +def get_unit_name_from_host_name(host_name, application): + """Return the juju unit name corresponding to a hostname. + + :param host_name: Host name to map to unit name. + :type host_name: string + :param application: Application name + :type application: string + """ + # Assume that a juju managed hostname always ends in the machine number. + machine_number = host_name.split('-')[-1] + unit_names = [ + u.entity_id + for u in model.get_units(application_name=application) + if int(u.data['machine-id']) == int(machine_number)] + return unit_names[0] + + def get_machine_status(machine, key=None): """Return the juju status for a machine. diff --git a/zaza/utilities/openstack.py b/zaza/utilities/openstack.py index 3d45cae..e0abff7 100644 --- a/zaza/utilities/openstack.py +++ b/zaza/utilities/openstack.py @@ -1889,3 +1889,59 @@ def neutron_bgp_speaker_appears_on_agent(neutron_client, agent_id): 'No BGP Speaker appeared on agent "{}"' ''.format(agent_id)) return result + + +@tenacity.retry(wait=tenacity.wait_exponential(multiplier=1, max=60), + reraise=True, stop=tenacity.stop_after_attempt(80)) +def wait_for_server_migration(nova_client, vm_name, original_hypervisor): + """Wait for guest to migrate to a different hypervisor. + + :param nova_client: Authenticated nova client + :type nova_client: novaclient.v2.client.Client + :param vm_name: Name of guest to monitor + :type vm_name: str + :param original_hypervisor: Name of hypervisor that was hosting guest + prior to migration. + :type original_hypervisor: str + :raises: exceptions.NovaGuestMigrationFailed + """ + server = nova_client.servers.find(name=vm_name) + current_hypervisor = getattr(server, 'OS-EXT-SRV-ATTR:host') + logging.info('{} is on {} in state {}'.format( + vm_name, + current_hypervisor, + server.status)) + if original_hypervisor == current_hypervisor or server.status != 'ACTIVE': + raise exceptions.NovaGuestMigrationFailed( + 'Migration of {} away from {} timed out of failed'.format( + vm_name, + original_hypervisor)) + else: + logging.info('SUCCESS {} has migrated to {}'.format( + vm_name, + current_hypervisor)) + + +def enable_all_nova_services(nova_client): + """Enable all nova services. + + :param nova_client: Authenticated nova client + :type nova_client: novaclient.v2.client.Client + """ + for svc in nova_client.services.list(): + if svc.status == 'disabled': + logging.info("Enabling {} on {}".format(svc.binary, svc.host)) + nova_client.services.enable(svc.host, svc.binary) + + +def get_hypervisor_for_guest(nova_client, guest_name): + """Return the name of the hypervisor hosting a guest. + + :param nova_client: Authenticated nova client + :type nova_client: novaclient.v2.client.Client + :param vm_name: Name of guest to loohup + :type vm_name: str + """ + logging.info('Finding hosting hypervisor') + server = nova_client.servers.find(name=guest_name) + return getattr(server, 'OS-EXT-SRV-ATTR:host')