Implementation of the OCCI v1.1 Core Model

Set of classes that represent the OCCI Core Model as described in the
GFD.183 – OCCI Core (v1.1) [1], along with some unit tests for the
model.

[1] http://ogf.org/documents/GFD.183.pdf
This commit is contained in:
Alvaro Lopez Garcia 2015-02-26 17:47:13 +01:00
parent 68d53fdbd0
commit 414e0c3fbe
12 changed files with 669 additions and 0 deletions

0
ooi/occi/__init__.py Normal file
View File

View File

26
ooi/occi/core/action.py Normal file
View File

@ -0,0 +1,26 @@
# -*- 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.occi.core import category
class Action(category.Category):
"""OCCI Action.
An Action represents an invocable operation applicable to a resource
instance.
"""
pass

View File

@ -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.
import abc
import six
@six.add_metaclass(abc.ABCMeta)
class Attribute(object):
def __init__(self, name, value):
self._name = name
self._value = value
@property
def name(self):
return self._name
@property
def value(self):
return self._value
class MutableAttribute(Attribute):
@Attribute.value.setter
def value(self, value):
self._value = value
class InmutableAttribute(Attribute):
pass
class AttributeCollection(object):
pass

52
ooi/occi/core/category.py Normal file
View File

@ -0,0 +1,52 @@
# -*- 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.occi.core import attribute
from ooi.occi import helpers
class Category(object):
"""OCCI Category."""
def __init__(self, scheme, term, title, attributes=[], location=None):
self._scheme = scheme
self._term = term
self._title = title
helpers.check_type(attributes, attribute.Attribute)
self._attributes = dict([(a.name, a) for a in attributes])
self._location = location
@property
def scheme(self):
return self._scheme
@property
def term(self):
return self._term
@property
def title(self):
return self._title
@property
def attributes(self):
return self._attributes
@property
def location(self):
return self._location

65
ooi/occi/core/entity.py Normal file
View File

@ -0,0 +1,65 @@
# -*- 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 abc
import six
from ooi.occi import helpers
from ooi.occi.core import attribute
from ooi.occi.core import kind
from ooi.occi.core import mixin
@six.add_metaclass(abc.ABCMeta)
class Entity(object):
"""OCCI Entity.
Entity is an abstract type, which both Resource and Link inherit. Each
sub-type of Entity is identified by a unique Kind instance
"""
def __init__(self, id, title, mixins):
helpers.check_type(mixins, mixin.Mixin)
self.mixins = mixins
self._attributes = {
"occi.core.id": attribute.InmutableAttribute("occi.core.id", id),
"occi.core.title": attribute.MutableAttribute("occi.core.title", title)
}
self._kind = kind.Kind(helpers.build_schema('core'), 'entity', 'entity',
self._attributes.values(), '/entity/')
@property
def kind(self):
return self._kind
@property
def attributes(self):
return self._attributes
@property
def id(self):
return self._attributes["occi.core.id"].value
@property
def title(self):
return self._attributes["occi.core.title"].value
@title.setter
def title(self, value):
self._attributes["occi.core.title"].value = value

39
ooi/occi/core/kind.py Normal file
View File

@ -0,0 +1,39 @@
# -*- 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.occi.core import action
from ooi.occi.core import category
from ooi.occi import helpers
class Kind(category.Category):
"""OCCI Kind.
The Kind type is the core of the type classification system built into the
OCCI Core Model. Kind is a specialisation of Category and introduces
additional resource capabilities in terms of Actions.
"""
def __init__(self, scheme, term, title, attributes=[], location=None,
related=[], actions=[]):
super(Kind, self).__init__(scheme, term, title, attributes=attributes,
location=location)
helpers.check_type(related, Kind)
helpers.check_type(actions, action.Action)
self.related = related
self.actions = actions

56
ooi/occi/core/link.py Normal file
View File

@ -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.
from ooi.occi import helpers
from ooi.occi.core import attribute
from ooi.occi.core import entity
from ooi.occi.core import kind
class Link(entity.Entity):
"""OCCI Resoure.
The Resource type is complemented by the Link type which associates one
Resource instance with another.
"""
def __init__(self, id, title, mixins, source, target):
super(Link, self).__init__(id, title, mixins)
cls_attrs = {
"occi.core.source": attribute.MutableAttribute("occi.core.source", source),
"occi.core.target": attribute.MutableAttribute("occi.core.target", target),
}
self._attributes.update(cls_attrs)
self._kind = kind.Kind(helpers.build_schema("core"), 'link', 'link', self._attributes.values(), '/link/')
@property
def source(self):
return self._attributes["occi.core.source"].value
@source.setter
def source(self, value):
self._attributes["occi.core.source"].value = value
@property
def target(self):
return self._attributes["occi.core.target"].value
@target.setter
def target(self, value):
self._attributes["occi.core.target"].value = value

