diff --git a/ooi/api/compute.py b/ooi/api/compute.py index f2a8efd..bdd4abb 100644 --- a/ooi/api/compute.py +++ b/ooi/api/compute.py @@ -17,15 +17,32 @@ import json import ooi.api.base +import ooi.api.network as network_api from ooi.occi.core import collection from ooi.occi.infrastructure import compute +from ooi.occi.infrastructure import network 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 network as os_network from ooi.openstack import templates +def _create_network_link(addr, comp, floating_ips): + if addr["OS-EXT-IPS:type"] == "floating": + for ip in floating_ips: + if addr["addr"] == ip["ip"]: + net = network.NetworkResource( + title="network", + id="%s/%s" % (network_api.FLOATING_PREFIX, ip["pool"])) + else: + net = network.NetworkResource(title="network", id="fixed") + return os_network.OSNetworkInterface(comp, net, + addr["OS-EXT-IPS-MAC:mac_addr"], + addr["addr"]) + + class Controller(ooi.api.base.Controller): def _get_compute_resources(self, servers): occi_compute_resources = [] @@ -133,7 +150,6 @@ def show(self, req, id): image["name"]) # build the compute object - # TODO(enolfc): link to network + storage comp = compute.ComputeResource(title=s["name"], id=s["id"], cores=flavor["vcpus"], hostname=s["name"], @@ -144,7 +160,19 @@ def show(self, req, id): 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)) + comp.add_link(storage_link.StorageLink(comp, st)) + + # network links + addresses = s.get("addresses", {}) + if addresses: + req = self._get_req(req, path="/%s/os-floating-ips" % tenant_id) + response = req.get_response(self.app) + floating_ips = self.get_from_response(response, "floating_ips", []) + for addr_type in addresses.values(): + for addr in addr_type: + comp.add_link(_create_network_link(addr, comp, + floating_ips)) + return [comp] def delete(self, req, id): diff --git a/ooi/api/network.py b/ooi/api/network.py new file mode 100644 index 0000000..87ef90d --- /dev/null +++ b/ooi/api/network.py @@ -0,0 +1,87 @@ +# -*- 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 webob.exc + +from ooi.api import base +from ooi.occi.core import collection +from ooi.occi.infrastructure import network + +FLOATING_PREFIX = "floating" + + +def _build_network(name, prefix=None): + if prefix: + network_id = '/'.join([prefix, name]) + else: + network_id = name + return network.NetworkResource(title=name, + summary="A OpenStack IP Pool", + id=network_id, + state="active", + mixins=[network.ip_network]) + + +class NetworkController(base.Controller): + def index(self, req): + occi_network_resources = [] + occi_network_resources.append(_build_network("fixed")) + + tenant_id = req.environ["keystone.token_auth"].user.project_id + + req = self._get_req(req, path="/%s/os-floating-ip-pools" % tenant_id) + response = req.get_response(self.app) + pools = self.get_from_response(response, "floating_ip_pools", []) + + for p in pools: + occi_network_resources.append(_build_network(p["name"], + FLOATING_PREFIX)) + + return collection.Collection(resources=occi_network_resources) + + def show(self, id, req): + if id != "fixed": + raise webob.exc.HTTPNotFound() + return _build_network("fixed") + + +class PoolController(base.Controller): + def index(self, req): + tenant_id = req.environ["keystone.token_auth"].user.project_id + + req = self._get_req(req, path="/%s/os-floating-ip-pools" % tenant_id) + response = req.get_response(self.app) + pools = self.get_from_response(response, "floating_ip_pools", []) + + occi_network_resources = [] + for p in pools: + occi_network_resources.append(_build_network(p["name"], + FLOATING_PREFIX)) + + return collection.Collection(resources=occi_network_resources) + + def show(self, req, id): + tenant_id = req.environ["keystone.token_auth"].user.project_id + + # get info from server + req = self._get_req(req, path="/%s/os-floating-ip-pools" % tenant_id) + response = req.get_response(self.app) + + pools = self.get_from_response(response, "floating_ip_pools", []) + for p in pools: + if p['name'] == id: + return [_build_network(p["name"], FLOATING_PREFIX)] + raise webob.exc.HTTPNotFound() diff --git a/ooi/api/query.py b/ooi/api/query.py index 7f01c3c..277fd90 100644 --- a/ooi/api/query.py +++ b/ooi/api/query.py @@ -19,6 +19,8 @@ from ooi.occi.core import link from ooi.occi.core import resource from ooi.occi.infrastructure import compute +from ooi.occi.infrastructure import network +from ooi.occi.infrastructure import network_link from ooi.occi.infrastructure import storage from ooi.occi.infrastructure import storage_link from ooi.occi.infrastructure import templates as infra_templates @@ -70,7 +72,14 @@ def index(self, req): l.append(storage_link.StorageLink.kind) l.extend(storage.StorageResource.actions) - # OCCI infra mixins + # OCCI infra network + l.append(network.NetworkResource.kind) + l.extend(network.NetworkResource.actions) + l.append(network.ip_network) + l.append(network_link.NetworkInterface.kind) + l.append(network_link.ip_network_interface) + + # OCCI infra compute mixins l.append(infra_templates.os_tpl) l.append(infra_templates.resource_tpl) diff --git a/ooi/occi/core/resource.py b/ooi/occi/core/resource.py index 7dfcc3c..d960889 100644 --- a/ooi/occi/core/resource.py +++ b/ooi/occi/core/resource.py @@ -53,6 +53,9 @@ def link(self, target, mixins=[]): l = link.Link("", mixins, self, target) self._links.append(l) + def add_link(self, link): + self._links.append(link) + @property def summary(self): return self.attributes["occi.core.summary"].value diff --git a/ooi/occi/infrastructure/network.py b/ooi/occi/infrastructure/network.py new file mode 100644 index 0000000..775c044 --- /dev/null +++ b/ooi/occi/infrastructure/network.py @@ -0,0 +1,78 @@ +# -*- 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 as attr +from ooi.occi.core import kind +from ooi.occi.core import mixin +from ooi.occi.core import resource +from ooi.occi import helpers + +up = action.Action(helpers.build_scheme('infrastructure/network/action'), + "up", "up network instance") + +down = action.Action(helpers.build_scheme('infrastructure/network/action'), + "down", "down network instance") + + +class NetworkResource(resource.Resource): + attributes = attr.AttributeCollection(["occi.network.vlan", + "occi.network.label", + "occi.network.state"]) + actions = (up, down) + kind = kind.Kind(helpers.build_scheme('infrastructure'), 'network', + 'network resource', attributes, '/network/', + actions=actions, + related=[resource.Resource.kind]) + + def __init__(self, title, summary=None, id=None, vlan=None, label=None, + state=None, mixins=[]): + super(NetworkResource, self).__init__(title, mixins, summary=summary, + id=id) + self.attributes["occi.network.vlan"] = attr.MutableAttribute( + "occi.network.vlan", vlan) + self.attributes["occi.network.label"] = attr.MutableAttribute( + "occi.network.label", label) + self.attributes["occi.network.state"] = attr.InmutableAttribute( + "occi.network.state", state) + + @property + def vlan(self): + return self.attributes["occi.network.vlan"].value + + @vlan.setter + def vlan(self, value): + self.attributes["occi.network.vlan"].value = value + + @property + def label(self): + return self.attributes["occi.network.label"].value + + @label.setter + def label(self, value): + self.attributes["occi.network.label"].value = value + + @property + def state(self): + return self.attributes["occi.network.state"].value + + +ip_network = mixin.Mixin(helpers.build_scheme("infrastructure/network"), + "ipnetwork", "IP Networking Mixin", + attributes=attr.AttributeCollection([ + "occi.network.address", + "occi.network.gateway", + "occi.network.allocation"])) diff --git a/ooi/occi/infrastructure/network_link.py b/ooi/occi/infrastructure/network_link.py new file mode 100644 index 0000000..b2c71a8 --- /dev/null +++ b/ooi/occi/infrastructure/network_link.py @@ -0,0 +1,69 @@ +# -*- 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 as attr +from ooi.occi.core import kind +from ooi.occi.core import link +from ooi.occi.core import mixin +from ooi.occi import helpers + + +class NetworkInterface(link.Link): + attributes = attr.AttributeCollection(["occi.networkinterface.interface", + "occi.networkinterface.mac", + "occi.networkinterface.state"]) + kind = kind.Kind(helpers.build_scheme('infrastructure'), + 'networkinterface', 'network link resource', + attributes, '/networklink/', + related=[link.Link.kind]) + + def __init__(self, mixins, source, target, id=None, interface=None, + mac=None, state=None): + + super(NetworkInterface, self).__init__(None, mixins, source, + target, id) + + self.attributes["occi.networkinterface.interface"] = ( + attr.InmutableAttribute("occi.networkinterface.interface", + interface)) + self.attributes["occi.networkinterface.mac"] = attr.MutableAttribute( + "occi.networkinterface.mac", mac) + self.attributes["occi.networkinterface.state"] = ( + attr.InmutableAttribute("occi.networkinterface.state", state)) + + @property + def interface(self): + return self.attributes["occi.networkinterface.interface"].value + + @property + def mac(self): + return self.attributes["occi.networkinterface.mac"].value + + @mac.setter + def mac(self, value): + self.attributes["occi.networkinterface.mac"].value = value + + @property + def state(self): + return self.attributes["occi.networkinterface.state"].value + +ip_network_interface = mixin.Mixin( + helpers.build_scheme("infrastructure/networkinterface"), + "ipnetworkinterface", "IP Network interface Mixin", + attributes=attr.AttributeCollection([ + "occi.networkinterface.address", + "occi.networkinterface.gateway", + "occi.networkinterface.allocation"])) diff --git a/ooi/openstack/network.py b/ooi/openstack/network.py new file mode 100644 index 0000000..52aef5a --- /dev/null +++ b/ooi/openstack/network.py @@ -0,0 +1,38 @@ +# -*- 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 as attr +from ooi.occi.infrastructure import network_link + + +class OSNetworkInterface(network_link.NetworkInterface): + attributes = attr.AttributeCollection(["occi.networkinterface.address", + "occi.networkinterface.gateway", + "occi.networkinterface.allocation"]) + + def __init__(self, source, target, mac, address): + link_id = '_'.join([source.id, address]) + mixins = [network_link.ip_network_interface] + super(OSNetworkInterface, self).__init__(mixins, source, target, + link_id, "eth0", mac, + "active") + self.attributes["occi.networkinterface.address"] = ( + attr.MutableAttribute("occi.networkinterface.address", address)) + self.attributes["occi.networkinterface.gateway"] = ( + attr.MutableAttribute("occi.networkinterface.gateway", None)) + self.attributes["occi.networkinterface.allocation"] = ( + attr.MutableAttribute("occi.networkinterface.allocation", + "dynamic")) diff --git a/ooi/tests/fakes.py b/ooi/tests/fakes.py index 24ddc68..5bd33f1 100644 --- a/ooi/tests/fakes.py +++ b/ooi/tests/fakes.py @@ -137,18 +137,7 @@ def fake_query_results(): cats = [] - cats.append( - 'storage; ' - 'scheme="http://schemas.ogf.org/occi/infrastructure#"; ' - 'class="kind"') - cats.append( - 'storagelink; ' - 'scheme="http://schemas.ogf.org/occi/infrastructure#"; ' - 'class="kind"') - cats.append( - 'compute; ' - 'scheme="http://schemas.ogf.org/occi/infrastructure#"; ' - 'class="kind"') + # OCCI Core cats.append( 'link; ' 'scheme="http://schemas.ogf.org/occi/core#"; ' @@ -161,26 +150,11 @@ def fake_query_results(): 'entity; ' 'scheme="http://schemas.ogf.org/occi/core#"; ' 'class="kind"') + # OCCI Infrastructure Compute cats.append( - 'offline; ' - 'scheme="http://schemas.ogf.org/occi/infrastructure/storage/action#"; ' - 'class="action"') - cats.append( - 'online; ' - 'scheme="http://schemas.ogf.org/occi/infrastructure/storage/action#"; ' - 'class="action"') - cats.append( - 'backup; ' - 'scheme="http://schemas.ogf.org/occi/infrastructure/storage/action#"; ' - 'class="action"') - cats.append( - 'resize; ' - 'scheme="http://schemas.ogf.org/occi/infrastructure/storage/action#"; ' - 'class="action"') - cats.append( - 'snapshot; ' - 'scheme="http://schemas.ogf.org/occi/infrastructure/storage/action#"; ' - 'class="action"') + 'compute; ' + 'scheme="http://schemas.ogf.org/occi/infrastructure#"; ' + 'class="kind"') cats.append( 'start; ' 'scheme="http://schemas.ogf.org/occi/infrastructure/compute/action#"; ' @@ -197,6 +171,15 @@ def fake_query_results(): 'suspend; ' 'scheme="http://schemas.ogf.org/occi/infrastructure/compute/action#"; ' 'class="action"') + cats.append( + 'os_tpl; ' + 'scheme="http://schemas.ogf.org/occi/infrastructure#"; ' + 'class="mixin"') + cats.append( + 'resource_tpl; ' + 'scheme="http://schemas.ogf.org/occi/infrastructure#"; ' + 'class="mixin"') + # OpenStack Templates cats.append( 'bar; ' 'scheme="http://schemas.openstack.org/template/os#"; ' @@ -213,14 +196,7 @@ def fake_query_results(): 'foo; ' 'scheme="http://schemas.openstack.org/template/resource#"; ' 'class="mixin"') - cats.append( - 'os_tpl; ' - 'scheme="http://schemas.ogf.org/occi/infrastructure#"; ' - 'class="mixin"') - cats.append( - 'resource_tpl; ' - 'scheme="http://schemas.ogf.org/occi/infrastructure#"; ' - 'class="mixin"') + # OpenStack contextualization cats.append( 'user_data; ' 'scheme="http://schemas.openstack.org/compute/instance#"; ' @@ -229,7 +205,60 @@ def fake_query_results(): 'public_key; ' 'scheme="http://schemas.openstack.org/instance/credentials#"; ' 'class="mixin"') - + # OCCI Infrastructure Network + cats.append( + 'network; ' + 'scheme="http://schemas.ogf.org/occi/infrastructure#"; ' + 'class="kind"') + cats.append( + 'ipnetwork; ' + 'scheme="http://schemas.ogf.org/occi/infrastructure/network#"; ' + 'class="mixin"') + cats.append( + 'up; ' + 'scheme="http://schemas.ogf.org/occi/infrastructure/network/action#"; ' + 'class="action"') + cats.append( + 'down; ' + 'scheme="http://schemas.ogf.org/occi/infrastructure/network/action#"; ' + 'class="action"') + cats.append( + 'networkinterface; ' + 'scheme="http://schemas.ogf.org/occi/infrastructure#"; ' + 'class="kind"') + cats.append( + 'ipnetworkinterface; ' + 'scheme="http://schemas.ogf.org/occi/infrastructure/' + 'networkinterface#"; class="mixin"') + # OCCI Infrastructure Storage + cats.append( + 'storage; ' + 'scheme="http://schemas.ogf.org/occi/infrastructure#"; ' + 'class="kind"') + cats.append( + 'storagelink; ' + 'scheme="http://schemas.ogf.org/occi/infrastructure#"; ' + 'class="kind"') + cats.append( + 'offline; ' + 'scheme="http://schemas.ogf.org/occi/infrastructure/storage/action#"; ' + 'class="action"') + cats.append( + 'online; ' + 'scheme="http://schemas.ogf.org/occi/infrastructure/storage/action#"; ' + 'class="action"') + cats.append( + 'backup; ' + 'scheme="http://schemas.ogf.org/occi/infrastructure/storage/action#"; ' + 'class="action"') + cats.append( + 'resize; ' + 'scheme="http://schemas.ogf.org/occi/infrastructure/storage/action#"; ' + 'class="action"') + cats.append( + 'snapshot; ' + 'scheme="http://schemas.ogf.org/occi/infrastructure/storage/action#"; ' + 'class="action"') result = [] for c in cats: result.append(("Category", c)) diff --git a/ooi/tests/middleware/test_compute_controller.py b/ooi/tests/middleware/test_compute_controller.py index 50fbfa1..7951c42 100644 --- a/ooi/tests/middleware/test_compute_controller.py +++ b/ooi/tests/middleware/test_compute_controller.py @@ -245,7 +245,11 @@ def test_vm_links(self): link_id = '_'.join([server["id"], vol_id]) self.assertContentType(resp) - self.assertResultIncludesLink(link_id, server["id"], vol_id, resp) + source = utils.join_url(self.application_url + "/", + "compute/%s" % server["id"]) + target = utils.join_url(self.application_url + "/", + "storage/%s" % vol_id) + self.assertResultIncludesLink(link_id, source, target, resp) self.assertEqual(200, resp.status_code) diff --git a/ooi/tests/middleware/test_middleware.py b/ooi/tests/middleware/test_middleware.py index 14067da..8f0d6fb 100644 --- a/ooi/tests/middleware/test_middleware.py +++ b/ooi/tests/middleware/test_middleware.py @@ -65,7 +65,7 @@ def assertResultIncludesLink(self, link_id, source, target, result): attrs = set([s.strip() for s in r[1].split(";")]) if expected_attrs.issubset(attrs): return - self.fail("Failed to find %s in %s." % expected_attrs, result) + self.fail("Failed to find %s in %s." % (expected_attrs, result)) def _build_req(self, path, tenant_id, **kwargs): if self.accept is not None: diff --git a/ooi/wsgi/__init__.py b/ooi/wsgi/__init__.py index cfa57d9..54318d5 100644 --- a/ooi/wsgi/__init__.py +++ b/ooi/wsgi/__init__.py @@ -20,6 +20,7 @@ import webob.dec import ooi.api.compute +import ooi.api.network from ooi.api import query import ooi.api.storage from ooi import exception @@ -144,6 +145,16 @@ def index(self, *args, **kwargs): self.mapper.resource("volume", "storage", controller=self.resources["storage"]) + self.resources["network"] = self._create_resource( + ooi.api.network.NetworkController) + self.mapper.resource("network", "network", + controller=self.resources["network"]) + self.resources["network_pool"] = self._create_resource( + ooi.api.network.PoolController) + netpool_name = "network/%s" % ooi.api.network.FLOATING_PREFIX + self.mapper.resource(netpool_name, netpool_name, + controller=self.resources["network_pool"]) + @webob.dec.wsgify(RequestClass=Request) def __call__(self, req): response = self.process_request(req)