Skip to content

Commit

Permalink
Merge 3f436d4 into 4d4e7ea
Browse files Browse the repository at this point in the history
  • Loading branch information
alvarolopez committed Apr 10, 2015
2 parents 4d4e7ea + 3f436d4 commit 4837a35
Show file tree
Hide file tree
Showing 35 changed files with 1,608 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
102 changes: 102 additions & 0 deletions ooi/api/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# -*- 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):
"""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)
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)
89 changes: 87 additions & 2 deletions ooi/api/compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,95 @@
# 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

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, id, req):
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]
60 changes: 54 additions & 6 deletions ooi/api/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,64 @@
# License for the specific language governing permissions and limitations
# under the License.

from ooi.api import base
from ooi.occi.core import entity
from ooi.occi.core import link
from ooi.occi.core import resource
from ooi.occi.infrastructure import compute
import ooi.wsgi
from ooi.occi.infrastructure import templates as infra_templates
from ooi.openstack import mixins
from ooi.openstack import templates


class Controller(object):
def index(self, *args, **kwargs):
class Controller(base.Controller):
def _resource_tpls(self, req):
tenant_id = req.environ["keystone.token_auth"].user.project_id
req = self._get_req(req, path="/%s/flavors/detail" % tenant_id)
response = req.get_response(self.app)
flavors = self.get_from_response(response, "flavors", [])
occi_resource_templates = []
if flavors:
for f in flavors:
tpl = templates.OpenStackResourceTemplate(f["name"],
f["vcpus"],
f["ram"],
f["disk"])
occi_resource_templates.append(tpl)
return occi_resource_templates

def _os_tpls(self, req):
tenant_id = req.environ["keystone.token_auth"].user.project_id
req = self._get_req(req, path="/%s/images/detail" % tenant_id)
response = req.get_response(self.app)
images = self.get_from_response(response, "images", [])
occi_os_templates = []
if images:
for i in images:
tpl = templates.OpenStackOSTemplate(i["id"], i["name"])
occi_os_templates.append(tpl)
return occi_os_templates

def index(self, req):
l = []
# OCCI Core Kinds:
l.append(entity.Entity.kind)
l.append(resource.Resource.kind)
l.append(link.Link.kind)

# OCCI infra Compute:
l.append(compute.ComputeResource.kind)
l.extend(compute.ComputeResource.actions)
return l

# OCCI infra mixins
l.append(infra_templates.os_tpl)
l.append(infra_templates.resource_tpl)

# OpenStack flavors & images
l.extend(self._resource_tpls(req))
l.extend(self._os_tpls(req))

def create_resource():
return ooi.wsgi.Resource(Controller())
# OpenStack mixins (contextualization)
l.append(mixins.user_data)
l.append(mixins.public_key)
return l
Loading

0 comments on commit 4837a35

Please sign in to comment.