39
ooi/occi/core/mixin.py Normal file
View File

@ -0,0 +1,39 @@
# -*- 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.occi.core import action
from ooi.occi.core import category
from ooi.occi import helpers
class Mixin(category.Category):
"""OCCI Mixin.
An instance of Mixin can be associated with a resource instance, i.e. a
sub-type of Entity, to "mix-in" additional resource capabilities at
run-time.
"""
def __init__(self, scheme, term, title, attributes=[], location=None,
related=[], actions=[]):
super(Mixin, self).__init__(scheme, term, title, attributes=attributes,
location=location)
helpers.check_type(related, Mixin)
helpers.check_type(actions, action.Action)
self.related = related
self.actions = actions

65
ooi/occi/core/resource.py Normal file
View File

@ -0,0 +1,65 @@
# -*- 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 uuid
from ooi.occi import helpers
from ooi.occi.core import attribute
from ooi.occi.core import entity
from ooi.occi.core import kind
from ooi.occi.core import link
class Resource(entity.Entity):
"""OCCI Resource.
The heart of the OCCI Core Model is the Resource type. Any resource exposed
through OCCI is a Resource or a sub-type thereof. A resource can be e.g. a
virtual machine, a job in a job submission system, a user, etc.
The Resource type is complemented by the Link type which associates one
Resource instance with another. The Link type contains a number of common
attributes that Link sub-types inherit.
"""
def __init__(self, id, title, mixins, summary):
super(Resource, self).__init__(id, title, mixins)
cls_attrs = {
"occi.core.summary": attribute.MutableAttribute("occi.core.summary", summary),
}
self._attributes.update(cls_attrs)
self._kind = kind.Kind(helpers.build_schema('core'), 'resource', 'resource',
self._attributes.values(), '/resource/')
self._links = []
@property
def links(self):
return self._links
def link(self, target, mixins=[]):
l = link.Link(uuid.uuid4().hex, "", mixins, self, target)
self._links.append(l)
@property
def summary(self):
return self._attributes["occi.core.summary"].value
@summary.setter
def summary(self, value):
self._attributes["occi.core.summary"].value = value

32
ooi/occi/helpers.py Normal file
View File

@ -0,0 +1,32 @@
# -*- 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 urlparse
_PREFIX = "http://schemas.ogf.org/occi/"
def build_schema(category):
category = "%s#" % category
return urlparse.urljoin(_PREFIX, category)
def check_type(obj_list, obj_type):
if not isinstance(obj_list, list):
raise TypeError('must be a list of objects')
if not all([isinstance(i, obj_type) for i in obj_list]):
raise TypeError('object must be of class %s' % obj_type)

247
ooi/tests/test_occi.py Normal file
View File

