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):
mappings = []
for l in obj.get("links", {}).values():
if l["rel"] == storage.StorageResource.kind.type_id:
_, vol_id = ooi.api.helpers.get_id_with_kind(
req,
l.get("occi.core.target"),
storage.StorageResource.kind)
mapping = {
"source_type": "volume",
"uuid": vol_id,
"delete_on_termination": False,
}
try:
mapping['device_name'] = l['occi.storagelink.deviceid']
except KeyError:
pass
mappings.append(mapping)
links = obj.get("links", {})
for l in links.get(storage.StorageResource.kind.type_id, []):
_, vol_id = ooi.api.helpers.get_id_with_kind(
req,
l.get("target"),
storage.StorageResource.kind)
mapping = {
"source_type": "volume",
"uuid": vol_id,
"delete_on_termination": False,
}
try:
device_name = l['attributes']['occi.storagelink.deviceid']
mapping['device_name'] = device_name
except KeyError:
pass
mappings.append(mapping)
# this needs to be there if we have a mapping
if mappings:
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):
networks = []
for l in obj.get("links", {}).values():
if l["rel"] == network.NetworkResource.kind.type_id:
_, net_id = ooi.api.helpers.get_id_with_kind(
req,
l.get("occi.core.target"),
network.NetworkResource.kind)
net = {'uuid': net_id}
networks.append(net)
links = obj.get("links", {})
for l in links.get(network.NetworkResource.kind.type_id, []):
_, net_id = ooi.api.helpers.get_id_with_kind(
req,
l.get("target"),
network.NetworkResource.kind)
net = {'uuid': net_id}
networks.append(net)
return networks
def create(self, req, body):

View File

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

View File

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

View File

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

View File

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

View File

@ -148,7 +148,7 @@ class TextParser(BaseParser):
return attrs
def parse_links(self, headers):
links = {}
links = collections.defaultdict(list)
try:
header_links = headers["Link"]
except KeyError:
@ -158,15 +158,31 @@ class TextParser(BaseParser):
# remove the "<" and ">"
if ll[0][1] != "<" and ll[0][-1] != ">":
raise exception.OCCIInvalidSchema("Unable to parse link")
link_dest = ll[0][1:-1]
d = {}
link_id = ll[0][1:-1]
target_location = None
target_kind = None
attrs = {}
try:
for attr in ll[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:
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
def _convert_to_headers(self):
@ -224,11 +240,16 @@ class JsonParser(BaseParser):
return {}
def parse_links(self, obj):
links = {}
links = collections.defaultdict(list)
for l in obj.get("links", []):
attrs = copy.copy(l.get("attributes", {}))
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:
raise exception.OCCIInvalidSchema("Unable to parse link")
return links