Correctly handling missing 'image' attribute in the OpenStack list nodes response and add a test case for it.
Part of LIBCLOUD-455. Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/df97607e Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/df97607e Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/df97607e Branch: refs/heads/trunk Commit: df97607ec9fe56126028abc0be788eb5fa86124a Parents: 991d8f1 Author: Tomaz Muraus <[email protected]> Authored: Fri Dec 6 18:51:09 2013 +0100 Committer: Tomaz Muraus <[email protected]> Committed: Fri Dec 6 19:04:50 2013 +0100 ---------------------------------------------------------------------- libcloud/compute/drivers/openstack.py | 8 ++- .../_servers_detail_ERROR_STATE.json | 66 ++++++++++++++++++++ libcloud/test/compute/test_openstack.py | 14 +++++ 3 files changed, 86 insertions(+), 2 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/libcloud/blob/df97607e/libcloud/compute/drivers/openstack.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/drivers/openstack.py b/libcloud/compute/drivers/openstack.py index 00debd0..7d0958a 100644 --- a/libcloud/compute/drivers/openstack.py +++ b/libcloud/compute/drivers/openstack.py @@ -1861,6 +1861,11 @@ class OpenStack_1_1_NodeDriver(OpenStackNodeDriver): else: private_ips.extend(ips) + # Sometimes 'image' attribute is not present if the node is in an error + # state + image = api_node.get('image', None) + image_id = image.get('id', None) if image else None + return Node( id=api_node['id'], name=api_node['name'], @@ -1875,8 +1880,7 @@ class OpenStack_1_1_NodeDriver(OpenStackNodeDriver): # Docs says "tenantId", but actual is "tenant_id". *sigh* # Best handle both. tenantId=api_node.get('tenant_id') or api_node['tenantId'], - # sometimes the image is not set if openstack is in an error state - imageId=api_node.get('image', {}).get('id', None), + imageId=image_id, flavorId=api_node['flavor']['id'], uri=next(link['href'] for link in api_node['links'] if link['rel'] == 'self'), http://git-wip-us.apache.org/repos/asf/libcloud/blob/df97607e/libcloud/test/compute/fixtures/openstack_v1.1/_servers_detail_ERROR_STATE.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/openstack_v1.1/_servers_detail_ERROR_STATE.json b/libcloud/test/compute/fixtures/openstack_v1.1/_servers_detail_ERROR_STATE.json new file mode 100644 index 0000000..3e41379 --- /dev/null +++ b/libcloud/test/compute/fixtures/openstack_v1.1/_servers_detail_ERROR_STATE.json @@ -0,0 +1,66 @@ +{ + "servers": [ + { + "status": "ERROR", + "updated": "2013-12-05T21:07:07Z", + "hostId": "2a4a12656a7a57c10188e4ea37f9e09dfb99e3d628f4064f97761e09", + "addresses": { + "pool": [ + { + "OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:be:f5:87", + "version": 4, + "addr": "192.168.3.4", + "OS-EXT-IPS:type": "fixed" + } + ] + }, + "links": [ + { + "href": "http://192.168.0.1:8774/v2/dd3eca3de72846948f5d6d975660d325/servers/2d05bd68-3fbb-4b47-9f38-c690a5d93e45", + "rel": "self" + }, + { + "href": "http://192.168.0.1:8774/dd3eca3de72846948f5d6d975660d325/servers/2d05bd68-3fbb-4b47-9f38-c690a5d93e45", + "rel": "bookmark" + } + ], + "key_name": "my_key", + "image": "", + "OS-EXT-STS:task_state": null, + "OS-EXT-STS:vm_state": "error", + "OS-SRV-USG:launched_at": null, + "flavor": { + "id": "4", + "links": [ + { + "href": "http://192.168.0.1:8774/dd3eca3de72846948f5d6d975660d325/flavors/4", + "rel": "bookmark" + } + ] + }, + "id": "2d05bd68-3fbb-4b47-9f38-c690a5d93e45", + "OS-SRV-USG:terminated_at": null, + "OS-EXT-AZ:availability_zone": "nova", + "user_id": "a75c583fa46148eaa020d3e88ab53802", + "name": "test_vm", + "created": "2013-12-02T18:40:36Z", + "tenant_id": "dd3eca3de72846948f5d6d975660d325", + "OS-DCF:diskConfig": "MANUAL", + "os-extended-volumes:volumes_attached": [ + { + "id": "0056485c-ada5-4e44-9905-5b09a18b0139" + } + ], + "accessIPv4": "", + "accessIPv6": "", + "fault": { + "message": "The server has either erred or is incapable of performing the requested operation. (HTTP 500) (Request-ID: req-5ec1e01c-bc04-43e7-957d-c810d4357908)", + "code": 500, + "created": "2013-12-05T21:07:07Z" + }, + "OS-EXT-STS:power_state": 0, + "config_drive": "", + "metadata": {} + } + ] +} http://git-wip-us.apache.org/repos/asf/libcloud/blob/df97607e/libcloud/test/compute/test_openstack.py ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/test_openstack.py b/libcloud/test/compute/test_openstack.py index a8f0c3d..b0e1d2c 100644 --- a/libcloud/test/compute/test_openstack.py +++ b/libcloud/test/compute/test_openstack.py @@ -850,9 +850,11 @@ class OpenStack_1_1_Tests(unittest.TestCase, TestCaseMixin): self.driver_klass.connectionCls.conn_classes = ( OpenStack_2_0_MockHttp, OpenStack_2_0_MockHttp) self.driver_klass.connectionCls.auth_url = "https://auth.api.example.com" + OpenStackMockHttp.type = None OpenStack_1_1_MockHttp.type = None OpenStack_2_0_MockHttp.type = None + self.driver = self.create_driver() # normally authentication happens lazily, but we force it here @@ -973,6 +975,14 @@ class OpenStack_1_1_Tests(unittest.TestCase, TestCaseMixin): self.assertEqual(node.extra['updated'], '2011-10-11T00:50:04Z') self.assertEqual(node.extra['created'], '2011-10-11T00:51:39Z') + def test_list_nodes_no_image_id_attribute(self): + # Regression test for LIBCLOD-455 + self.driver_klass.connectionCls.conn_classes[0].type = 'ERROR_STATE_NO_IMAGE_ID' + self.driver_klass.connectionCls.conn_classes[1].type = 'ERROR_STATE_NO_IMAGE_ID' + + nodes = self.driver.list_nodes() + self.assertEqual(nodes[0].extra['imageId'], None) + def test_list_volumes(self): volumes = self.driver.list_volumes() self.assertEqual(len(volumes), 2) @@ -1501,6 +1511,10 @@ class OpenStack_1_1_MockHttp(MockHttpTestCase): body = self.fixtures.load('_servers_detail.json') return (httplib.OK, body, self.json_content_headers, httplib.responses[httplib.OK]) + def _v1_1_slug_servers_detail_ERROR_STATE_NO_IMAGE_ID(self, method, url, body, headers): + body = self.fixtures.load('_servers_detail_ERROR_STATE.json') + return (httplib.OK, body, self.json_content_headers, httplib.responses[httplib.OK]) + def _v1_1_slug_flavors_detail(self, method, url, body, headers): body = self.fixtures.load('_flavors_detail.json') return (httplib.OK, body, self.json_content_headers, httplib.responses[httplib.OK])