@ -0,0 +1,247 @@
# -*- 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.occi.core import action
from ooi.occi.core import attribute
from ooi.occi.core import category
from ooi.occi.core import entity
from ooi.occi.core import kind
from ooi.occi.core import link
from ooi.occi.core import mixin
from ooi.occi.core import resource
from ooi.tests import base
class TestAttributes(base.TestCase):
def test_base(self):
attr = attribute.Attribute("occi.foo.bar", "crap")
self.assertEqual("crap", attr.value)
def test_mutable(self):
attr = attribute.MutableAttribute("occi.foo.bar", "crap")
attr.value = "bazonk"
self.assertEqual("bazonk", attr.value)
def test_inmutable(self):
attr = attribute.InmutableAttribute("occi.foo.bar", "crap")
def set_val():
attr.value = "bazonk"
self.assertRaises(AttributeError, set_val)
class TestCoreOCCICategory(base.TestCase):
args = ("scheme", "term", "title")
obj = category.Category
def test_obj(self):
cat = self.obj(*self.args)
for i in self.args:
self.assertEqual(i, getattr(cat, i))
def test_attributes(self):
attr = attribute.MutableAttribute("occi.foo.bar", "crap")
cat = self.obj(*self.args, attributes=[attr])
self.assertEqual({"occi.foo.bar": attr}, cat.attributes)
def test_attributes_empty(self):
cat = self.obj(*self.args, attributes=[])
self.assertEqual({}, cat.attributes)
def test_attributes_invalid(self):
self.assertRaises(TypeError,
self.obj,
*self.args,
attributes=None)
def test_attributes_invalid_list(self):
self.assertRaises(TypeError,
self.obj,
*self.args,
attributes=[None])
class TestCoreOCCIKind(TestCoreOCCICategory):
obj = kind.Kind
def setUp(self):
super(TestCoreOCCIKind, self).setUp()
def test_obj(self):
k = self.obj(*self.args)
for i in (self.args):
self.assertEqual(i, getattr(k, i))
def test_actions(self):
actions = [action.Action(None, None, None)]
kind = self.obj(*self.args, actions=actions)
for i in (self.args):
self.assertEqual(i, getattr(kind, i))
self.assertEqual(actions, kind.actions)
def test_actions_empty(self):
actions = []
kind = self.obj(*self.args, actions=actions)
for i in (self.args):
self.assertEqual(i, getattr(kind, i))
self.assertEqual(actions, kind.actions)
def test_actions_invalid(self):
actions = None
self.assertRaises(TypeError,
self.obj,
*self.args,
actions=actions)
def test_actions_invalid_list(self):
actions = [None]
self.assertRaises(TypeError,
self.obj,
*self.args,
actions=actions)
def test_related(self):
related = [self.obj(None, None, None)]
kind = self.obj(*self.args, related=related)
for i in (self.args):
self.assertEqual(i, getattr(kind, i))
self.assertEqual(related, kind.related)
def test_related_empty(self):
related = []
kind = self.obj(*self.args, related=related)
for i in (self.args):
self.assertEqual(i, getattr(kind, i))
self.assertEqual(related, kind.related)
def test_related_invalid(self):
related = None
self.assertRaises(TypeError,
self.obj,
*self.args,
related=related)
def test_related_invalid_list(self):
related = [None]
self.assertRaises(TypeError,
self.obj,
*self.args,
related=related)
class TestCoreOCCIMixin(TestCoreOCCIKind):
obj = mixin.Mixin
class TestCoreOCCIAction(TestCoreOCCICategory):
obj = action.Action
class TestCoreOCCIEntity(base.TestCase):
def test_entity(self):
e = entity.Entity("foo", "bar", [])
self.assertIsInstance(e.kind, kind.Kind)
self.assertIn("occi.core.id", e.attributes)
self.assertIn("occi.core.title", e.attributes)
self.assertEqual("foo", e.attributes["occi.core.id"].value)
self.assertEqual("foo", e.id)
self.assertIs(e.id, e.attributes["occi.core.id"].value)
def set_attr():
e.attributes["occi.core.id"].value = "foo"
self.assertRaises(AttributeError, set_attr)
def set_attr_directly():
e.id = "foo"
self.assertRaises(AttributeError, set_attr_directly)
e.title = "baz"
self.assertEqual("baz", e.attributes["occi.core.title"].value)
self.assertEqual("baz", e.title)
self.assertIs(e.title, e.attributes["occi.core.title"].value)
e.attributes["occi.core.title"].value = "bar"
self.assertEqual("bar", e.attributes["occi.core.title"].value)
self.assertEqual("bar", e.title)
self.assertIs(e.title, e.attributes["occi.core.title"].value)
class TestCoreOCCIResource(base.TestCase):
def test_resource(self):
r = resource.Resource("foo", "bar", [], "baz")
self.assertIsInstance(r.kind, kind.Kind)
self.assertEqual("resource", r.kind.term)
self.assertEqual("foo", r.id)
self.assertEqual("bar", r.title)
self.assertEqual("baz", r.summary)
r.summary = "bazonk"
self.assertEqual("bazonk", r.summary)
def test_valid_link(self):
r1 = resource.Resource(None, None, [], None)
r2 = resource.Resource(None, None, [], None)
r1.link(r2)
self.assertIsInstance(r1.links[0], link.Link)
self.assertIs(r1, r1.links[0].source)
self.assertIs(r2, r1.links[0].target)
def test_mixins(self):
m = mixin.Mixin(None, None, None)
r = resource.Resource(None, None, [m], [])
self.assertIsInstance(r.kind, kind.Kind)
self.assertEqual([m], r.mixins)
def test_invalid_mixins(self):
self.assertRaises(TypeError,
resource.Resource,
None, None, ["foo"], None)
class TestCoreOCCILink(base.TestCase):
def test_correct_link(self):
resource_1 = resource.Resource(None, None, [], None)
resource_2 = resource.Resource(None, None, [], None)
resource_3 = resource.Resource(None, None, [], None)
l = link.Link(None, None, [], resource_1, resource_2)
self.assertIsInstance(l.kind, kind.Kind)
self.assertEqual("link", l.kind.term)
self.assertIs(resource_1, l.source)
self.assertIs(resource_2, l.target)
self.assertIs(resource_1, l.attributes["occi.core.source"].value)
self.assertIs(resource_1, l.source)
self.assertIs(resource_1, l.attributes["occi.core.source"].value)
self.assertIs(resource_2, l.attributes["occi.core.target"].value)
self.assertIs(resource_2, l.target)
self.assertIs(resource_2, l.attributes["occi.core.target"].value)
l.source = resource_3
self.assertIs(resource_3, l.attributes["occi.core.source"].value)
self.assertIs(resource_3, l.source)
self.assertIs(resource_3, l.attributes["occi.core.source"].value)
l.target = resource_1
self.assertIs(resource_1, l.target)
self.assertIs(resource_1, l.attributes["occi.core.target"].value)
self.assertIs(resource_1, l.attributes["occi.core.target"].value)