Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Contextualization #14

Merged
merged 14 commits into from
Apr 16, 2015
107 changes: 0 additions & 107 deletions ooi/api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,107 +0,0 @@
# -*- 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 collections
import shlex

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
9 changes: 9 additions & 0 deletions ooi/api/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,15 @@ def get_from_response(response, element, default):
else:
raise exception_from_response(response)

@staticmethod
def validate(schema):
def accepts(f):
def _validate(obj, req, body, *args, **kwargs):
parsed_obj = req.validate(schema)
return f(obj, parsed_obj, req, body, *args, **kwargs)
return _validate
return accepts


def exception_from_response(response):
"""Convert an OpenStack V2 Fault into a webob exception.
Expand Down
53 changes: 36 additions & 17 deletions ooi/api/compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@

import json

import ooi.api
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 +66,42 @@ 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.base.Controller.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")
# TODO(enolfc): add here the correct metadata info
# if contextualization.public_key.scheme in obj["schemes"]:
# req_body["metadata"] = XXX
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"]
}}))
body=json.dumps(req_body))
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 +138,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