diff --git a/openstack/cloud/_tasks.py b/openstack/cloud/_tasks.py index 3ad6b8592..a41502f4f 100644 --- a/openstack/cloud/_tasks.py +++ b/openstack/cloud/_tasks.py @@ -17,81 +17,94 @@ from openstack import task_manager -class MachineCreate(task_manager.Task): - def main(self, client): - return client.ironic_client.node.create(**self.args) +class IronicTask(task_manager.Task): + + def __init__(self, client, **kwargs): + super(IronicTask, self).__init__(**kwargs) + self.client = client -class MachineDelete(task_manager.Task): - def main(self, client): - return client.ironic_client.node.delete(**self.args) +class MachineCreate(IronicTask): + def main(self): + return self.client.ironic_client.node.create(*self.args, **self.kwargs) -class MachinePatch(task_manager.Task): - def main(self, client): - return client.ironic_client.node.update(**self.args) +class MachineDelete(IronicTask): + def main(self): + return self.client.ironic_client.node.delete(*self.args, **self.kwargs) -class MachinePortGet(task_manager.Task): - def main(self, client): - return client.ironic_client.port.get(**self.args) +class MachinePatch(IronicTask): + def main(self): + return self.client.ironic_client.node.update(*self.args, **self.kwargs) -class MachinePortGetByAddress(task_manager.Task): - def main(self, client): - return client.ironic_client.port.get_by_address(**self.args) +class MachinePortGet(IronicTask): + def main(self): + return self.client.ironic_client.port.get(*self.args, **self.kwargs) -class MachinePortCreate(task_manager.Task): - def main(self, client): - return client.ironic_client.port.create(**self.args) +class MachinePortGetByAddress(IronicTask): + def main(self): + return self.client.ironic_client.port.get_by_address( + *self.args, **self.kwargs) -class MachinePortDelete(task_manager.Task): - def main(self, client): - return client.ironic_client.port.delete(**self.args) +class MachinePortCreate(IronicTask): + def main(self): + return self.client.ironic_client.port.create(*self.args, **self.kwargs) -class MachinePortList(task_manager.Task): - def main(self, client): - return client.ironic_client.port.list() +class MachinePortDelete(IronicTask): + def main(self): + return self.client.ironic_client.port.delete(*self.args, **self.kwargs) -class MachineNodeGet(task_manager.Task): - def main(self, client): - return client.ironic_client.node.get(**self.args) +class MachinePortList(IronicTask): + def main(self): + return self.client.ironic_client.port.list() -class MachineNodeList(task_manager.Task): - def main(self, client): - return client.ironic_client.node.list(**self.args) +class MachineNodeGet(IronicTask): + def main(self): + return self.client.ironic_client.node.get(*self.args, **self.kwargs) -class MachineNodePortList(task_manager.Task): - def main(self, client): - return client.ironic_client.node.list_ports(**self.args) +class MachineNodeList(IronicTask): + def main(self): + return self.client.ironic_client.node.list(*self.args, **self.kwargs) -class MachineNodeUpdate(task_manager.Task): - def main(self, client): - return client.ironic_client.node.update(**self.args) +class MachineNodePortList(IronicTask): + def main(self): + return self.client.ironic_client.node.list_ports( + *self.args, **self.kwargs) -class MachineNodeValidate(task_manager.Task): - def main(self, client): - return client.ironic_client.node.validate(**self.args) +class MachineNodeUpdate(IronicTask): + def main(self): + return self.client.ironic_client.node.update(*self.args, **self.kwargs) -class MachineSetMaintenance(task_manager.Task): - def main(self, client): - return client.ironic_client.node.set_maintenance(**self.args) +class MachineNodeValidate(IronicTask): + def main(self): + return self.client.ironic_client.node.validate( + *self.args, **self.kwargs) -class MachineSetPower(task_manager.Task): - def main(self, client): - return client.ironic_client.node.set_power_state(**self.args) +class MachineSetMaintenance(IronicTask): + def main(self): + return self.client.ironic_client.node.set_maintenance( + *self.args, **self.kwargs) -class MachineSetProvision(task_manager.Task): - def main(self, client): - return client.ironic_client.node.set_provision_state(**self.args) +class MachineSetPower(IronicTask): + def main(self): + return self.client.ironic_client.node.set_power_state( + *self.args, **self.kwargs) + + +class MachineSetProvision(IronicTask): + def main(self): + return self.client.ironic_client.node.set_provision_state( + *self.args, **self.kwargs) diff --git a/openstack/cloud/operatorcloud.py b/openstack/cloud/operatorcloud.py index 283ad802e..beba3a598 100644 --- a/openstack/cloud/operatorcloud.py +++ b/openstack/cloud/operatorcloud.py @@ -16,6 +16,7 @@ import jsonpatch import munch from openstack.cloud.exc import * # noqa +from openstack.cloud import meta from openstack.cloud import openstackcloud from openstack.cloud import _tasks from openstack.cloud import _utils @@ -35,27 +36,28 @@ class OperatorCloud(openstackcloud.OpenStackCloud): def list_nics(self): with _utils.shade_exceptions("Error fetching machine port list"): - return self.manager.submit_task(_tasks.MachinePortList()) + return meta.obj_list_to_munch( + self.manager.submit_task(_tasks.MachinePortList(self))) def list_nics_for_machine(self, uuid): with _utils.shade_exceptions( "Error fetching port list for node {node_id}".format( node_id=uuid)): - return self.manager.submit_task( - _tasks.MachineNodePortList(node_id=uuid)) + return meta.obj_list_to_munch(self.manager.submit_task( + _tasks.MachineNodePortList(self, node_id=uuid))) def get_nic_by_mac(self, mac): """Get Machine by MAC address""" # TODO(shade) Finish porting ironic to REST/sdk # try: # return self.manager.submit_task( - # _tasks.MachineNodePortGet(port_id=mac)) + # _tasks.MachineNodePortGet(self, port_id=mac)) # except ironic_exceptions.ClientException: # return None def list_machines(self): return self._normalize_machines( - self.manager.submit_task(_tasks.MachineNodeList())) + self.manager.submit_task(_tasks.MachineNodeList(self))) def get_machine(self, name_or_id): """Get Machine by name or uuid @@ -211,7 +213,7 @@ class OperatorCloud(openstackcloud.OpenStackCloud): nic = self.manager.submit_task( _tasks.MachinePortCreate(address=row['mac'], node_uuid=machine['uuid'])) - created_nics.append(nic.uuid) + created_nics.append(nic['uuid']) except Exception as e: self.log.debug("ironic NIC registration failed", exc_info=True) @@ -277,7 +279,10 @@ class OperatorCloud(openstackcloud.OpenStackCloud): machine = self.get_machine(machine['uuid']) if (machine['reservation'] is None and machine['provision_state'] is not 'enroll'): - + # NOTE(TheJulia): In this case, the node has + # has moved on from the previous state and is + # likely not being verified, as no lock is + # present on the node. self.node_set_provision_state( machine['uuid'], 'provide') machine = self.get_machine(machine['uuid']) @@ -292,8 +297,10 @@ class OperatorCloud(openstackcloud.OpenStackCloud): raise OpenStackCloudException( "Machine encountered a failure: %s" % machine['last_error']) - - return machine + if not isinstance(machine, str): + return self._normalize_machine(machine) + else: + return machine def unregister_machine(self, nics, uuid, wait=False, timeout=600): """Unregister Baremetal from Ironic diff --git a/openstack/tests/fakes.py b/openstack/tests/fakes.py index cc35723f5..a0cfb74be 100644 --- a/openstack/tests/fakes.py +++ b/openstack/tests/fakes.py @@ -248,6 +248,16 @@ def make_fake_machine(machine_name, machine_id=None): id=machine_id, name=machine_name)) +def make_fake_port(address, node_id=None, port_id=None): + if not node_id: + node_id = uuid.uuid4().hex + if not port_id: + port_id = uuid.uuid4().hex + return meta.obj_to_munch(FakeMachinePort( + id=port_id, + address=address, + node_id=node_id)) + class FakeFloatingIP(object): def __init__(self, id, pool, ip, fixed_ip, instance_id): @@ -346,7 +356,7 @@ class FakeVolumeSnapshot(object): class FakeMachine(object): def __init__(self, id, name=None, driver=None, driver_info=None, chassis_uuid=None, instance_info=None, instance_uuid=None, - properties=None): + properties=None, reservation=None, last_error=None): self.uuid = id self.name = name self.driver = driver @@ -355,6 +365,8 @@ class FakeMachine(object): self.instance_info = instance_info self.instance_uuid = instance_uuid self.properties = properties + self.reservation = reservation + self.last_error = last_error class FakeMachinePort(object): diff --git a/openstack/tests/unit/cloud/test_baremetal_node.py b/openstack/tests/unit/cloud/test_baremetal_node.py index 837b51917..cd76c5a5c 100644 --- a/openstack/tests/unit/cloud/test_baremetal_node.py +++ b/openstack/tests/unit/cloud/test_baremetal_node.py @@ -34,6 +34,12 @@ class TestBaremetalNode(base.IronicTestCase): self.skipTest("Ironic operations not supported yet") self.fake_baremetal_node = fakes.make_fake_machine( self.name, self.uuid) + # TODO(TheJulia): Some tests below have fake ports, + # since they are required in some processes. Lets refactor + # them at some point to use self.fake_baremetal_port. + self.fake_baremetal_port = fakes.make_fake_port( + '00:01:02:03:04:05', + node_id=self.uuid) def test_list_machines(self): fake_baremetal_two = fakes.make_fake_machine('two', str(uuid.uuid4())) @@ -822,6 +828,542 @@ class TestBaremetalNode(base.IronicTestCase): self.assertIsNone(return_value) self.assert_calls() + def test_register_machine(self): + mac_address = '00:01:02:03:04:05' + nics = [{'mac': mac_address}] + node_uuid = self.fake_baremetal_node['uuid'] + # TODO(TheJulia): There is a lot of duplication + # in testing creation. Surely this hsould be a helper + # or something. We should fix this, after we have + # ironicclient removed, as in the mean time visibility + # will be helpful. + node_to_post = { + 'chassis_uuid': None, + 'driver': None, + 'driver_info': None, + 'name': self.fake_baremetal_node['name'], + 'properties': None, + 'uuid': node_uuid} + self.fake_baremetal_node['provision_state'] = 'available' + if 'provision_state' in node_to_post: + node_to_post.pop('provision_state') + self.register_uris([ + dict( + method='POST', + uri=self.get_mock_url( + resource='nodes'), + json=self.fake_baremetal_node, + validate=dict(json=node_to_post)), + dict( + method='POST', + uri=self.get_mock_url( + resource='ports'), + validate=dict(json={'address': mac_address, + 'node_uuid': node_uuid}), + json=self.fake_baremetal_port), + ]) + return_value = self.op_cloud.register_machine(nics, **node_to_post) + + self.assertDictEqual(self.fake_baremetal_node, return_value) + self.assert_calls() + + # TODO(TheJulia): After we remove ironicclient, + # we need to de-duplicate these tests. Possibly + # a dedicated class, although we should do it then + # as we may find differences that need to be accounted + # for newer microversions. + def test_register_machine_enroll(self): + mac_address = '00:01:02:03:04:05' + nics = [{'mac': mac_address}] + node_uuid = self.fake_baremetal_node['uuid'] + node_to_post = { + 'chassis_uuid': None, + 'driver': None, + 'driver_info': None, + 'name': self.fake_baremetal_node['name'], + 'properties': None, + 'uuid': node_uuid} + self.fake_baremetal_node['provision_state'] = 'enroll' + manageable_node = self.fake_baremetal_node.copy() + manageable_node['provision_state'] = 'manageable' + available_node = self.fake_baremetal_node.copy() + available_node['provision_state'] = 'available' + self.register_uris([ + dict( + method='POST', + uri=self.get_mock_url( + resource='nodes'), + validate=dict(json=node_to_post), + json=self.fake_baremetal_node), + dict( + method='POST', + uri=self.get_mock_url( + resource='ports'), + validate=dict(json={'address': mac_address, + 'node_uuid': node_uuid}), + json=self.fake_baremetal_port), + dict( + method='PUT', + uri=self.get_mock_url( + resource='nodes', + append=[self.fake_baremetal_node['uuid'], + 'states', 'provision']), + validate=dict(json={'target': 'manage'})), + dict( + method='GET', + uri=self.get_mock_url( + resource='nodes', + append=[self.fake_baremetal_node['uuid']]), + json=manageable_node), + dict( + method='GET', + uri=self.get_mock_url( + resource='nodes', + append=[self.fake_baremetal_node['uuid']]), + json=manageable_node), + dict( + method='PUT', + uri=self.get_mock_url( + resource='nodes', + append=[self.fake_baremetal_node['uuid'], + 'states', 'provision']), + validate=dict(json={'target': 'provide'})), + dict( + method='GET', + uri=self.get_mock_url( + resource='nodes', + append=[self.fake_baremetal_node['uuid']]), + json=available_node), + dict( + method='GET', + uri=self.get_mock_url( + resource='nodes', + append=[self.fake_baremetal_node['uuid']]), + json=available_node), + ]) + # NOTE(When we migrate to a newer microversion, this test + # may require revision. It was written for microversion + # ?1.13?, which accidently got reverted to 1.6 at one + # point during code being refactored soon after the + # change landed. Presently, with the lock at 1.6, + # this code is never used in the current code path. + return_value = self.op_cloud.register_machine(nics, **node_to_post) + + self.assertDictEqual(available_node, return_value) + self.assert_calls() + + def test_register_machine_enroll_wait(self): + mac_address = self.fake_baremetal_port + nics = [{'mac': mac_address}] + node_uuid = self.fake_baremetal_node['uuid'] + node_to_post = { + 'chassis_uuid': None, + 'driver': None, + 'driver_info': None, + 'name': self.fake_baremetal_node['name'], + 'properties': None, + 'uuid': node_uuid} + self.fake_baremetal_node['provision_state'] = 'enroll' + manageable_node = self.fake_baremetal_node.copy() + manageable_node['provision_state'] = 'manageable' + available_node = self.fake_baremetal_node.copy() + available_node['provision_state'] = 'available' + self.register_uris([ + dict( + method='POST', + uri=self.get_mock_url( + resource='nodes'), + validate=dict(json=node_to_post), + json=self.fake_baremetal_node), + dict( + method='POST', + uri=self.get_mock_url( + resource='ports'), + validate=dict(json={'address': mac_address, + 'node_uuid': node_uuid}), + json=self.fake_baremetal_port), + dict( + method='GET', + uri=self.get_mock_url( + resource='nodes', + append=[self.fake_baremetal_node['uuid']]), + json=self.fake_baremetal_node), + dict( + method='PUT', + uri=self.get_mock_url( + resource='nodes', + append=[self.fake_baremetal_node['uuid'], + 'states', 'provision']), + validate=dict(json={'target': 'manage'})), + dict( + method='GET', + uri=self.get_mock_url( + resource='nodes', + append=[self.fake_baremetal_node['uuid']]), + json=self.fake_baremetal_node), + dict( + method='GET', + uri=self.get_mock_url( + resource='nodes', + append=[self.fake_baremetal_node['uuid']]), + json=manageable_node), + dict( + method='PUT', + uri=self.get_mock_url( + resource='nodes', + append=[self.fake_baremetal_node['uuid'], + 'states', 'provision']), + validate=dict(json={'target': 'provide'})), + dict( + method='GET', + uri=self.get_mock_url( + resource='nodes', + append=[self.fake_baremetal_node['uuid']]), + json=available_node), + dict( + method='GET', + uri=self.get_mock_url( + resource='nodes', + append=[self.fake_baremetal_node['uuid']]), + json=available_node), + ]) + return_value = self.op_cloud.register_machine(nics, wait=True, + **node_to_post) + + self.assertDictEqual(available_node, return_value) + self.assert_calls() + + def test_register_machine_enroll_failure(self): + mac_address = '00:01:02:03:04:05' + nics = [{'mac': mac_address}] + node_uuid = self.fake_baremetal_node['uuid'] + node_to_post = { + 'chassis_uuid': None, + 'driver': None, + 'driver_info': None, + 'name': self.fake_baremetal_node['name'], + 'properties': None, + 'uuid': node_uuid} + self.fake_baremetal_node['provision_state'] = 'enroll' + failed_node = self.fake_baremetal_node.copy() + failed_node['reservation'] = 'conductor0' + failed_node['provision_state'] = 'verifying' + failed_node['last_error'] = 'kaboom!' + self.register_uris([ + dict( + method='POST', + uri=self.get_mock_url( + resource='nodes'), + json=self.fake_baremetal_node, + validate=dict(json=node_to_post)), + dict( + method='POST', + uri=self.get_mock_url( + resource='ports'), + validate=dict(json={'address': mac_address, + 'node_uuid': node_uuid}), + json=self.fake_baremetal_port), + dict( + method='PUT', + uri=self.get_mock_url( + resource='nodes', + append=[self.fake_baremetal_node['uuid'], + 'states', 'provision']), + validate=dict(json={'target': 'manage'})), + dict( + method='GET', + uri=self.get_mock_url( + resource='nodes', + append=[self.fake_baremetal_node['uuid']]), + json=failed_node), + dict( + method='GET', + uri=self.get_mock_url( + resource='nodes', + append=[self.fake_baremetal_node['uuid']]), + json=failed_node), + ]) + + self.assertRaises( + exc.OpenStackCloudException, + self.op_cloud.register_machine, + nics, + **node_to_post) + self.assert_calls() + + def test_register_machine_enroll_timeout(self): + mac_address = '00:01:02:03:04:05' + nics = [{'mac': mac_address}] + node_uuid = self.fake_baremetal_node['uuid'] + node_to_post = { + 'chassis_uuid': None, + 'driver': None, + 'driver_info': None, + 'name': self.fake_baremetal_node['name'], + 'properties': None, + 'uuid': node_uuid} + self.fake_baremetal_node['provision_state'] = 'enroll' + busy_node = self.fake_baremetal_node.copy() + busy_node['reservation'] = 'conductor0' + busy_node['provision_state'] = 'verifying' + self.register_uris([ + dict( + method='POST', + uri=self.get_mock_url( + resource='nodes'), + json=self.fake_baremetal_node, + validate=dict(json=node_to_post)), + dict( + method='POST', + uri=self.get_mock_url( + resource='ports'), + validate=dict(json={'address': mac_address, + 'node_uuid': node_uuid}), + json=self.fake_baremetal_port), + dict( + method='PUT', + uri=self.get_mock_url( + resource='nodes', + append=[self.fake_baremetal_node['uuid'], + 'states', 'provision']), + validate=dict(json={'target': 'manage'})), + dict( + method='GET', + uri=self.get_mock_url( + resource='nodes', + append=[self.fake_baremetal_node['uuid']]), + json=self.fake_baremetal_node), + dict( + method='GET', + uri=self.get_mock_url( + resource='nodes', + append=[self.fake_baremetal_node['uuid']]), + json=busy_node), + ]) + # NOTE(TheJulia): This test shortcircuits the timeout loop + # such that it executes only once. The very last returned + # state to the API is essentially a busy state that we + # want to block on until it has cleared. + self.assertRaises( + exc.OpenStackCloudException, + self.op_cloud.register_machine, + nics, + timeout=0.001, + lock_timeout=0.001, + **node_to_post) + self.assert_calls() + + def test_register_machine_enroll_timeout_wait(self): + mac_address = '00:01:02:03:04:05' + nics = [{'mac': mac_address}] + node_uuid = self.fake_baremetal_node['uuid'] + node_to_post = { + 'chassis_uuid': None, + 'driver': None, + 'driver_info': None, + 'name': self.fake_baremetal_node['name'], + 'properties': None, + 'uuid': node_uuid} + self.fake_baremetal_node['provision_state'] = 'enroll' + self.register_uris([ + dict( + method='POST', + uri=self.get_mock_url( + resource='nodes'), + json=self.fake_baremetal_node, + validate=dict(json=node_to_post)), + dict( + method='POST', + uri=self.get_mock_url( + resource='ports'), + validate=dict(json={'address': mac_address, + 'node_uuid': node_uuid}), + json=self.fake_baremetal_port), + dict( + method='GET', + uri=self.get_mock_url( + resource='nodes', + append=[self.fake_baremetal_node['uuid']]), + json=self.fake_baremetal_node), + dict( + method='PUT', + uri=self.get_mock_url( + resource='nodes', + append=[self.fake_baremetal_node['uuid'], + 'states', 'provision']), + validate=dict(json={'target': 'manage'})), + dict( + method='GET', + uri=self.get_mock_url( + resource='nodes', + append=[self.fake_baremetal_node['uuid']]), + json=self.fake_baremetal_node), + ]) + self.assertRaises( + exc.OpenStackCloudException, + self.op_cloud.register_machine, + nics, + wait=True, + timeout=0.001, + **node_to_post) + self.assert_calls() + + def test_register_machine_port_create_failed(self): + mac_address = '00:01:02:03:04:05' + nics = [{'mac': mac_address}] + node_uuid = self.fake_baremetal_node['uuid'] + node_to_post = { + 'chassis_uuid': None, + 'driver': None, + 'driver_info': None, + 'name': self.fake_baremetal_node['name'], + 'properties': None, + 'uuid': node_uuid} + self.fake_baremetal_node['provision_state'] = 'available' + self.register_uris([ + dict( + method='POST', + uri=self.get_mock_url( + resource='nodes'), + json=self.fake_baremetal_node, + validate=dict(json=node_to_post)), + dict( + method='POST', + uri=self.get_mock_url( + resource='ports'), + status_code=400, + json={'error': 'invalid'}, + validate=dict(json={'address': mac_address, + 'node_uuid': node_uuid})), + dict( + method='DELETE', + uri=self.get_mock_url( + resource='nodes', + append=[self.fake_baremetal_node['uuid']])), + ]) + self.assertRaises(exc.OpenStackCloudException, + self.op_cloud.register_machine, + nics, **node_to_post) + + self.assert_calls() + + def test_unregister_machine(self): + mac_address = self.fake_baremetal_port['address'] + nics = [{'mac': mac_address}] + port_uuid = self.fake_baremetal_port['uuid'] + # NOTE(TheJulia): The two values below should be the same. + port_node_uuid = self.fake_baremetal_port['node_uuid'] + port_url_address = 'detail?address=%s' % mac_address + self.fake_baremetal_node['provision_state'] = 'available' + self.register_uris([ + dict( + method='GET', + uri=self.get_mock_url( + resource='nodes', + append=[self.fake_baremetal_node['uuid']]), + json=self.fake_baremetal_node), + dict( + method='GET', + uri=self.get_mock_url( + resource='ports', + append=[port_url_address]), + json={'ports': [{'address': mac_address, + 'node_uuid': port_node_uuid, + 'uuid': port_uuid}]}), + dict( + method='DELETE', + uri=self.get_mock_url( + resource='ports', + append=[self.fake_baremetal_port['uuid']])), + dict( + method='DELETE', + uri=self.get_mock_url( + resource='nodes', + append=[self.fake_baremetal_node['uuid']])), + ]) + + self.op_cloud.unregister_machine(nics, + self.fake_baremetal_node['uuid']) + + self.assert_calls() + + def test_unregister_machine_timeout(self): + mac_address = self.fake_baremetal_port['address'] + nics = [{'mac': mac_address}] + port_uuid = self.fake_baremetal_port['uuid'] + port_node_uuid = self.fake_baremetal_port['node_uuid'] + port_url_address = 'detail?address=%s' % mac_address + self.fake_baremetal_node['provision_state'] = 'available' + self.register_uris([ + dict( + method='GET', + uri=self.get_mock_url( + resource='nodes', + append=[self.fake_baremetal_node['uuid']]), + json=self.fake_baremetal_node), + dict( + method='GET', + uri=self.get_mock_url( + resource='ports', + append=[port_url_address]), + json={'ports': [{'address': mac_address, + 'node_uuid': port_node_uuid, + 'uuid': port_uuid}]}), + dict( + method='DELETE', + uri=self.get_mock_url( + resource='ports', + append=[self.fake_baremetal_port['uuid']])), + dict( + method='DELETE', + uri=self.get_mock_url( + resource='nodes', + append=[self.fake_baremetal_node['uuid']])), + dict( + method='GET', + uri=self.get_mock_url( + resource='nodes', + append=[self.fake_baremetal_node['uuid']]), + json=self.fake_baremetal_node), + ]) + self.assertRaises( + exc.OpenStackCloudException, + self.op_cloud.unregister_machine, + nics, + self.fake_baremetal_node['uuid'], + wait=True, + timeout=0.001) + + self.assert_calls() + + def test_unregister_machine_unavailable(self): + # This is a list of invalid states that the method + # should fail on. + invalid_states = ['active', 'cleaning', 'clean wait', 'clean failed'] + mac_address = self.fake_baremetal_port['address'] + nics = [{'mac': mac_address}] + url_list = [] + for state in invalid_states: + self.fake_baremetal_node['provision_state'] = state + url_list.append( + dict( + method='GET', + uri=self.get_mock_url( + resource='nodes', + append=[self.fake_baremetal_node['uuid']]), + json=self.fake_baremetal_node)) + + self.register_uris(url_list) + + for state in invalid_states: + self.assertRaises( + exc.OpenStackCloudException, + self.op_cloud.unregister_machine, + nics, + self.fake_baremetal_node['uuid']) + + self.assert_calls() + def test_update_machine_patch_no_action(self): self.register_uris([dict( method='GET', diff --git a/openstack/tests/unit/cloud/test_image.py b/openstack/tests/unit/cloud/test_image.py index 5df8ac1ac..5d4b8a4b7 100644 --- a/openstack/tests/unit/cloud/test_image.py +++ b/openstack/tests/unit/cloud/test_image.py @@ -374,8 +374,8 @@ class TestImage(BaseTestImage): 'X-Trans-Id': 'txbbb825960a3243b49a36f-005a0dadaedfw1', 'Content-Length': '1290170880', 'Last-Modified': 'Tue, 14 Apr 2015 18:29:01 GMT', - 'X-Object-Meta-X-Shade-Sha256': fakes.NO_SHA256, - 'X-Object-Meta-X-Shade-Md5': fakes.NO_MD5, + 'X-Object-Meta-X-Sdk-Sha256': fakes.NO_SHA256, + 'X-Object-Meta-X-Sdk-Md5': fakes.NO_MD5, 'Date': 'Thu, 16 Nov 2017 15:24:30 GMT', 'Accept-Ranges': 'bytes', 'Content-Type': 'application/octet-stream', diff --git a/openstack/tests/unit/cloud/test_operator.py b/openstack/tests/unit/cloud/test_operator.py new file mode 100644 index 000000000..068e735d8 --- /dev/null +++ b/openstack/tests/unit/cloud/test_operator.py @@ -0,0 +1,170 @@ +# 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. + +import mock +import munch +import testtools + +import openstack +from openstack.cloud import exc +from openstack.cloud import meta +from openstack.config import cloud_config +from openstack.tests import fakes +from openstack.tests.unit import base + + +class TestOperatorCloud(base.RequestsMockTestCase): + + def setUp(self): + super(TestOperatorCloud, self).setUp() + + def test_operator_cloud(self): + self.assertIsInstance(self.op_cloud, openstack.OperatorCloud) + + @mock.patch.object(openstack.OperatorCloud, 'ironic_client') + def test_list_nics(self, mock_client): + port1 = fakes.FakeMachinePort(1, "aa:bb:cc:dd", "node1") + port2 = fakes.FakeMachinePort(2, "dd:cc:bb:aa", "node2") + port_list = [port1, port2] + port_dict_list = meta.obj_list_to_munch(port_list) + + mock_client.port.list.return_value = port_list + nics = self.op_cloud.list_nics() + + self.assertTrue(mock_client.port.list.called) + self.assertEqual(port_dict_list, nics) + + @mock.patch.object(openstack.OperatorCloud, 'ironic_client') + def test_list_nics_failure(self, mock_client): + mock_client.port.list.side_effect = Exception() + self.assertRaises(exc.OpenStackCloudException, + self.op_cloud.list_nics) + + @mock.patch.object(openstack.OperatorCloud, 'ironic_client') + def test_list_nics_for_machine(self, mock_client): + mock_client.node.list_ports.return_value = [] + self.op_cloud.list_nics_for_machine("123") + mock_client.node.list_ports.assert_called_with(node_id="123") + + @mock.patch.object(openstack.OperatorCloud, 'ironic_client') + def test_list_nics_for_machine_failure(self, mock_client): + mock_client.node.list_ports.side_effect = Exception() + self.assertRaises(exc.OpenStackCloudException, + self.op_cloud.list_nics_for_machine, None) + + @mock.patch.object(openstack.OpenStackCloud, '_image_client') + def test_get_image_name(self, mock_client): + + fake_image = munch.Munch( + id='22', + name='22 name', + status='success') + mock_client.get.return_value = [fake_image] + self.assertEqual('22 name', self.op_cloud.get_image_name('22')) + self.assertEqual('22 name', self.op_cloud.get_image_name('22 name')) + + @mock.patch.object(openstack.OpenStackCloud, '_image_client') + def test_get_image_id(self, mock_client): + + fake_image = munch.Munch( + id='22', + name='22 name', + status='success') + mock_client.get.return_value = [fake_image] + self.assertEqual('22', self.op_cloud.get_image_id('22')) + self.assertEqual('22', self.op_cloud.get_image_id('22 name')) + + @mock.patch.object(cloud_config.CloudConfig, 'get_endpoint') + def test_get_session_endpoint_provided(self, fake_get_endpoint): + fake_get_endpoint.return_value = 'http://fake.url' + self.assertEqual( + 'http://fake.url', self.op_cloud.get_session_endpoint('image')) + + @mock.patch.object(cloud_config.CloudConfig, 'get_session') + def test_get_session_endpoint_session(self, get_session_mock): + session_mock = mock.Mock() + session_mock.get_endpoint.return_value = 'http://fake.url' + get_session_mock.return_value = session_mock + self.assertEqual( + 'http://fake.url', self.op_cloud.get_session_endpoint('image')) + + @mock.patch.object(cloud_config.CloudConfig, 'get_session') + def test_get_session_endpoint_exception(self, get_session_mock): + class FakeException(Exception): + pass + + def side_effect(*args, **kwargs): + raise FakeException("No service") + session_mock = mock.Mock() + session_mock.get_endpoint.side_effect = side_effect + get_session_mock.return_value = session_mock + self.op_cloud.name = 'testcloud' + self.op_cloud.region_name = 'testregion' + with testtools.ExpectedException( + exc.OpenStackCloudException, + "Error getting image endpoint on testcloud:testregion:" + " No service"): + self.op_cloud.get_session_endpoint("image") + + @mock.patch.object(cloud_config.CloudConfig, 'get_session') + def test_get_session_endpoint_unavailable(self, get_session_mock): + session_mock = mock.Mock() + session_mock.get_endpoint.return_value = None + get_session_mock.return_value = session_mock + image_endpoint = self.op_cloud.get_session_endpoint("image") + self.assertIsNone(image_endpoint) + + @mock.patch.object(cloud_config.CloudConfig, 'get_session') + def test_get_session_endpoint_identity(self, get_session_mock): + session_mock = mock.Mock() + get_session_mock.return_value = session_mock + self.op_cloud.get_session_endpoint('identity') + kwargs = dict( + interface='public', region_name='RegionOne', + service_name=None, service_type='identity') + + session_mock.get_endpoint.assert_called_with(**kwargs) + + @mock.patch.object(cloud_config.CloudConfig, 'get_session') + def test_has_service_no(self, get_session_mock): + session_mock = mock.Mock() + session_mock.get_endpoint.return_value = None + get_session_mock.return_value = session_mock + self.assertFalse(self.op_cloud.has_service("image")) + + @mock.patch.object(cloud_config.CloudConfig, 'get_session') + def test_has_service_yes(self, get_session_mock): + session_mock = mock.Mock() + session_mock.get_endpoint.return_value = 'http://fake.url' + get_session_mock.return_value = session_mock + self.assertTrue(self.op_cloud.has_service("image")) + + def test_list_hypervisors(self): + '''This test verifies that calling list_hypervisors results in a call + to nova client.''' + self.register_uris([ + dict(method='GET', + uri=self.get_mock_url( + 'compute', 'public', append=['os-hypervisors', 'detail']), + json={'hypervisors': [ + fakes.make_fake_hypervisor('1', 'testserver1'), + fakes.make_fake_hypervisor('2', 'testserver2'), + ]}), + ]) + + r = self.op_cloud.list_hypervisors() + + self.assertEqual(2, len(r)) + self.assertEqual('testserver1', r[0]['hypervisor_hostname']) + self.assertEqual('testserver2', r[1]['hypervisor_hostname']) + + self.assert_calls()