Skip to content

Commit

Permalink
Merge 65edb4b into 4d4e7ea
Browse files Browse the repository at this point in the history
  • Loading branch information
alvarolopez committed Apr 10, 2015
2 parents 4d4e7ea + 65edb4b commit f09fc3a
Show file tree
Hide file tree
Showing 35 changed files with 1,645 additions and 263 deletions.
92 changes: 90 additions & 2 deletions ooi/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,94 @@
# License for the specific language governing permissions and limitations
# under the License.

import collections
import shlex

class BaseController(object):
pass
import six.moves.urllib.parse as urlparse

from ooi import exception


def _lexize(s, separator, ignore_whitespace=False):
lex = shlex.shlex(instream=s, posix=True)
lex.commenters = ""
if ignore_whitespace:
lex.whitespace = separator
else:
lex.whitespace += separator
lex.whitespace_split = True
return list(lex)


def parse(f):
def _parse(obj, req, *args, **kwargs):
headers = {}
try:
l = []
params = {}
for ctg in _lexize(req.headers["Category"],
separator=',',
ignore_whitespace=True):
ll = _lexize(ctg, ';')
d = {"term": ll[0]} # assumes 1st element => term's value
d.update(dict([i.split('=') for i in ll[1:]]))
l.append(d)
params[urlparse.urlparse(d["scheme"]).path] = d["term"]
headers["Category"] = l
except KeyError:
raise exception.HeaderNotFound(header="Category")

return f(obj, req, headers, params, *args, **kwargs)
return _parse


def _get_header_by_class(headers, class_id):
return [h for h in headers["Category"] if h["class"] in [class_id]]


def validate(class_id, schemas, term=None):
def accepts(f):
def _validate(obj, req, headers, params, *args, **kwargs):
"""Category headers validation.
Arguments::
class_id: type of OCCI class (kind, mixin, ..).
schemas: dict mapping the mandatory schemas with its
occurrences.
term (optional): if present, validates its value.
Validation checks::
class_presence: asserts the existance of given class_id.
scheme_occurrences: enforces the number of occurrences of the
given schemas.
term_validation: asserts the correct term value of the
matching headers.
"""
header_l = _get_header_by_class(headers, class_id)

def class_presence():
if not header_l:
raise exception.OCCINoClassFound(class_id=class_id)

def scheme_occurrences():
d = collections.Counter([h["scheme"]
for h in header_l])
s = set(d.items()) ^ set(schemas.items())
if len(s) != 0:
mismatched_schemas = [(scheme, d[scheme])
for scheme in dict(s).keys()]
raise exception.OCCISchemaOccurrencesMismatch(
mismatched_schemas=mismatched_schemas)

def term_validation():
if [h for h in header_l if h["term"] not in [term]]:
raise exception.OCCINotCompliantTerm(term=term)

class_presence()
scheme_occurrences()
if term:
term_validation()

return f(obj, req, headers, params, *args, **kwargs)
return _validate
return accepts
108 changes: 108 additions & 0 deletions ooi/api/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# -*- coding: utf-8 -*-

# Copyright 2015 Spanish National Research Council
#
# 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 copy

from ooi import utils

import webob.exc


class Controller(object):
def __init__(self, app, openstack_version):
self.app = app
self.openstack_version = openstack_version

def _get_req(self, req,
path=None,
content_type=None,
body=None,
method=None):
"""Return a new Request object to interact with OpenStack.
This method will create a new request starting with the same WSGI
environment as the original request, prepared to interact with
OpenStack. Namely, it will override the script name to match the
OpenStack version. It will also override the path, content_type and
body of the request, if any of those keyword arguments are passed.
:param req: the original request
:param path: new path for the request
:param content_type: new content type for the request
:param body: new body for the request
:returns: a Request object
"""
new_req = webob.Request(copy.copy(req.environ))
new_req.script_name = self.openstack_version
if path is not None:
new_req.path_info = path
if content_type is not None:
new_req.content_type = content_type
if body is not None:
new_req.body = utils.utf8(body)
if method is not None:
new_req.method = method
return new_req

@staticmethod
def get_from_response(response, element, default):
"""Get a JSON element from a valid response or raise an exception.
This method will extract an element a JSON response (falling back to a
default value) if the response has a code of 200, otherwise it will
raise a webob.exc.exception
:param response: The webob.Response object
:param element: The element to look for in the JSON body
:param default: The default element to be returned if not found.
"""
if response.status_int in [200, 201, 202]:
return response.json_body.get(element, default)
else:
raise exception_from_response(response)


