From 88daa5370c9c693667fc1dafc81e63b00c129332 Mon Sep 17 00:00:00 2001 From: Alvaro Lopez Garcia Date: Mon, 20 Apr 2015 15:32:14 +0200 Subject: [PATCH] actions: implement stop and start for compute resources --- ooi/api/base.py | 11 +++- ooi/api/compute.py | 36 ++++++++++++ ooi/exception.py | 4 ++ ooi/tests/fakes.py | 17 +++++- .../middleware/test_compute_controller.py | 55 +++++++++++++++++++ ooi/wsgi/__init__.py | 4 ++ 6 files changed, 122 insertions(+), 5 deletions(-) diff --git a/ooi/api/base.py b/ooi/api/base.py index b00894d..cb3c8d1 100644 --- a/ooi/api/base.py +++ b/ooi/api/base.py @@ -31,9 +31,10 @@ def __init__(self, app, openstack_version): def _get_req(self, req, path=None, - content_type=None, + content_type="application/json", body=None, - method=None): + method=None, + query_string=""): """Return a new Request object to interact with OpenStack. This method will create a new request starting with the same WSGI @@ -44,12 +45,16 @@ def _get_req(self, req, :param req: the original request :param path: new path for the request - :param content_type: new content type for the request + :param content_type: new content type for the request, defaults to + "application/json" if not specified :param body: new body for the request + :param query_string: query string for the request, defaults to an empty + query if not specified :returns: a Request object """ new_req = webob.Request(copy.copy(req.environ)) new_req.script_name = self.openstack_version + new_req.query_string = query_string if path is not None: new_req.path_info = path if content_type is not None: diff --git a/ooi/api/compute.py b/ooi/api/compute.py index 38de06e..58d6906 100644 --- a/ooi/api/compute.py +++ b/ooi/api/compute.py @@ -17,6 +17,7 @@ import json import ooi.api.base +from ooi import exception from ooi.occi.core import collection from ooi.occi.infrastructure import compute from ooi.occi.infrastructure import storage @@ -28,6 +29,10 @@ class Controller(ooi.api.base.Controller): + def __init__(self, *args, **kwargs): + super(Controller, self).__init__(*args, **kwargs) + self.compute_actions = compute.ComputeResource.actions + def _get_compute_resources(self, servers): occi_compute_resources = [] if servers: @@ -67,6 +72,37 @@ def index(self, req): return collection.Collection(resources=occi_compute_resources) + def run_action(self, req, id, body): + action = req.GET.get("action", None) + actions = [a.term for a in compute.ComputeResource.actions] + + if action is None or action not in actions: + raise exception.InvalidAction(action=action) + + parser = req.get_parser()(req.headers, req.body) + obj = parser.parse() + + if action == "stop": + scheme = {"category": compute.stop} + req_body = {"os-stop": None} + elif action == "start": + scheme = {"category": compute.start} + req_body = {"os-start": None} + else: + raise exception.NotImplemented + + validator = occi_validator.Validator(obj) + validator.validate(scheme) + + tenant_id = req.environ["keystone.token_auth"].user.project_id + path = "/%s/servers/%s/action" % (tenant_id, id) + req = self._get_req(req, path=path, body=json.dumps(req_body), + method="POST") + response = req.get_response(self.app) + if response.status_int != 202: + raise ooi.api.base.exception_from_response(response) + return [] + def create(self, req, body): tenant_id = req.environ["keystone.token_auth"].user.project_id parser = req.get_parser()(req.headers, req.body) diff --git a/ooi/exception.py b/ooi/exception.py index 549e7c7..da466ba 100644 --- a/ooi/exception.py +++ b/ooi/exception.py @@ -76,6 +76,10 @@ class Invalid(OCCIException): code = 400 +class InvalidAction(Invalid): + msg_fmt = "Invalid action %(action)s provided." + + class InvalidContentType(Invalid): msg_fmt = "Invalid Content-type %(content_type)s." code = 406 diff --git a/ooi/tests/fakes.py b/ooi/tests/fakes.py index 24ddc68..f5648e2 100644 --- a/ooi/tests/fakes.py +++ b/ooi/tests/fakes.py @@ -274,13 +274,14 @@ def __init__(self): for tenant in tenants.values(): path = "/%s" % tenant["id"] - self._populate(path, "server", servers[tenant["id"]]) + self._populate(path, "server", servers[tenant["id"]], actions=True) self._populate(path, "volume", volumes[tenant["id"]], "os-volumes") # NOTE(aloga): dict_values un Py3 is not serializable in JSON self._populate(path, "image", list(images.values())) self._populate(path, "flavor", list(flavors.values())) - def _populate(self, path_base, obj_name, obj_list, objs_path=None): + def _populate(self, path_base, obj_name, obj_list, + objs_path=None, actions=[]): objs_name = "%ss" % obj_name if objs_path: path = "%s/%s" % (path_base, objs_path) @@ -295,6 +296,10 @@ def _populate(self, path_base, obj_name, obj_list, objs_path=None): obj_path = "%s/%s" % (path, o["id"]) self.routes[obj_path] = create_fake_json_resp({obj_name: o}) + if actions: + action_path = "%s/action" % obj_path + self.routes[action_path] = webob.Response(status=202) + @webob.dec.wsgify() def __call__(self, req): if req.method == "GET": @@ -315,9 +320,17 @@ def _do_create(self, req): def _do_post(self, req): if req.path_info.endswith("servers"): return self._do_create(req) + elif req.path_info.endswith("action"): + body = req.json_body.copy() + action = body.popitem() + if action[0] in ["os-start", "os-stop"]: + return self._get_from_routes(req) raise Exception def _do_get(self, req): + return self._get_from_routes(req) + + def _get_from_routes(self, req): try: ret = self.routes[req.path_info] except KeyError: diff --git a/ooi/tests/middleware/test_compute_controller.py b/ooi/tests/middleware/test_compute_controller.py index 4033c22..c0c241e 100644 --- a/ooi/tests/middleware/test_compute_controller.py +++ b/ooi/tests/middleware/test_compute_controller.py @@ -150,6 +150,61 @@ def test_vm_not_found(self): resp = req.get_response(app) self.assertEqual(404, resp.status_code) + def test_action_vm(self): + tenant = fakes.tenants["foo"] + app = self.get_app() + + for action in ("stop", "start"): + headers = { + 'Category': ( + '%s;' + 'scheme="http://schemas.ogf.org/occi/infrastructure/' + 'compute/action#";' + 'class="action"' % action) + } + for server in fakes.servers[tenant["id"]]: + req = self._build_req("/compute/%s?action=%s" % (server["id"], + action), + tenant["id"], method="POST", + headers=headers) + resp = req.get_response(app) + self.assertDefaults(resp) + self.assertEqual(204, resp.status_code) + + def test_invalid_action(self): + tenant = fakes.tenants["foo"] + app = self.get_app() + + action = "foo" + for server in fakes.servers[tenant["id"]]: + req = self._build_req("/compute/%s?action=%s" % (server["id"], + action), + tenant["id"], method="POST") + resp = req.get_response(app) + self.assertDefaults(resp) + self.assertEqual(400, resp.status_code) + + def test_action_body_mismatch(self): + tenant = fakes.tenants["foo"] + app = self.get_app() + + action = "stop" + headers = { + 'Category': ( + 'start;' + 'scheme="http://schemas.ogf.org/occi/infrastructure/' + 'compute/action#";' + 'class="action"') + } + for server in fakes.servers[tenant["id"]]: + req = self._build_req("/compute/%s?action=%s" % (server["id"], + action), + tenant["id"], method="POST", + 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/wsgi/__init__.py b/ooi/wsgi/__init__.py index 9fc306f..2ff557e 100644 --- a/ooi/wsgi/__init__.py +++ b/ooi/wsgi/__init__.py @@ -150,6 +150,10 @@ def index(self, *args, **kwargs): controller=self.resources["compute"], action="delete_all", conditions=dict(method=["DELETE"])) + self.mapper.connect("/compute/{id}", + controller=self.resources["compute"], + action="run_action", + conditions=dict(method=["POST"])) self.resources["storage"] = self._create_resource( ooi.api.storage.Controller)