Skip to content

Commit

Permalink
Merge 079b615 into a55edb3
Browse files Browse the repository at this point in the history
  • Loading branch information
alvarolopez committed Apr 27, 2015
2 parents a55edb3 + 079b615 commit 6a9bd79
Show file tree
Hide file tree
Showing 7 changed files with 312 additions and 91 deletions.
19 changes: 9 additions & 10 deletions ooi/api/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@

from ooi import utils

from oslo_log import log as logging
import webob.exc

LOG = logging.getLogger(__name__)


class Controller(object):
def __init__(self, app, openstack_version):
Expand Down Expand Up @@ -74,15 +77,6 @@ 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 Expand Up @@ -111,7 +105,12 @@ def exception_from_response(response):
503: webob.exc.HTTPServiceUnavailable,
}
code = response.status_int
message = response.json_body.popitem()[1].get("message")
try:
message = response.json_body.popitem()[1].get("message")
except Exception:
LOG.exception("Unknown error happenened processing response %s"
% response)
return webob.exc.HTTPInternalServerError

exc = exceptions.get(code, webob.exc.HTTPInternalServerError)
return exc(explanation=message)
30 changes: 18 additions & 12 deletions ooi/api/compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from ooi.occi.infrastructure import compute
from ooi.occi.infrastructure import storage
from ooi.occi.infrastructure import storage_link
from ooi.occi import validator as occi_validator
from ooi.openstack import contextualization
from ooi.openstack import helpers
from ooi.openstack import templates
Expand Down Expand Up @@ -66,19 +67,24 @@ def index(self, req):

return collection.Collection(resources=occi_compute_resources)

@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):
def create(self, req, body):
tenant_id = req.environ["keystone.token_auth"].user.project_id
parser = req.get_parser()(req.headers, req.body)
scheme = {
"category": compute.ComputeResource.kind,
"mixins": [
templates.OpenStackOSTemplate,
templates.OpenStackResourceTemplate,
],
"optional_mixins": [
contextualization.user_data,
contextualization.public_key,
]
}
obj = parser.parse()
validator = occi_validator.Validator(obj)
validator.validate(scheme)

name = obj.get("occi.core.title", "OCCI VM")
image = obj["schemes"][templates.OpenStackOSTemplate.scheme][0]
flavor = obj["schemes"][templates.OpenStackResourceTemplate.scheme][0]
Expand Down
84 changes: 84 additions & 0 deletions ooi/occi/validator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# -*- 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 exception
from ooi.occi import helpers


class Validator(object):
def __init__(self, obj):
self.parsed_obj = obj

def _validate_category(self, category):
try:
if category.type_id != self.parsed_obj["category"]:
raise exception.OCCISchemaMismatch(
expected=category.type_id,
found=self.parsed_obj["category"]
)
except KeyError:
raise exception.OCCIMissingType(
type_id=category.type_id)

def _compare_schemes(self, 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 _validate_mandatory_mixins(self, mixins, unmatched):
for m in mixins:
for um in unmatched:
if self._compare_schemes(m, um):
unmatched[um] -= 1
break
else:
# NOTE(aloga): I am not sure of this...
expected = m.scheme
if hasattr(m, "term"):
expected += m.term
raise exception.OCCIMissingType(type_id=expected)
return unmatched

def _validate_optional_mixins(self, mixins, unmatched):
for m in mixins:
for um in unmatched:
if self._compare_schemes(m, um):
unmatched[um] -= 1
break
return unmatched

def validate(self, schema):
if "category" in schema:
self._validate_category(schema["category"])
unmatched = copy.copy(self.parsed_obj["mixins"])
unmatched = self._validate_mandatory_mixins(
schema.get("mixins", []), unmatched)
unmatched = self._validate_optional_mixins(
schema.get("optional_mixins", []), unmatched)
unexpected = [m for m in unmatched if unmatched[m]]
if unexpected:
raise exception.OCCISchemaMismatch(expected="",
found=unexpected)
return True
8 changes: 4 additions & 4 deletions ooi/tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def test_kind(self):
'class="kind"')
parser = parsers.TextParser({}, body)
res = parser.parse()
self.assertEqual("http://example.com/scheme#foo", res["kind"])
self.assertEqual("http://example.com/scheme#foo", res["category"])
self.assertItemsEqual(["foo"],
res["schemes"]["http://example.com/scheme#"])
self.assertEqual({}, res["mixins"])
Expand Down Expand Up @@ -91,7 +91,7 @@ def test_kind(self):
}
parser = parsers.HeaderParser(headers, None)
res = parser.parse()
self.assertEqual("http://example.com/scheme#foo", res["kind"])
self.assertEqual("http://example.com/scheme#foo", res["category"])
self.assertItemsEqual(["foo"],
res["schemes"]["http://example.com/scheme#"])
self.assertEqual({}, res["mixins"])
Expand Down Expand Up @@ -142,9 +142,9 @@ def test_attributes(self):
'Category': ('foo; '
'scheme="http://example.com/scheme#"; '
'class="kind"'),
'X-OCCI-Attribute': ('foo="bar", baz=1234'),
'X-OCCI-Attribute': 'foo="bar", baz=1234, bazonk="foo=123"',
}
parser = parsers.HeaderParser(headers, None)
res = parser.parse()
expected_attrs = {"foo": "bar", "baz": "1234"}
expected_attrs = {"foo": "bar", "baz": "1234", "bazonk": "foo=123"}
self.assertEqual(expected_attrs, res["attributes"])
188 changes: 188 additions & 0 deletions ooi/tests/test_validator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
# -*- 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 mock

