Skip to content

Commit

Permalink
Merge b6fa794 into 4d4e7ea
Browse files Browse the repository at this point in the history
  • Loading branch information
alvarolopez committed Mar 18, 2015
2 parents 4d4e7ea + b6fa794 commit 599a149
Show file tree
Hide file tree
Showing 28 changed files with 761 additions and 166 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.OCCISchemaOcurrencesMismatch(
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
33 changes: 33 additions & 0 deletions ooi/api/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# -*- 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.

from ooi.wsgi import utils


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):
req.script_name = self.openstack_version
if path is not None:
req.path_info = path
if content_type is not None:
req.content_type = content_type
if body is not None:
req.body = utils.utf8(body)
return req
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 = response.json_body.get("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 = response.json_body.get("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 = response.json_body.get("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 = response.json_body.get("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 = response.json_body.get("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]
10 changes: 3 additions & 7 deletions ooi/api/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,12 @@
# License for the specific language governing permissions and limitations
# under the License.

from ooi.api import base
from ooi.occi.infrastructure import compute
import ooi.wsgi


class Controller(object):
def index(self, *args, **kwargs):
class Controller(base.Controller):
def index(self, req):
l = []
l.extend(compute.ComputeResource.actions)
return l


def create_resource():
return ooi.wsgi.Resource(Controller())
16 changes: 16 additions & 0 deletions ooi/exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,19 @@ class NotImplemented(OCCIException):

class HeaderNotFound(Invalid):
msg_fmt = "Header '%(header)s' not found."


class HeaderValidation(Invalid):
"""Parent class for header validation error exceptions."""


class OCCINoClassFound(HeaderValidation):
msg_fmt = "Found no headers matching class '%(class_id)s'."


class OCCISchemaOccurrencesMismatch(HeaderValidation):
msg_fmt = "Schema occurrences do not match: '%(mismatched_schemas)s'."


class OCCINotCompliantTerm(HeaderValidation):
msg_fmt = "Found a non-compliant term '%(term)s'."
4 changes: 3 additions & 1 deletion ooi/occi/core/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,6 @@ class Action(category.Category):
An Action represents an invocable operation applicable to a resource
instance.
"""
pass

def _class_name(self):
return "action"
18 changes: 18 additions & 0 deletions ooi/occi/core/attribute.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,24 @@ def name(self):
def value(self):
return self._value

def _as_str(self):
value_str = ''
if isinstance(self._value, six.string_types):
value_str = '"%s"' % self._value
elif isinstance(self._value, bool):
value_str = '"%s"' % str(self._value).lower()
else:
value_str = "%s" % self._value
return "%s=%s" % (self.name, value_str)

def __str__(self):
"""Render the attribute to text/plain."""
return ": ".join(self.headers()[0])

def headers(self):
"""Render the attribute to text/occi."""
return [("X-OCCI-Attribute", self._as_str())]


class MutableAttribute(Attribute):
@Attribute.value.setter
Expand Down
10 changes: 7 additions & 3 deletions ooi/occi/core/category.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,21 @@ def __init__(self, scheme, term, title, attributes=None, location=None):
self.attributes = attributes
self.location = location

def _class_name(self):
"""Returns this class name (see OCCI v1.1 rendering)."""
raise ValueError

def _as_str(self):
d = {
"term": self.term,
"scheme": self.scheme,
"class": self.__class__.__name__.lower()
"class": self._class_name()
}

return "%(term)s; scheme=%(scheme)s; class=%(class)s" % d
return '%(term)s; scheme="%(scheme)s"; class="%(class)s"' % d

def headers(self):
return [("Category", self._as_str())]

def __str__(self):
return "Category: %s" % self._as_str()
return ": ".join(self.headers()[0])
56 changes: 56 additions & 0 deletions ooi/occi/core/collection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# -*- 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.


class Collection(object):
"""An OCCI Collection is used to render a set of OCCI objects.
Depending on the rendering and the contents of the collection, there will
be one output or another. This class should do the magic and render the
proper information, taking into account what is in the collection.
"""
def __init__(self, kinds=[], mixins=[], actions=[],
resources=[], links=[]):

self.kinds = kinds
self.mixins = mixins
self.actions = actions
self.resources = resources
self.links = links

def __str__(self):
"""Render the collection to text/plain."""
# NOTE(aloga): This is unfinished, we need to check what is inside the
# collection and render it properly. For example, if we have a
# collection of resources, we should render only their locations.
ret = []
for what in [self.kinds, self.mixins, self.actions,
self.resources, self.links]:
for el in what:
ret.append("X-OCCI-Location: %s" % el.location)
return "\n".join(ret)

def headers(self):
"""Render the collection to text/occi."""
# NOTE(aloga): This is unfinished, we need to check what is inside the
# collection and render it properly. For example, if we have a
# collection of resources, we should render only their locations.
headers = []
for what in [self.kinds, self.mixins, self.actions,
self.resources, self.links]:
for el in what:
headers.append(("X-OCCI-Location", el.location))
return headers
Loading

0 comments on commit 599a149

Please sign in to comment.