From 1b3d713f48b5dbe8f63e02d7a85143c94578700c Mon Sep 17 00:00:00 2001 From: tengqm Date: Mon, 21 Sep 2015 02:58:47 -0400 Subject: [PATCH] Add support to stack update This patch adds support to update operation for Heat stacks. Heat returns a 202 for update operation, so this needs a special handler. Change-Id: I21a3ba5b4f50252e5be62488f25dac41ff438460 --- openstack/orchestration/v1/_proxy.py | 15 ++++++++++ openstack/orchestration/v1/stack.py | 12 ++++++++ .../tests/unit/orchestration/v1/test_proxy.py | 3 ++ .../tests/unit/orchestration/v1/test_stack.py | 29 ++++++++++++++++++- openstack/tests/unit/test_transport.py | 10 +++++++ openstack/transport.py | 4 ++- 6 files changed, 71 insertions(+), 2 deletions(-) diff --git a/openstack/orchestration/v1/_proxy.py b/openstack/orchestration/v1/_proxy.py index d5dbda1d1..02a64840c 100644 --- a/openstack/orchestration/v1/_proxy.py +++ b/openstack/orchestration/v1/_proxy.py @@ -67,6 +67,21 @@ class Proxy(proxy.BaseProxy): """ return self._get(stack.Stack, value) + def update_stack(self, value, **attrs): + """Update a stack + + :param value: The value can be the ID of a stack or a + :class:`~openstack.orchestration.v1.stack.Stack` instance. + :param kwargs \*\*attrs: The attributes to update on the stack + represented by ``value``. + + :returns: The updated stack + :rtype: :class:`~openstack.orchestration.v1.stack.Stack` + :raises: :class:`~openstack.exceptions.ResourceNotFound` + when no resource can be found. + """ + return self._update(stack.Stack, value, **attrs) + def delete_stack(self, value, ignore_missing=True): """Delete a stack diff --git a/openstack/orchestration/v1/stack.py b/openstack/orchestration/v1/stack.py index 4830945c6..6f821eb98 100644 --- a/openstack/orchestration/v1/stack.py +++ b/openstack/orchestration/v1/stack.py @@ -27,6 +27,7 @@ class Stack(resource.Resource): allow_create = True allow_list = True allow_retrieve = True + allow_update = True allow_delete = True # Properties @@ -81,3 +82,14 @@ class Stack(resource.Resource): url = cls.base_path resp = session.post(url, service=cls.service, json=body).body return resp[cls.resource_key] + + @classmethod + def update_by_id(cls, session, resource_id, attrs, path_args=None): + # Heat returns a 202 for update operation, we ignore the non-existent + # response.body and do an additional get + body = attrs.copy() + body.pop('id', None) + body.pop('name', None) + url = cls._get_url(path_args, resource_id) + session.put(url, service=cls.service, json=body) + return cls.get_by_id(session, resource_id) diff --git a/openstack/tests/unit/orchestration/v1/test_proxy.py b/openstack/tests/unit/orchestration/v1/test_proxy.py index 562db11b4..e424d9791 100644 --- a/openstack/tests/unit/orchestration/v1/test_proxy.py +++ b/openstack/tests/unit/orchestration/v1/test_proxy.py @@ -38,6 +38,9 @@ class TestOrchestrationProxy(test_proxy_base.TestProxyBase): def test_stack_get(self): self.verify_get(self.proxy.get_stack, stack.Stack) + def test_stack_update(self): + self.verify_update(self.proxy.update_stack, stack.Stack) + def test_stack_delete(self): self.verify_delete(self.proxy.delete_stack, stack.Stack, False) diff --git a/openstack/tests/unit/orchestration/v1/test_stack.py b/openstack/tests/unit/orchestration/v1/test_stack.py index 76f51e6b1..58fb72fa9 100644 --- a/openstack/tests/unit/orchestration/v1/test_stack.py +++ b/openstack/tests/unit/orchestration/v1/test_stack.py @@ -55,7 +55,7 @@ class TestStack(testtools.TestCase): self.assertEqual('orchestration', sot.service.service_type) self.assertTrue(sot.allow_create) self.assertTrue(sot.allow_retrieve) - self.assertFalse(sot.allow_update) + self.assertTrue(sot.allow_update) self.assertTrue(sot.allow_delete) self.assertTrue(sot.allow_list) @@ -100,6 +100,33 @@ class TestStack(testtools.TestCase): self.assertEqual(FAKE_ID, sot.id) self.assertEqual(FAKE_NAME, sot.name) + def test_update(self): + # heat responds to update request with an 202 status code + resp_update = mock.Mock() + resp_update.status_code = 202 + sess = mock.Mock() + sess.put = mock.Mock(return_value=resp_update) + + resp_get = mock.Mock() + resp_get.body = {'stack': FAKE_CREATE_RESPONSE} + sess.get = mock.Mock(return_value=resp_get) + + # create a stack for update + sot = stack.Stack(FAKE) + new_body = sot._attrs.copy() + del new_body['id'] + del new_body['name'] + + sot.timeout_mins = 119 + resp = sot.update(sess) + + url = 'stacks/%s' % sot.id + new_body['timeout_mins'] = 119 + sess.put.assert_called_once_with(url, service=sot.service, + json=new_body) + sess.get.assert_called_once_with(url, service=sot.service) + self.assertEqual(sot, resp) + def test_check(self): session_mock = mock.MagicMock() sot = stack.Stack(FAKE) diff --git a/openstack/tests/unit/test_transport.py b/openstack/tests/unit/test_transport.py index 469e8ef78..c4b2297af 100644 --- a/openstack/tests/unit/test_transport.py +++ b/openstack/tests/unit/test_transport.py @@ -208,6 +208,16 @@ class TestTransport(TestTransportBase): self.assertRequestHeaderEqual(mock_req, 'Accept', transport.JSON) self.assertEqual(fake_record1, resp.json()) + @requests_mock.Mocker() + def test_request_status_202(self, mock_req): + mock_req.put(self.TEST_URL, text='', status_code=202) + + xport = transport.Transport() + resp = xport.put(self.TEST_URL, json=fake_record2) + + self.assertEqual(202, resp.status_code) + self.assertEqual({}, resp.body) + @requests_mock.Mocker() def test_user_agent_no_arg(self, mock_req): mock_req.get(self.TEST_URL, text=fake_response) diff --git a/openstack/transport.py b/openstack/transport.py index c9045617f..20d14b514 100644 --- a/openstack/transport.py +++ b/openstack/transport.py @@ -266,7 +266,9 @@ class Transport(requests.Session): details=self._parse_error_response(resp), status_code=resp.status_code) - if accept == JSON: + if resp.status_code == 202: + resp.body = {} + elif accept == JSON: try: resp.body = resp.json() except ValueError as e: