Skip to content

Commit

Permalink
Merge 4ee3782 into 65edb4b
Browse files Browse the repository at this point in the history
  • Loading branch information
enolfc committed Apr 13, 2015
2 parents 65edb4b + 4ee3782 commit 2b9d92e
Show file tree
Hide file tree
Showing 21 changed files with 895 additions and 166 deletions.
124 changes: 43 additions & 81 deletions ooi/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,94 +14,56 @@
# License for the specific language governing permissions and limitations
# under the License.

import collections
import shlex

import six.moves.urllib.parse as urlparse
import copy

from ooi import exception
from ooi.occi import helpers


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 compare_schemes(expected_type, actual):
actual_scheme, actual_term = helpers.decompose_type(actual)
if expected_type.scheme != actual_scheme:
return False
try:
if expected_type.term != actual_term:
return False
except AttributeError:
# ignore the fact the type does not have a term
pass
return True


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 validate(schema):
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)
# TODO(enolfc): proper testing and attribute checking.
def _validate(obj, req, body, *args, **kwargs):
parsed_obj = req.parse()
if "kind" in schema:
try:
if schema["kind"].type_id != parsed_obj["kind"]:
raise exception.OCCISchemaMismatch(
expected=schema["kind"].type_id,
found=parsed_obj["kind"])
except KeyError:
raise exception.OCCIMissingType(
type_id=schema["kind"].type_id)
unmatched = copy.copy(parsed_obj["mixins"])
for m in schema.get("mixins", []):
for um in unmatched:
if compare_schemes(m, um):
unmatched[um] -= 1
break
else:
raise exception.OCCIMissingType(type_id=m.scheme)
for m in schema.get("optional_mixins", []):
for um in unmatched:
if compare_schemes(m, um):
unmatched[um] -= 1
unexpected = [m for m in unmatched if unmatched[m]]
if unexpected:
raise exception.OCCISchemaMismatch(expected="",
found=unexpected)
return f(obj, parsed_obj, req, body, *args, **kwargs)
return _validate
return accepts
47 changes: 34 additions & 13 deletions ooi/api/compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
import ooi.api.base
from ooi.occi.core import collection
from ooi.occi.infrastructure import compute
from ooi.occi.infrastructure import storage
from ooi.occi.infrastructure import storage_link
from ooi.openstack import contextualization
from ooi.openstack import helpers
from ooi.openstack import templates

Expand Down Expand Up @@ -64,30 +67,43 @@ def index(self, req):

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):
@ooi.api.validate({"kind": compute.ComputeResource.kind,
"mixins": [
templates.OpenStackOSTemplate,
templates.OpenStackResourceTemplate,
],
"optional_mixins": [
contextualization.user_data,
contextualization.public_key,
]
})
def create(self, obj, req, body):
tenant_id = req.environ["keystone.token_auth"].user.project_id
name = obj.get("occi.core.title", "OCCI VM")
image = obj["schemes"][templates.OpenStackOSTemplate.scheme][0]
flavor = obj["schemes"][templates.OpenStackResourceTemplate.scheme][0]
req_body = {"server": {
"name": name,
"imageRef": image,
"flavorRef": flavor,
}}
if contextualization.user_data.scheme in obj["schemes"]:
req_body["user_data"] = obj.get("org.openstack.compute.user_data")
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"]
"name": name,
"imageRef": image,
"flavorRef": flavor,
}}))
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"]
server["name"] = name
occi_compute_resources = self._get_compute_resources([server])

return collection.Collection(resources=occi_compute_resources)
Expand Down Expand Up @@ -124,8 +140,13 @@ def show(self, req, id):
cores=flavor["vcpus"],
hostname=s["name"],
memory=flavor["ram"],
state=helpers.occi_state(s["status"]),
state=helpers.vm_state(s["status"]),
mixins=[os_tpl, res_tpl])
# storage links
vols_attached = s.get("os-extended-volumes:volumes_attached", [])
for v in vols_attached:
st = storage.StorageResource(title="storage", id=v["id"])
comp._links.append(storage_link.StorageLink(comp, st))
return [comp]

