Update link handling in parser

Change the link structure returned by the parser to better follow the
OCCI 1.2 standard. The parser returns a dictionary where keys are the
link target kind type and values are lists with dictionaries for each
of the links of the links defined for that kind type. Those dictionaries
contain the target location (occi.core.target), the link id if defined,
and a dictionary with any extra attributes defined.

Change-Id: I24205044a6e2b670ceb98f3a0e9224637bf9fcf4
This commit is contained in:
Enol Fernandez 2016-06-15 10:56:21 +02:00 committed by Enol Fernández
parent a74fdd5dd5
commit db9dae4a26
6 changed files with 120 additions and 83 deletions

View File

@ -97,22 +97,23 @@ class Controller(ooi.api.base.Controller):
def _build_block_mapping(self, req, obj): def _build_block_mapping(self, req, obj):
mappings = [] mappings = []
for l in obj.get("links", {}).values(): links = obj.get("links", {})
if l["rel"] == storage.StorageResource.kind.type_id: for l in links.get(storage.StorageResource.kind.type_id, []):
_, vol_id = ooi.api.helpers.get_id_with_kind( _, vol_id = ooi.api.helpers.get_id_with_kind(
req, req,
l.get("occi.core.target"), l.get("target"),
storage.StorageResource.kind) storage.StorageResource.kind)
mapping = { mapping = {
"source_type": "volume", "source_type": "volume",
"uuid": vol_id, "uuid": vol_id,
"delete_on_termination": False, "delete_on_termination": False,
} }
try: try:
mapping['device_name'] = l['occi.storagelink.deviceid'] device_name = l['attributes']['occi.storagelink.deviceid']
except KeyError: mapping['device_name'] = device_name
pass except KeyError:
mappings.append(mapping) pass
mappings.append(mapping)
# this needs to be there if we have a mapping # this needs to be there if we have a mapping
if mappings: if mappings:
image = obj["schemes"][templates.OpenStackOSTemplate.scheme][0] image = obj["schemes"][templates.OpenStackOSTemplate.scheme][0]
@ -127,14 +128,14 @@ class Controller(ooi.api.base.Controller):
def _get_network_from_req(self, req, obj): def _get_network_from_req(self, req, obj):
networks = [] networks = []
for l in obj.get("links", {}).values(): links = obj.get("links", {})
if l["rel"] == network.NetworkResource.kind.type_id: for l in links.get(network.NetworkResource.kind.type_id, []):
_, net_id = ooi.api.helpers.get_id_with_kind( _, net_id = ooi.api.helpers.get_id_with_kind(
req, req,
l.get("occi.core.target"), l.get("target"),
network.NetworkResource.kind) network.NetworkResource.kind)
net = {'uuid': net_id} net = {'uuid': net_id}
networks.append(net) networks.append(net)
return networks return networks
def create(self, req, body): def create(self, req, body):

View File

@ -68,18 +68,14 @@ class Validator(object):
return unmatched return unmatched
def _validate_optional_links(self, expected, links): def _validate_optional_links(self, expected, links):
for uri, l in links.items(): expected_types = [e.type_id for e in expected]
try: for l in links.keys():
rel = l['rel'] if l in expected_types:
except KeyError: break
raise exception.OCCIMissingType(type_id=uri)
for ex in expected:
if rel == ex.type_id:
break
else: else:
expected_types = ', '.join([e.type_id for e in expected]) raise exception.OCCISchemaMismatch(
raise exception.OCCISchemaMismatch(expected=expected_types, expected=', '.join(expected_types),
found=l['rel']) found=l)
def validate_attributes(self, required): def validate_attributes(self, required):
"""Validate required attributes """Validate required attributes

View File