def exception_from_response(response):
"""Convert an OpenStack V2 Fault into a webob exception.
Since we are calling the OpenStack API we should process the Faults
produced by them. Extract the Fault information according to [1] and
convert it back to a webob exception.
[1] http://docs.openstack.org/developer/nova/v2/faults.html
:param response: a webob.Response containing an exception
:returns: a webob.exc.exception object
"""
exceptions = {
400: webob.exc.HTTPBadRequest,
401: webob.exc.HTTPUnauthorized,
403: webob.exc.HTTPForbidden,
404: webob.exc.HTTPNotFound,
405: webob.exc.HTTPMethodNotAllowed,
406: webob.exc.HTTPNotAcceptable,
409: webob.exc.HTTPConflict,
413: webob.exc.HTTPRequestEntityTooLarge,
415: webob.exc.HTTPUnsupportedMediaType,
429: webob.exc.HTTPTooManyRequests,
501: webob.exc.HTTPNotImplemented,
503: webob.exc.HTTPServiceUnavailable,
}
code = response.status_int
message = response.json_body.popitem()[1].get("message")

exc = exceptions.get(code, webob.exc.HTTPInternalServerError)
return exc(explanation=message)
116 changes: 114 additions & 2 deletions ooi/api/compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,122 @@
# License for the specific language governing permissions and limitations
# under the License.

import json

import ooi.api
import ooi.api.base
from ooi.occi.core import collection
from ooi.occi.infrastructure import compute
from ooi.openstack import helpers
from ooi.openstack import templates


class Controller(ooi.api.base.Controller):
def _get_compute_resources(self, servers):
occi_compute_resources = []
if servers:
for s in servers:
s = compute.ComputeResource(title=s["name"], id=s["id"])
occi_compute_resources.append(s)

return occi_compute_resources

def _get_compute_ids(self, req):
tenant_id = req.environ["keystone.token_auth"].user.project_id
req = self._get_req(req,
path="/%s/servers" % tenant_id,
method="GET")
response = req.get_response(self.app)
return [s["id"] for s in self.get_from_response(response,
"servers", [])]

def _delete(self, req, ids):
tenant_id = req.environ["keystone.token_auth"].user.project_id
for id in ids:
req = self._get_req(req,
path="/%s/servers/%s" % (tenant_id,
id),
method="DELETE")
response = req.get_response(self.app)
if response.status_int not in [204]:
raise ooi.api.base.exception_from_response(response)
return []

class ComputeController(ooi.api.BaseController):
def index(self, req):
tenant_id = req.environ["keystone.token_auth"].user.project_id
req.path_info = "/%s/servers" % tenant_id
req = self._get_req(req, path="/%s/servers" % tenant_id)
response = req.get_response(self.app)
servers = self.get_from_response(response, "servers", [])
occi_compute_resources = self._get_compute_resources(servers)

return collection.Collection(resources=occi_compute_resources)

@ooi.api.parse
@ooi.api.validate("kind",
{"http://schemas.ogf.org/occi/infrastructure#": 1},
term="compute")
@ooi.api.validate("mixin",
{"http://schemas.openstack.org/template/resource#": 1,
"http://schemas.openstack.org/template/os#": 1})
def create(self, req, headers, params, body):
tenant_id = req.environ["keystone.token_auth"].user.project_id
req = self._get_req(req,
path="/%s/servers" % tenant_id,
content_type="application/json",
body=json.dumps({
"server": {
"name": params["/occi/infrastructure"],
"imageRef": params["/template/os"],
"flavorRef": params["/template/resource"]
}}))
response = req.get_response(self.app)
# We only get one server
server = self.get_from_response(response, "server", {})

# The returned JSON does not contain the server name
server["name"] = params["/occi/infrastructure"]
occi_compute_resources = self._get_compute_resources([server])

return collection.Collection(resources=occi_compute_resources)

def show(self, req, id):
tenant_id = req.environ["keystone.token_auth"].user.project_id

# get info from server
req = self._get_req(req, path="/%s/servers/%s" % (tenant_id, id))
response = req.get_response(self.app)
s = self.get_from_response(response, "server", {})

# get info from flavor
req = self._get_req(req, path="/%s/flavors/%s" % (tenant_id,
s["flavor"]["id"]))
response = req.get_response(self.app)
flavor = self.get_from_response(response, "flavor", {})
res_tpl = templates.OpenStackResourceTemplate(flavor["name"],
flavor["vcpus"],
flavor["ram"],
flavor["disk"])

# get info from image
req = self._get_req(req, path="/%s/images/%s" % (tenant_id,
s["image"]["id"]))
response = req.get_response(self.app)
image = self.get_from_response(response, "image", {})
os_tpl = templates.OpenStackOSTemplate(image["id"],
image["name"])

# build the compute object
# TODO(enolfc): link to network + storage
comp = compute.ComputeResource(title=s["name"], id=s["id"],
cores=flavor["vcpus"],
hostname=s["name"],
memory=flavor["ram"],
state=helpers.occi_state(s["status"]),
mixins=[os_tpl, res_tpl])
return [comp]

def delete(self, req, id):
return self._delete(req, [id])

def delete_all(self, req):
return self._delete(req, self._get_compute_ids(req))
Loading

0 comments on commit f09fc3a

Please sign in to comment.