def delete(self, req, id):
Expand Down
15 changes: 11 additions & 4 deletions ooi/api/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@
from ooi.occi.core import link
from ooi.occi.core import resource
from ooi.occi.infrastructure import compute
from ooi.occi.infrastructure import storage
from ooi.occi.infrastructure import storage_link
from ooi.occi.infrastructure import templates as infra_templates
from ooi.openstack import mixins
from ooi.openstack import contextualization
from ooi.openstack import templates


Expand Down Expand Up @@ -63,6 +65,11 @@ def index(self, req):
l.append(compute.ComputeResource.kind)
l.extend(compute.ComputeResource.actions)

# OCCI infra Storage
l.append(storage.StorageResource.kind)
l.append(storage_link.StorageLink.kind)
l.extend(storage.StorageResource.actions)

# OCCI infra mixins
l.append(infra_templates.os_tpl)
l.append(infra_templates.resource_tpl)
Expand All @@ -71,7 +78,7 @@ def index(self, req):
l.extend(self._resource_tpls(req))
l.extend(self._os_tpls(req))

# OpenStack mixins (contextualization)
l.append(mixins.user_data)
l.append(mixins.public_key)
# OpenStack Contextualization
l.append(contextualization.user_data)
l.append(contextualization.public_key)
return l
48 changes: 48 additions & 0 deletions ooi/api/storage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# -*- 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.api import base
from ooi.occi.core import collection
from ooi.occi.infrastructure import storage
from ooi.openstack import helpers


class Controller(base.Controller):
def index(self, req):
tenant_id = req.environ["keystone.token_auth"].user.project_id
req = self._get_req(req, path="/%s/os-volumes" % tenant_id)
response = req.get_response(self.app)
volumes = self.get_from_response(response, "volumes", [])
occi_storage_resources = []
if volumes:
for v in volumes:
s = storage.StorageResource(title=v["displayName"], id=v["id"])
occi_storage_resources.append(s)

return collection.Collection(resources=occi_storage_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/os-volumes/%s" % (tenant_id, id))
response = req.get_response(self.app)
v = self.get_from_response(response, "volume", {})

state = helpers.vol_state(v["status"])
st = storage.StorageResource(title=v["displayName"], id=v["id"],
size=v["size"], state=state)
return [st]
21 changes: 7 additions & 14 deletions ooi/exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,21 +94,14 @@ class NotImplemented(OCCIException):
code = 501


class HeaderNotFound(Invalid):
msg_fmt = "Header '%(header)s' not found."
class OCCIInvalidSchema(Invalid):
msg_fmt = "Found invalid schema: '%(msg)s'."


class HeaderValidation(Invalid):
"""Parent class for header validation error exceptions."""
class OCCIMissingType(Invalid):
msg_fmt = "Missing OCCI types: '%(type_id)s'."


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'."
class OCCISchemaMismatch(Invalid):
msg_fmt = ("Schema does not match. Expecting '%(expected)s', "
"but found '%(found)s'.")
4 changes: 2 additions & 2 deletions ooi/occi/core/link.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ class Link(entity.Entity):
kind = kind.Kind(helpers.build_scheme("core"), 'link', 'link',
attributes, 'link/')

def __init__(self, title, mixins, source, target):
super(Link, self).__init__(title, mixins)
def __init__(self, title, mixins, source, target, id=None):
super(Link, self).__init__(title, mixins, id)
self.attributes["occi.core.source"] = attribute.MutableAttribute(
"occi.core.source", source)
self.attributes["occi.core.target"] = attribute.MutableAttribute(
Expand Down
9 changes: 7 additions & 2 deletions ooi/occi/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@


def build_scheme(category, prefix=_PREFIX):
category = "%s#" % category
return urlparse.urljoin(prefix, category)
scheme = urlparse.urljoin(prefix, category)
return '%s#' % scheme


def check_type(obj_list, obj_type):
Expand All @@ -30,3 +30,8 @@ def check_type(obj_list, obj_type):

if not all([isinstance(i, obj_type) for i in obj_list]):
raise TypeError('object must be of class %s' % obj_type)


def decompose_type(type_id):
scheme, term = type_id.split('#', 1)
return '%s#' % scheme, term
Loading

0 comments on commit 2b9d92e

Please sign in to comment.