@ -389,7 +389,7 @@ class TestComputeController(base.TestController):
self.assertEqual([], ret) self.assertEqual([], ret)
def test_build_block_mapping_invalid_rel(self): def test_build_block_mapping_invalid_rel(self):
obj = {'links': {'foo': {'rel': 'bar'}}} obj = {"links": {"foo": [{"target": "bar"}]}}
ret = self.controller._build_block_mapping(None, obj) ret = self.controller._build_block_mapping(None, obj)
self.assertEqual([], ret) self.assertEqual([], ret)
@ -398,12 +398,13 @@ class TestComputeController(base.TestController):
vol_id = uuid.uuid4().hex vol_id = uuid.uuid4().hex
image_id = uuid.uuid4().hex image_id = uuid.uuid4().hex
obj = { obj = {
'links': { "links": {
'l1': { "http://schemas.ogf.org/occi/infrastructure#storage": [
'rel': ('http://schemas.ogf.org/occi/infrastructure#' {
'storage'), "id": "l1",
'occi.core.target': vol_id, "target": vol_id,
} }
]
}, },
"schemes": { "schemes": {
templates.OpenStackOSTemplate.scheme: [image_id], templates.OpenStackOSTemplate.scheme: [image_id],
@ -434,13 +435,16 @@ class TestComputeController(base.TestController):
vol_id = uuid.uuid4().hex vol_id = uuid.uuid4().hex
image_id = uuid.uuid4().hex image_id = uuid.uuid4().hex
obj = { obj = {
'links': { "links": {
'l1': { "http://schemas.ogf.org/occi/infrastructure#storage": [
'rel': ('http://schemas.ogf.org/occi/infrastructure#' {
'storage'), "id": "l1",
'occi.core.target': vol_id, "target": vol_id,
'occi.storagelink.deviceid': 'baz' "attributes": {
} "occi.storagelink.deviceid": "baz"
}
}
]
}, },
"schemes": { "schemes": {
templates.OpenStackOSTemplate.scheme: [image_id], templates.OpenStackOSTemplate.scheme: [image_id],
@ -505,12 +509,14 @@ class TestComputeController(base.TestController):
def test_get_network_from_req(self, m_get_id): def test_get_network_from_req(self, m_get_id):
net_id = uuid.uuid4().hex net_id = uuid.uuid4().hex
obj = { obj = {
'links': { "links": {
'l1': { "%s%s" % (occi_network.NetworkResource.kind.scheme,
'rel': '%s%s' % (occi_network.NetworkResource.kind.scheme, occi_network.NetworkResource.kind.term): [
occi_network.NetworkResource.kind.term), {
'occi.core.target': net_id, "id": "l1",
} "target": net_id,
}
]
}, },
} }
m_get_id.return_value = (None, net_id) m_get_id.return_value = (None, net_id)
@ -529,17 +535,18 @@ class TestComputeController(base.TestController):
net_id_1 = uuid.uuid4().hex net_id_1 = uuid.uuid4().hex
net_id_2 = uuid.uuid4().hex net_id_2 = uuid.uuid4().hex
obj = { obj = {
'links': { "links": {
'l1': { "%s%s" % (occi_network.NetworkResource.kind.scheme,
'rel': '%s%s' % (occi_network.NetworkResource.kind.scheme, occi_network.NetworkResource.kind.term): [
occi_network.NetworkResource.kind.term), {
'occi.core.target': net_id_1, "id": "l1",
}, "target": net_id_1,
'l2': { },
'rel': '%s%s' % (occi_network.NetworkResource.kind.scheme, {
occi_network.NetworkResource.kind.term), "id": "l2",
'occi.core.target': net_id_2, "target": net_id_2,
} }
]
}, },
} }
m_get_id.side_effect = [(None, net_id_1), m_get_id.side_effect = [(None, net_id_1),

View File

@ -73,16 +73,27 @@ class BaseParserTest(object):
self.assertEqual(expected_attrs, res["attributes"]) self.assertEqual(expected_attrs, res["attributes"])
def test_link(self): def test_link(self):
attrs = {"foo": 1234, "bazonk": "foo=123"}
h, b = self.get_test_link( h, b = self.get_test_link(
{"term": "foo", "scheme": "http://example.com/scheme#"}, {"term": "foo", "scheme": "http://example.com/scheme#"},
{ {
"id": "bar", "id": "link_id",
"attributes": {"foo": 1234, "bazonk": "foo=123"} "target": "/bar",
"kind": "http://example.com/scheme#link",
"attributes": attrs,
} }
) )
parser = self._get_parser(h, b) parser = self._get_parser(h, b)
res = parser.parse() res = parser.parse()
expected_links = {"bar": {"foo": 1234, "bazonk": "foo=123"}} expected_links = {
"http://example.com/scheme#link": [
{
"id": "link_id",
"target": "/bar",
"attributes": attrs,
}
]
}
self.assertEqual(expected_links, res["links"]) self.assertEqual(expected_links, res["links"])
@ -124,7 +135,8 @@ class TestHeaderParser(BaseParserTest, base.TestCase):
def get_test_link(self, kind, link): def get_test_link(self, kind, link):
h, b = self.get_test_kind(kind) h, b = self.get_test_kind(kind)
l = ["<%(id)s>" % link] l = [('<%(id)s>; "rel"="%(kind)s"; '
'occi.core.target="%(target)s"') % link]
for n, v in link["attributes"].items(): for n, v in link["attributes"].items():
l.append('"%s"=%s' % (n, self._get_attribute_value(v))) l.append('"%s"=%s' % (n, self._get_attribute_value(v)))
h["Link"] = "; ".join(l) h["Link"] = "; ".join(l)
@ -207,8 +219,8 @@ class TestJsonParser(BaseParserTest, base.TestCase):
attrs = [] attrs = []
for n, v in link["attributes"].items(): for n, v in link["attributes"].items():
attrs.append('"%s": %s' % (n, self._get_attribute_value(v))) attrs.append('"%s": %s' % (n, self._get_attribute_value(v)))
target = '"location": "%s"' % link["id"] target = '"location": "%(target)s", "kind": "%(kind)s"' % link
l = ('"links": [{"attributes": { %s }, "target": { %s } }]' l = ('"links": [{"attributes": { %s }, "target": { %s }, "id": "%s" }]'
% (",".join(attrs), target)) % (",".join(attrs), target, link["id"]))
body.append(l) body.append(l)
return {}, "{ %s }" % ",".join(body) return {}, "{ %s }" % ",".join(body)

View File

@ -188,7 +188,7 @@ class TestValidator(base.TestCase):
def test_optional_links(self): def test_optional_links(self):
mixins = collections.Counter() mixins = collections.Counter()
schemes = collections.defaultdict(list) schemes = collections.defaultdict(list)
links = {"foo": {"rel": "http://example.com/scheme#foo"}} links = {"http://example.com/scheme#foo": [{"location": "foo"}]}
pobj = { pobj = {
"kind": "compute", "kind": "compute",
"category": "foo type", "category": "foo type",
@ -205,7 +205,7 @@ class TestValidator(base.TestCase):
def test_optional_links_invalid(self): def test_optional_links_invalid(self):
mixins = collections.Counter() mixins = collections.Counter()
schemes = collections.defaultdict(list) schemes = collections.defaultdict(list)
links = {"foo": {"rel": "http://example.com/scheme#foo"}} links = {"http://example.com/scheme#foo": [{"location": "foo"}]}
pobj = { pobj = {
"kind": "compute", "kind": "compute",
"category": "foo type", "category": "foo type",

View File

@ -148,7 +148,7 @@ class TextParser(BaseParser):
return attrs return attrs
def parse_links(self, headers): def parse_links(self, headers):
links = {} links = collections.defaultdict(list)
try: try:
header_links = headers["Link"] header_links = headers["Link"]
except KeyError: except KeyError:
@ -158,15 +158,31 @@ class TextParser(BaseParser):
# remove the "<" and ">" # remove the "<" and ">"
if ll[0][1] != "<" and ll[0][-1] != ">": if ll[0][1] != "<" and ll[0][-1] != ">":
raise exception.OCCIInvalidSchema("Unable to parse link") raise exception.OCCIInvalidSchema("Unable to parse link")
link_dest = ll[0][1:-1] link_id = ll[0][1:-1]
d = {} target_location = None
target_kind = None
attrs = {}
try: try:
for attr in ll[1:]: for attr in ll[1:]:
n, v = attr.split("=", 1) n, v = attr.split("=", 1)
d[n.strip().strip('"')] = self.parse_attribute_value(v) n = n.strip().strip('"')
v = self.parse_attribute_value(v)
if n == "rel":
target_kind = v
continue
elif n == "occi.core.target":
target_location = v
continue
attrs[n] = v
except ValueError: except ValueError:
raise exception.OCCIInvalidSchema("Unable to parse link") raise exception.OCCIInvalidSchema("Unable to parse link")
links[link_dest] = d if not (target_kind and target_location):
raise exception.OCCIInvalidSchema("Unable to parse link")
links[target_kind].append({
"target": target_location,
"attributes": attrs,
"id": link_id,
})
return links return links
def _convert_to_headers(self): def _convert_to_headers(self):
@ -224,11 +240,16 @@ class JsonParser(BaseParser):
return {} return {}
def parse_links(self, obj): def parse_links(self, obj):
links = {} links = collections.defaultdict(list)
for l in obj.get("links", []): for l in obj.get("links", []):
attrs = copy.copy(l.get("attributes", {}))
try: try:
links[l["target"]["location"]] = attrs d = {
"target": l["target"]["location"],
"attributes": copy.copy(l.get("attributes", {})),
}
if "id" in l:
d["id"] = l["id"]
links[l["target"]["kind"]].append(d)
except KeyError: except KeyError:
raise exception.OCCIInvalidSchema("Unable to parse link") raise exception.OCCIInvalidSchema("Unable to parse link")
return links return links