diff --git a/ooi/api/compute.py b/ooi/api/compute.py index 09309fe..aeefbac 100644 --- a/ooi/api/compute.py +++ b/ooi/api/compute.py @@ -246,6 +246,28 @@ class Controller(ooi.api.base.Controller): return collection.Collection(resources=occi_compute_resources) + def update(self, req, id, body): + # get info from server + self.os_helper.get_server(req, id) + + parser = req.get_parser()(req.headers, req.body) + scheme = { + "mixins": [ + templates.OpenStackResourceTemplate, + ], + } + obj = parser.parse() + validator = occi_validator.Validator(obj) + validator.validate(scheme) + + # Changes to the flavor (resize) + flavor = obj["schemes"][templates.OpenStackResourceTemplate.scheme][0] + action_args = {"flavorRef": flavor} + self.os_helper.run_action(req, "resize", id, action_args) + + # re-use the show function + return self.show(req, id) + def show(self, req, id): # get info from server s = self.os_helper.get_server(req, id) diff --git a/ooi/api/helpers.py b/ooi/api/helpers.py index 4646d37..1e92470 100644 --- a/ooi/api/helpers.py +++ b/ooi/api/helpers.py @@ -306,7 +306,8 @@ class OpenStackHelper(BaseHelper): "resume": {"resume": None}, "unpause": {"unpause": None}, "restart": {"reboot": {"type": "SOFT"}}, - "save": {"createImage": None} + "save": {"createImage": None}, + "resize": {"resize": None}, } os_action, default_args = actions_map[action].popitem() diff --git a/ooi/tests/fakes.py b/ooi/tests/fakes.py index f65ecf9..4cd2bf0 100644 --- a/ooi/tests/fakes.py +++ b/ooi/tests/fakes.py @@ -711,6 +711,26 @@ class FakeApp(object): "status": "on-line"}} return create_fake_json_resp(s) + def _do_resize_server(self, req): + body = req.json_body.copy() + new_flavor = body.popitem()[1]["flavorRef"] + r = self._get_from_routes(req) + # Make sure subsequent calls to the server are up to date + # Some request path knowledge used which should be in this form: + # /v2.1/tenant_id/servers/server_id/action + p = req.path.split("/") + tenant_id = p[2] + server_id = p[4] + for s in servers[tenant_id]: + if s["id"] == server_id: + # do a conversion to avoid current id schemas + s["flavor"]["id"] = int(new_flavor) + break + # And repopulate the objects so they are properly returned + self._populate("/%s" % tenant_id, "server", servers[tenant_id], + actions=True) + return r + def _do_create_attachment(self, req): v = {"volumeAttachment": {"serverId": "foo", "volumeId": "bar", @@ -764,6 +784,8 @@ class FakeApp(object): "removeSecurityGroup", "addSecurityGroup"]: return self._get_from_routes(req) + if action[0] == "resize": + return self._do_resize_server(req) elif req.path_info.endswith("os-volume_attachments"): return self._do_create_attachment(req) elif req.path_info.endswith("os-floating-ips"): diff --git a/ooi/tests/functional/middleware/test_compute_controller.py b/ooi/tests/functional/middleware/test_compute_controller.py index c4798a6..cf3523b 100644 --- a/ooi/tests/functional/middleware/test_compute_controller.py +++ b/ooi/tests/functional/middleware/test_compute_controller.py @@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. +import copy import uuid from ooi.tests import fakes @@ -239,6 +240,52 @@ class TestComputeController(test_middleware.TestMiddleware): self.assertDefaults(resp) self.assertEqual(400, resp.status_code) + def test_modify_vm_flavor(self): + tenant = fakes.tenants["foo"] + app = self.get_app() + flavor_ids = [ids for ids in fakes.flavors] + + for server in fakes.servers[tenant["id"]]: + old_flavor = server["flavor"]["id"] + # just pick a different one + new_flavor = flavor_ids[flavor_ids.index(old_flavor) - 1] + headers = { + 'Category': ( + '%s ;' + 'scheme="http://schemas.openstack.org/template/resource#";' + 'class="mixin"' + ) % new_flavor + } + req = self._build_req("/compute/%s" % server["id"], tenant["id"], + method="PUT", headers=headers) + + modified_server = copy.deepcopy(server) + modified_server["flavor"]["id"] = new_flavor + expected = build_occi_server(modified_server) + resp = req.get_response(app) + self.assertDefaults(resp) + self.assertExpectedResult(expected, resp) + self.assertEqual(200, resp.status_code) + + def test_modify_vm_wrong_mixin(self): + tenant = fakes.tenants["foo"] + app = self.get_app() + + for server in fakes.servers[tenant["id"]]: + headers = { + 'Category': ( + 'foobar;' + 'scheme="http://schemas.openstack.org/template/os#";' + 'class="mixin"' + ) + } + req = self._build_req("/compute/%s" % server["id"], tenant["id"], + method="PUT", headers=headers) + + resp = req.get_response(app) + self.assertDefaults(resp) + self.assertEqual(400, resp.status_code) + def test_create_vm(self): tenant = fakes.tenants["foo"] diff --git a/ooi/tests/unit/controllers/test_compute.py b/ooi/tests/unit/controllers/test_compute.py index 2603c86..8b10ee4 100644 --- a/ooi/tests/unit/controllers/test_compute.py +++ b/ooi/tests/unit/controllers/test_compute.py @@ -787,3 +787,30 @@ class TestComputeController(base.TestController): block_device_mapping=[], networks=net) m_net.assert_called_with(req, obj) + + @mock.patch.object(compute.Controller, "show") + @mock.patch.object(helpers.OpenStackHelper, "run_action") + @mock.patch("ooi.occi.validator.Validator") + @mock.patch.object(helpers.OpenStackHelper, "get_server") + def test_update(self, m_server, m_validator, m_run_action, m_show): + tenant = fakes.tenants["foo"] + req = self._build_req(tenant["id"]) + obj = { + "schemes": { + templates.OpenStackResourceTemplate.scheme: ["bar"], + }, + } + # NOTE(aloga): the mocked call is + # "parser = req.get_parser()(req.headers, req.body)" + req.get_parser = mock.MagicMock() + req.get_parser.return_value.return_value.parse.return_value = obj + m_validator.validate.return_value = True + + servers = fakes.servers[tenant["id"]] + for server in servers: + ret = self.controller.update(req, server["id"], None) + m_run_action.assert_called_with(mock.ANY, "resize", server["id"], + {'flavorRef': 'bar'}) + m_server.assert_called_with(mock.ANY, server["id"]) + m_show.assert_called_with(mock.ANY, server["id"]) + self.assertEqual(m_show.return_value, ret)