from ooi import exception
from ooi.occi import validator
from ooi.tests import base


class TestValidator(base.TestCase):
def test_category(self):
mixins = collections.Counter()
schemes = collections.defaultdict(list)
pobj = {
"kind": "compute",
"category": "foo type",
"mixins": mixins,
"schemes": schemes,
}
cat = mock.MagicMock()
cat.type_id = "foo type"

scheme = {"category": cat}

v = validator.Validator(pobj)
self.assertTrue(v.validate(scheme))

def test_category_missmatch(self):
mixins = collections.Counter()
schemes = collections.defaultdict(list)
pobj = {
"kind": "compute",
"category": "foo bar baz",
"mixins": mixins,
"schemes": schemes,
}
cat = mock.MagicMock()
cat.type_id = "foo type"

scheme = {"category": cat}

v = validator.Validator(pobj)
self.assertRaises(exception.OCCISchemaMismatch, v.validate, scheme)

def test_missing_category(self):
mixins = collections.Counter()
schemes = collections.defaultdict(list)
pobj = {
"kind": "compute",
"mixins": mixins,
"schemes": schemes,
}
cat = mock.MagicMock()
cat.type_id = "foo type"

scheme = {"category": cat}

v = validator.Validator(pobj)
self.assertRaises(exception.OCCIMissingType, v.validate, scheme)

def test_mixins(self):
mixins = collections.Counter()
schemes = collections.defaultdict(list)
mixins["http://example.com/scheme#foo"] += 1
schemes["http://example.com/scheme#"].append("foo")
pobj = {
"kind": "compute",
"category": "foo type",
"mixins": mixins,
"schemes": schemes,
}

mixin = mock.MagicMock()
scheme = {"mixins": [mixin]}

mixin.scheme = "http://example.com/scheme#"
mixin.term = "foo"
v = validator.Validator(pobj)
self.assertTrue(v.validate(scheme))

def test_optional_mixins(self):
mixins = collections.Counter()
schemes = collections.defaultdict(list)
mixins["http://example.com/scheme#foo"] += 1
schemes["http://example.com/scheme#"].append("foo")
pobj = {
"kind": "compute",
"category": "foo type",
"mixins": mixins,
"schemes": schemes,
}

mixin = mock.MagicMock()
mixin.scheme = "http://example.com/scheme#"
mixin.term = "foo"
scheme = {"optional_mixins": [mixin]}

v = validator.Validator(pobj)
self.assertTrue(v.validate(scheme))

def test_mixin_schema_mismatch(self):
mixins = collections.Counter()
schemes = collections.defaultdict(list)
mixins["http://example.com/scheme#foo"] += 1
schemes["http://example.com/scheme#"].append("foo")
pobj = {
"kind": "compute",
"category": "foo type",
"mixins": mixins,
"schemes": schemes,
}

mixin = mock.MagicMock()
mixin.scheme = "http://example.com/foo#"
mixin.term = "foo"
scheme = {"mixins": [mixin]}

v = validator.Validator(pobj)
self.assertRaises(exception.OCCIMissingType, v.validate, scheme)

def test_mixin_schema_mismatch_term(self):
mixins = collections.Counter()
schemes = collections.defaultdict(list)
mixins["http://example.com/scheme#foo"] += 1
schemes["http://example.com/scheme#"].append("foo")
pobj = {
"kind": "compute",
"category": "foo type",
"mixins": mixins,
"schemes": schemes,
}

mixin = mock.MagicMock()
mixin.scheme = "http://example.com/scheme#"
mixin.term = "bar"
scheme = {"mixins": [mixin]}

v = validator.Validator(pobj)
self.assertRaises(exception.OCCIMissingType, v.validate, scheme)

def test_no_optional_mixins(self):
mixins = collections.Counter()
schemes = collections.defaultdict(list)
pobj = {
"kind": "compute",
"category": "foo type",
"mixins": mixins,
"schemes": schemes,
}

mixin = mock.MagicMock()
mixin.scheme = "http://example.com/scheme#"
mixin.term = "foo"
scheme = {"optional_mixins": [mixin]}

v = validator.Validator(pobj)
self.assertTrue(v.validate(scheme))

def test_something_unexpected(self):
mixins = collections.Counter()
schemes = collections.defaultdict(list)
mixins["http://example.com/scheme#foo"] += 1
schemes["http://example.com/scheme#"].append("foo")
pobj = {
"kind": "compute",
"category": "foo type",
"mixins": mixins,
"schemes": schemes,
}

v = validator.Validator(pobj)
self.assertRaises(exception.OCCISchemaMismatch, v.validate, {})
Loading

0 comments on commit 6a9bd79

Please sign in to comment.