From 7566dce6affd1b453f2b276ca1fe831bc28f5f0a Mon Sep 17 00:00:00 2001 From: Jorge Sevilla Date: Thu, 26 Jan 2017 21:50:55 +0100 Subject: [PATCH] Included OCCI IPReservation support This is a complete change in which we have add the OCCI IPRESERVATION class with the features provided by CESNET. We include: the controller, the occi class, middleware changes, and tests to control it. In addition, we have modified networklink to support this feature, now networklink is identified by using only compute id and ipreservation ip. Last, we add the documentation relate to its usage. Change-Id: I52f80a9a7a39e00d1d7487001863586a551edf5f --- doc/source/user/usage.rst | 54 +++++ ooi/api/compute.py | 19 +- ooi/api/helpers.py | 199 ++++++++++++------ ooi/api/ip_reservation.py | 131 ++++++++++++ ooi/api/network_link.py | 69 ++++-- ooi/api/query.py | 2 + ooi/occi/infrastructure/ip_reservation.py | 73 +++++++ ooi/openstack/network.py | 2 +- ooi/tests/fakes.py | 71 ++++++- ooi/tests/fakes_network.py | 3 +- .../middleware/test_compute_controller.py | 19 +- .../test_ip_reservation_controller.py | 178 ++++++++++++++++ .../test_netinterface_controller.py | 70 +++--- ooi/tests/unit/controllers/test_compute.py | 26 ++- ooi/tests/unit/controllers/test_helpers.py | 29 +++ .../unit/controllers/test_ip_reservation.py | 120 +++++++++++ .../unit/controllers/test_network_links.py | 47 ++++- ooi/tests/unit/controllers/test_query.py | 3 + .../unit/occi/test_occi_infrastructure.py | 43 ++++ ooi/tests/unit/occi/test_openstack.py | 2 +- ooi/wsgi/__init__.py | 6 + 21 files changed, 1014 insertions(+), 152 deletions(-) create mode 100644 ooi/api/ip_reservation.py create mode 100644 ooi/occi/infrastructure/ip_reservation.py create mode 100644 ooi/tests/functional/middleware/test_ip_reservation_controller.py create mode 100644 ooi/tests/unit/controllers/test_ip_reservation.py diff --git a/doc/source/user/usage.rst b/doc/source/user/usage.rst index 5ba3e8a..916bc9d 100644 --- a/doc/source/user/usage.rst +++ b/doc/source/user/usage.rst @@ -291,6 +291,60 @@ It deletes a network:: It returns a 204 empty response. +IPReservation +************** + +OOI allows to manage public IPs by using IPReservation resources. This resource is a special network to provide public access. +It allocates and releases IPs from public network pools. + +List IPReservations +------------------- +It list IPReservation resources:: + + curl -H "X-Auth-token: "$OS_TOKEN http://127.0.0.1:8787/occi1.1/ipreservation + +It returns a HTTP 200 with output:: + + X-OCCI-Location: http://127.0.0.1:8787/occi1.1/ipreservation/3318c3af-ce57-41ef-a9c1-9a5ecfbe0526 + +Show IPReservation +------------------ +It shows the IPReservation details:: + + curl -H "X-Auth-token: "$OS_TOKEN http://127.0.0.1:8787/occi1.1/ipreservation/3318c3af-ce57-41ef-a9c1-9a5ecfbe0526 + +It returns a HTTP 200 with output:: + + Category: ipreservation; scheme="http://schemas.ogf.org/occi/infrastructure#"; class="kind"; title="IPReservation"; rel="http://schemas.ogf.org/occi/infrastructure#network"; location="http://127.0.0.1:8787/occi1.1/ipreservation/" + X-OCCI-Attribute: occi.core.title="external-net" + X-OCCI-Attribute: occi.core.summary=[] + X-OCCI-Attribute: occi.core.id="3318c3af-ce57-41ef-a9c1-9a5ecfbe0526" + X-OCCI-Attribute: occi.ipreservation.address="193.136.75.90" + X-OCCI-Attribute: occi.ipreservation.used="true" + Link: ; rel="http://schemas.ogf.org/occi/infrastructure/network/action#up" + Link: ; rel="http://schemas.ogf.org/occi/infrastructure/network/action#down" + +Create IPReservation +-------------------- +It creates a IPReservation resource:: + + curl -X POST http://127.0.0.1:8787/occi1.1/ipreservation -H 'X-Auth-token: '$OS_TOKEN \ + -H 'Category: ipreshemas.ogf.org/occi/infrastructure#"; class="kind",' \ + 'external-net; scheme="http://schemas.openstack.org/network/floatingippool#"; class="mixin"' \ + -H 'Content-Type: text/occi' + +It returns a HTTP 200 with output:: + + X-OCCI-Location: http://127.0.0.1:8787/occi1.1/ipreservation/3318c3af-ce57-41ef-a9c1-9a5ecfbe0526 + +Delete IPReservation +-------------------- +It deletes IPReservation resources:: + + curl -X DELETE -H "X-Auth-token: "$OS_TOKEN http://127.0.0.1:8787/occi1.1/ipreservation/3318c3af-ce57-41ef-a9c1-9a5ecfbe0526 + +It returns a 204 empty response. + Network Link ************ diff --git a/ooi/api/compute.py b/ooi/api/compute.py index 04099d2..50475ac 100644 --- a/ooi/api/compute.py +++ b/ooi/api/compute.py @@ -22,6 +22,7 @@ from ooi import exception from ooi.occi.core import collection from ooi.occi.infrastructure import compute from ooi.occi.infrastructure import contextualization +from ooi.occi.infrastructure import ip_reservation from ooi.occi.infrastructure import network from ooi.occi.infrastructure import storage from ooi.occi.infrastructure import storage_link @@ -32,8 +33,13 @@ from ooi.openstack import network as os_network from ooi.openstack import templates -def _create_network_link(addr, comp, net_id): - net = network.NetworkResource(title="network", id=net_id) +def _create_network_link(addr, comp, net_id, type_ip): + if type_ip == "floating": + net = ip_reservation.IPReservation(title="network", + address=None, + id=net_id) + else: + net = network.NetworkResource(title="network", id=net_id) return os_network.OSNetworkInterface(comp, net, addr["OS-EXT-IPS-MAC:mac_addr"], addr["addr"]) @@ -278,7 +284,9 @@ class Controller(ooi.api.base.Controller): for addr in addr_set: # TODO(jorgesece): add pool information if addr["OS-EXT-IPS:type"] == "floating": - net_id = helpers.PUBLIC_NETWORK + net_id = self.os_helper.get_floatingip_id( + req, addr['addr'] + ) else: try: net_id = self.os_helper.get_network_id( @@ -286,7 +294,9 @@ class Controller(ooi.api.base.Controller): ) except webob.exc.HTTPNotFound: net_id = "FIXED" - comp.add_link(_create_network_link(addr, comp, net_id)) + comp.add_link(_create_network_link( + addr, comp, net_id, + addr["OS-EXT-IPS:type"])) return comp @@ -310,7 +320,6 @@ class Controller(ooi.api.base.Controller): if server_ip == ip["ip"]: self.os_helper.remove_floating_ip(req, server_id, ip["ip"]) - self.os_helper.release_floating_ip(req, ip["id"]) def _delete(self, req, server_ids): for server_id in server_ids: diff --git a/ooi/api/helpers.py b/ooi/api/helpers.py index 45611df..0e4fd38 100644 --- a/ooi/api/helpers.py +++ b/ooi/api/helpers.py @@ -534,6 +534,19 @@ class OpenStackHelper(BaseHelper): response = req.get_response(self.app) return self.get_from_response(response, "floating_ips", []) + def get_floating_ip(self, req, floating_id): + """Get information about a floating IP. + + :param req: the incoming request + :param floating_id: floating ip id to get info from + """ + tenant_id = self.tenant_from_req(req) + path = "/%s/os-floating-ips/%s" % (tenant_id, + floating_id) + req = self._make_get_request(req, path) + response = req.get_response(self.app) + return self.get_from_response(response, "floating_ip", []) + def _get_floating_ip_pools_req(self, req): tenant_id = self.tenant_from_req(req) path = "/%s/os-floating-ip-pools" % tenant_id @@ -708,7 +721,7 @@ class OpenStackHelper(BaseHelper): @staticmethod def _build_link(net_id, compute_id, ip, ip_id=None, mac=None, pool=None, - state='ACTIVE'): + state='ACTIVE', public_ip=False): link = {} link['mac'] = mac link['pool'] = pool @@ -717,6 +730,7 @@ class OpenStackHelper(BaseHelper): link['ip'] = ip link['ip_id'] = ip_id link['state'] = os_helpers.vm_state(state) + link['public_ip'] = public_ip return link def _get_ports(self, req, compute_id): @@ -732,48 +746,55 @@ class OpenStackHelper(BaseHelper): LOG.exception("Interfaces can not be shown: " + e.explanation) return result - def get_compute_net_link(self, req, compute_id, network_id, - address): + def get_compute_net_link(self, req, compute_id, address): """Get a specific network/server link It shows a specific link (either private or public ip) :param req: the incoming request :param compute_id: server id - :param network_id: network id :param address: ip connected """ - - s = self.get_server(req, compute_id) - pool = None - ip_id = None - server_addrs = s.get("addresses", {}) + compute = self.get_server(req, compute_id) + server_addrs = compute.get("addresses", {}) + compute_id = compute["id"] + ports = self._get_ports(req, compute_id) + floating_ips = self.get_floating_ips( + req + ) for addr_set in server_addrs.values(): for addr in addr_set: if addr["addr"] == address: + public_ip = False + pool = None + ip_id = None mac = addr["OS-EXT-IPS-MAC:mac_addr"] - if network_id == os_helpers.PUBLIC_NETWORK: - floating_ips = self.get_floating_ips( - req - ) - for ip in floating_ips: - if address == ip['ip']: - pool = ip['pool'] - ip_id = ip['id'] - else: - ports = self._get_ports(req, compute_id) + ip_type = addr["OS-EXT-IPS:type"] + if ip_type == "fixed": + network_id = "FIXED" for p in ports: - if p["net_id"] == network_id: - for ip in p["fixed_ips"]: - if ip['ip_address'] == address: - ip_id = p['port_id'] - return self._build_link(network_id, - compute_id, - address, - mac=mac, - pool=pool, - ip_id=ip_id - ) + if p['mac_addr'] == mac: + ip_id = str(p['port_id']) + network_id = str(p['net_id']) + break + else: + network_id = "PUBLIC" + for fp in floating_ips: + if compute_id == fp['instance_id']: + pool = fp['pool'] + ip_id = str(fp['id']) + network_id = str(fp['id']) + public_ip = True + break + return self._build_link( + network_id, + compute_id, + address, + mac=mac, + pool=pool, + ip_id=ip_id, + public_ip=public_ip + ) raise exception.NotFound() def list_compute_net_links(self, req): @@ -781,45 +802,48 @@ class OpenStackHelper(BaseHelper): :param req: the incoming request """ - floating_ips = self.get_floating_ips(req) - float_list = {} - for ip in floating_ips: - if ip["instance_id"]: - float_list.update({ip['fixed_ip']: ip}) - servers = self.index(req) link_list = [] - for s in servers: - ports = self._get_ports(req, s['id']) - for p in ports: - for ip in p["fixed_ips"]: - mac = p['mac_addr'] - state = p["port_state"] - link = self._build_link(p["net_id"], - s['id'], - ip['ip_address'], - ip_id=p["port_id"], - mac=mac, - state=state) - link_list.append(link) - float_ip = float_list.get(ip['ip_address'], None) - if float_ip: - link = self._build_link(p["net_id"], - float_ip['instance_id'], - float_ip['ip'], - ip_id=float_ip["id"], - mac=mac, - pool=float_ip["pool"] - ) - link_list.append(link) - if not link_list: - for ip in floating_ips: - if ip["instance_id"]: - link = self._build_link(os_helpers.PUBLIC_NETWORK, - ip['instance_id'], - ip['ip'], - ip_id=ip["id"], - pool=ip["pool"] - ) + compute_list = self.index(req) + floating_ips = self.get_floating_ips( + req + ) + for c in compute_list: + compute_id = c["id"] + compute = self.get_server(req, compute_id) + ports = self._get_ports(req, compute_id) + server_addrs = compute.get("addresses", {}) + for addr_set in server_addrs.values(): + for addr in addr_set: + public_ip = False + pool = None + network_id = "fixed" + mac = addr["OS-EXT-IPS-MAC:mac_addr"] + ip_type = addr["OS-EXT-IPS:type"] + address = addr['addr'] + ip_id = None + if ip_type == "fixed": + for p in ports: + if p['mac_addr'] == mac: + ip_id = p['port_id'] + network_id = p["net_id"] + break + else: + for fp in floating_ips: + if address == fp['ip']: + pool = fp['pool'] + ip_id = fp['id'] + network_id = fp['id'] + public_ip = True + break + link = self._build_link( + network_id, + compute_id, + address, + mac=mac, + pool=pool, + ip_id=ip_id, + public_ip=public_ip + ) link_list.append(link) return link_list @@ -886,8 +910,43 @@ class OpenStackHelper(BaseHelper): raise webob.exc.HTTPNotFound - def assign_floating_ip(self, req, network_id, device_id, - pool=None): + def get_floatingip_id(self, req, address): + """Get the floating IP ID + + :param req: the incoming network + :param address: floating ip address + """ + floating_ips = self.get_floating_ips(req) + for fp in floating_ips: + if address == fp['ip']: + return str(fp['id']) + raise webob.exc.HTTPNotFound + + def assign_floating_ip(self, req, floatingip_id, device_id): + """assign floating ip to a server + + :param req: the incoming request + :param floatingip_id: floating ip id + :param device_id: device id + """ + ip = self.get_floating_ip(req, floatingip_id) + + self.associate_floating_ip(req, device_id, ip['ip']) + + try: + link_public = self._build_link( + floatingip_id, + device_id, + ip['ip'], + ip_id=floatingip_id, + public_ip=True + ) + except Exception: + raise exception.OCCIInvalidSchema() + return link_public + + def assign_floating_ip_deprecated(self, req, network_id, device_id, + pool=None): """assign floating ip to a server :param req: the incoming request diff --git a/ooi/api/ip_reservation.py b/ooi/api/ip_reservation.py new file mode 100644 index 0000000..d342f6a --- /dev/null +++ b/ooi/api/ip_reservation.py @@ -0,0 +1,131 @@ +# -*- coding: utf-8 -*- + +# Copyright 2015 LIP - INDIGO-DataCloud +# +# 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.api import base +from ooi.api import helpers +from ooi import exception +from ooi.occi.core import collection +from ooi.occi.infrastructure import ip_reservation +from ooi.occi import validator as occi_validator +from ooi.openstack import network as os_network + + +class Controller(base.Controller): + def __init__(self, app=None, openstack_version=None): + """IP reservation controller initialization + + :param app: application + :param: openstack_version: nova version + """ + + super(Controller, self).__init__( + app=app, + openstack_version=openstack_version) + self.os_helper = helpers.OpenStackHelper( + self.app, + self.openstack_version + ) + + @staticmethod + def _get_ipreservation_resources(ipreservation_list): + """Create network instances from ip reservations in json format + + :param ipreservation_list: ip reservation objects provides by + the cloud infrastructure + """ + occi_ipreservation_resources = [] + if ipreservation_list: + for s in ipreservation_list: + n_id = str(s["id"]) # some versions retrieve int. + n_name = s["pool"] + n_address = s["ip"] + n_used = False + if s["instance_id"]: + n_used = True + s = ip_reservation.IPReservation(title=n_name, + id=n_id, + address=n_address, + used=n_used + ) + occi_ipreservation_resources.append(s) + return occi_ipreservation_resources + + def index(self, req): + """List ip reservations + + :param req: request object + """ + occi_ipreservation = self.os_helper.get_floating_ips(req) + occi_ipreservation_resources = self._get_ipreservation_resources( + occi_ipreservation) + + return collection.Collection( + resources=occi_ipreservation_resources) + + def show(self, req, id): + """Get ip reservation details + + :param req: request object + :param id: ip reservation identification + """ + resp = self.os_helper.get_floating_ip(req, id) + occi_network_resources = self._get_ipreservation_resources( + [resp]) + return occi_network_resources[0] + + def create(self, req, body=None): + """Create an ip reservation instance in the cloud + + :param req: request object + :param body: body request (not used) + """ + parser = req.get_parser()(req.headers, req.body) + scheme = { + "category": ip_reservation.IPReservation.kind, + "optional_mixins": [ + os_network.OSFloatingIPPool, + ] + } + obj = parser.parse() + validator = occi_validator.Validator(obj) + validator.validate(scheme) + pool = None + if os_network.OSFloatingIPPool.scheme in obj["schemes"]: + pool = ( + obj["schemes"][os_network.OSFloatingIPPool.scheme][0] + ) + resp = self.os_helper.allocate_floating_ip(req, pool) + occi_network_resources = self._get_ipreservation_resources( + [resp]) + return collection.Collection(resources=occi_network_resources) + + def delete(self, req, id): + """delete an ip reservation instance + + :param req: current request + :param id: identification + """ + self.os_helper.release_floating_ip(req, id) + return [] + + def run_action(self, req, id, body): + """Run action over the network + + :param req: current request + :param id: ip reservation identification + :param body: body + """ + raise exception.NotImplemented() \ No newline at end of file diff --git a/ooi/api/network_link.py b/ooi/api/network_link.py index 97af659..7d8b384 100644 --- a/ooi/api/network_link.py +++ b/ooi/api/network_link.py @@ -18,6 +18,7 @@ from ooi.api import helpers from ooi import exception from ooi.occi.core import collection from ooi.occi.infrastructure import compute +from ooi.occi.infrastructure import ip_reservation from ooi.occi.infrastructure import network from ooi.occi.infrastructure import network_link from ooi.occi import validator as occi_validator @@ -40,8 +41,14 @@ def _get_network_link_resources(link_list): state = l.get('state', None) ip_id = l.get('ip_id', None) net_id = l['network_id'] - n = network.NetworkResource(title="network", - id=net_id) + public_ip = l['public_ip'] + if public_ip: + n = ip_reservation.IPReservation(title="network", + address=ip, + id=net_id) + else: + n = network.NetworkResource(title="network", + id=net_id) c = compute.ComputeResource(title="Compute", id=compute_id ) @@ -77,14 +84,13 @@ class Controller(base.Controller): :param id: network link identification """ try: - server_id, network_id, server_addr = id.split('_', 2) + server_id, server_addr = id.split('_', 1) except ValueError: raise exception.LinkNotFound(link_id=id) try: link = self.os_helper.get_compute_net_link( req, server_id, - network_id, server_addr) occi_instance = _get_network_link_resources([link])[0] except Exception: @@ -122,28 +128,42 @@ class Controller(base.Controller): validator.validate(scheme) attrs = obj.get("attributes", {}) - _, net_id = helpers.get_id_with_kind( - req, - attrs.get("occi.core.target"), - network.NetworkResource.kind) _, server_id = helpers.get_id_with_kind( req, attrs.get("occi.core.source"), compute.ComputeResource.kind) - pool = None - if os_network.OSFloatingIPPool.scheme in obj["schemes"]: - pool = ( - obj["schemes"][os_network.OSFloatingIPPool.scheme][0] - ) - # Allocate public IP and associate it ot the server - if net_id == os_helpers.PUBLIC_NETWORK: + + if (ip_reservation.IPReservation.kind.location in + attrs.get("occi.core.target")): + _, net_id = helpers.get_id_with_kind( + req, + attrs.get("occi.core.target"), + ip_reservation.IPReservation.kind) os_link = self.os_helper.assign_floating_ip( - req, net_id, server_id, pool + req, net_id, server_id ) else: - # Allocate private network - os_link = self.os_helper.create_port( - req, net_id, server_id) + _, net_id = helpers.get_id_with_kind( + req, + attrs.get("occi.core.target"), + network.NetworkResource.kind) + # TODO(jorgesece): DEPRECATION + # Delete this method for linking public network. + if net_id == os_helpers.PUBLIC_NETWORK: + pool = None + if os_network.OSFloatingIPPool.scheme in obj["schemes"]: + pool = ( + obj["schemes"][os_network.OSFloatingIPPool.scheme][0] + ) + # Allocate public IP and associate it on the server + os_link = self.os_helper.assign_floating_ip_deprecated( + req, net_id, server_id, pool + ) + # END DEPRECATION + else: + # Allocate private network + os_link = self.os_helper.create_port( + req, net_id, server_id) occi_link = _get_network_link_resources([os_link]) return collection.Collection(resources=occi_link) @@ -155,6 +175,7 @@ class Controller(base.Controller): """ iface = self._get_interface_from_id(req, id) server = iface.source.id + # TODO(jorgesece): DEPRECATION if iface.target.id == os_helpers.PUBLIC_NETWORK: # remove floating IP self.os_helper.remove_floating_ip(req, server, @@ -163,7 +184,13 @@ class Controller(base.Controller): # release IP self.os_helper.release_floating_ip(req, iface.ip_id) + # END DEPRECATION else: - self.os_helper.delete_port( - req, server, iface.ip_id) + if isinstance(iface.target, + ip_reservation.IPReservation): + self.os_helper.remove_floating_ip(req, server, + iface.address) + else: + self.os_helper.delete_port( + req, server, iface.ip_id) return [] \ No newline at end of file diff --git a/ooi/api/query.py b/ooi/api/query.py index 8d67658..c6d8a9a 100644 --- a/ooi/api/query.py +++ b/ooi/api/query.py @@ -21,6 +21,7 @@ from ooi.occi.core import link from ooi.occi.core import resource from ooi.occi.infrastructure import compute from ooi.occi.infrastructure import contextualization +from ooi.occi.infrastructure import ip_reservation from ooi.occi.infrastructure import network from ooi.occi.infrastructure import network_link from ooi.occi.infrastructure import storage @@ -99,6 +100,7 @@ class Controller(base.Controller): mixins.append(network.ip_network) kinds.append(network_link.NetworkInterface.kind) mixins.append(network_link.ip_network_interface) + kinds.append(ip_reservation.IPReservation.kind) # OCCI infra compute mixins mixins.append(infra_templates.os_tpl) diff --git a/ooi/occi/infrastructure/ip_reservation.py b/ooi/occi/infrastructure/ip_reservation.py new file mode 100644 index 0000000..9b625ef --- /dev/null +++ b/ooi/occi/infrastructure/ip_reservation.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- + +# Copyright 2015 LIP - INDIGO-DataCloud +# +# 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 import helpers +from ooi.occi.infrastructure import network + + +class IPReservation(network.NetworkResource): + attributes = attr.AttributeCollection({ + "occi.ipreservation.address": attr.MutableAttribute( + "occi.ipreservation.address", + description="Internet Protocol(IP) network" + " address re-served for Compute instances" + " linking with this IPReservation instance", + attr_type=attr.AttributeType.string_type), + "occi.ipreservation.used": attr.InmutableAttribute( + "occi.ipreservation.used", + description="Indication whether" + " the reserved address is currently in use.", + attr_type=attr.AttributeType.boolean_type), + "occi.ipreservation.state": attr.InmutableAttribute( + "occi.ipreservation.state", + description="Indicates the state of the resource.", + attr_type=attr.AttributeType.string_type), + }) + + kind = kind.Kind(helpers.build_scheme('infrastructure'), 'ipreservation', + 'IPReservation', attributes, 'ipreservation/', + parent=network.NetworkResource.kind) + + def __init__(self, title, address, id=None, used=False, + state=None, mixins=[]): + super(IPReservation, self).__init__(title, id=id, + mixins=mixins) + + self.address = address + self.attributes["occi.ipreservation.used"] = ( + attr.InmutableAttribute.from_attr( + self.attributes["occi.ipreservation.used"], used)) + self.attributes["occi.ipreservation.state"] = ( + attr.InmutableAttribute.from_attr( + self.attributes["occi.ipreservation.state"], state)) + + @property + def address(self): + return self.attributes["occi.ipreservation.address"].value + + @address.setter + def address(self, value): + self.attributes["occi.ipreservation.address"].value = value + + @property + def used(self): + return self.attributes["occi.ipreservation.used"].value + + @property + def state(self): + return self.attributes["occi.ipreservation.state"].value diff --git a/ooi/openstack/network.py b/ooi/openstack/network.py index 0b8728d..8fea4fc 100644 --- a/ooi/openstack/network.py +++ b/ooi/openstack/network.py @@ -46,7 +46,7 @@ class OSNetworkInterface(network_link.NetworkInterface): def __init__(self, source, target, mac, address, ip_id=None, pool=None, state='active'): - link_id = '_'.join([source.id, target.id, address]) + link_id = '_'.join([source.id, address]) mixins = [network_link.ip_network_interface] if pool: mixins.append(OSFloatingIPPool(pool)) diff --git a/ooi/tests/fakes.py b/ooi/tests/fakes.py index 6b536a9..d655112 100644 --- a/ooi/tests/fakes.py +++ b/ooi/tests/fakes.py @@ -125,8 +125,12 @@ pools = { } linked_vm_id = uuid.uuid4().hex +linked_vm_id_2 = uuid.uuid4().hex -allocated_ip = "192.168.253.23" +allocated_ip = {"ip": "192.168.253.23", + "id": 1, + "pool": uuid.uuid4().hex, + "instance_id": None} floating_ips = { tenants["foo"]["id"]: [], @@ -182,6 +186,16 @@ ports = { "net_id": uuid.uuid4().hex, "server_id": linked_vm_id }, + { + "port_id": uuid.uuid4().hex, + "fixed_ips": [ + {"ip_address": "192.168.253.2"} + ], + "mac_addr": uuid.uuid4().hex, + "port_state": "ACTIVE", + "net_id": uuid.uuid4().hex, + "server_id": linked_vm_id_2 + }, ], } @@ -251,6 +265,32 @@ servers = { "security_groups":[ {"name": "group1"} ] + }, + { + "id": linked_vm_id_2, + "name": "withvolume", + "flavor": {"id": flavors[1]["id"]}, + "image": {"id": images["bar"]["id"]}, + "status": "ACTIVE", + "os-extended-volumes:volumes_attached": [ + {"id": volumes[tenants["baz"]["id"]][0]["id"]} + ], + "addresses": { + "private": [ + {"addr": ( + (ports[tenants["baz"]["id"]] + [1]["fixed_ips"][0]["ip_address"]) + ), + "OS-EXT-IPS:type": "fixed", + "OS-EXT-IPS-MAC:mac_addr": ( + ports[tenants["baz"]["id"]][1]["mac_addr"] + ) + }, + {"addr": floating_ips[tenants["baz"]["id"]][0]["ip"], + "OS-EXT-IPS:type": "floating", + "OS-EXT-IPS-MAC:mac_addr": "1234"}, + ] + } } ], } @@ -441,6 +481,12 @@ def fake_query_results(): 'scheme="http://schemas.ogf.org/occi/infrastructure/' 'networkinterface#"; ' 'class="mixin"; title="IP Network interface Mixin"') + cats.append( + 'ipreservation; ' + 'scheme="http://schemas.ogf.org/occi/infrastructure#"; ' + 'class="kind"; title="IPReservation"; ' + 'rel="http://schemas.ogf.org/occi/infrastructure#network"; ' + 'location="%s/ipreservation/"' % application_url) # OCCI Infrastructure Storage cats.append( @@ -615,6 +661,20 @@ class FakeApp(object): self.routes[path_base] = create_fake_json_resp( {obj: list_obj}) + def _populate_ports(self, path, servers_list, ports_list): + for s in servers_list: + p_list = [] + path_base = "%s/servers/%s/%s" % ( + path, + s["id"], + "os-interface" + ) + for p in ports_list: + if p["server_id"] == s["id"]: + p_list.append(p) + self.routes[path_base] = create_fake_json_resp( + {"interfaceAttachments": p_list}) + @webob.dec.wsgify() def __call__(self, req): if req.method == "GET": @@ -660,7 +720,7 @@ class FakeApp(object): else: exc = webob.exc.HTTPNotFound() return FakeOpenStackFault(exc) - ip = {"floating_ip": {"ip": allocated_ip, "id": 1}} + ip = {"floating_ip": allocated_ip} return create_fake_json_resp(ip, 202) def _do_create_port(self, req): @@ -673,10 +733,9 @@ class FakeApp(object): p = {"interfaceAttachment": { "port_id": uuid.uuid4().hex, "fixed_ips": - [{"ip_address": - port[0]["fixed_ips"] - [0]["ip_address"] - }], + [{ + "ip_address": port[0]["fixed_ips"][0]["ip_address"] + }], "mac_addr": port[0]["mac_addr"], "port_state": "DOWN", "net_id": net, diff --git a/ooi/tests/fakes_network.py b/ooi/tests/fakes_network.py index c4d336d..d4179ef 100644 --- a/ooi/tests/fakes_network.py +++ b/ooi/tests/fakes_network.py @@ -292,7 +292,7 @@ def create_headers(category, content_type=None, def fake_build_link(net_id, compute_id, ip, mac=None, - pool=None, state='active'): + pool=None, state='active', public_ip=False): link = {} link['mac'] = mac link['pool'] = pool @@ -300,6 +300,7 @@ def fake_build_link(net_id, compute_id, ip, mac=None, link['compute_id'] = compute_id link['ip'] = ip link['state'] = state + link['public_ip'] = public_ip return link diff --git a/ooi/tests/functional/middleware/test_compute_controller.py b/ooi/tests/functional/middleware/test_compute_controller.py index 8b72e40..bf1be3d 100644 --- a/ooi/tests/functional/middleware/test_compute_controller.py +++ b/ooi/tests/functional/middleware/test_compute_controller.py @@ -424,12 +424,21 @@ class TestComputeController(test_middleware.TestMiddleware): for addr in addr_set: ip = addr["addr"] if addr["OS-EXT-IPS:type"] == "fixed": - net_id = fakes.ports[tenant["id"]][0]["net_id"] + for p in fakes.ports[tenant["id"]]: + if (p["mac_addr"] == + addr["OS-EXT-IPS-MAC:mac_addr"]): + net_id = p["net_id"] + break + target = utils.join_url(self.application_url + "/", + "network/%s" % net_id) else: - net_id = "PUBLIC" - link_id = '_'.join([server["id"], net_id, ip]) - target = utils.join_url(self.application_url + "/", - "network/%s" % net_id) + for floating_ip in fakes.floating_ips[tenant["id"]]: + if floating_ip["ip"] == ip: + net_id = floating_ip['id'] + break + target = utils.join_url(self.application_url + "/", + "ipreservation/%s" % net_id) + link_id = '_'.join([server["id"], ip]) self.assertResultIncludesLink(link_id, source, target, resp) diff --git a/ooi/tests/functional/middleware/test_ip_reservation_controller.py b/ooi/tests/functional/middleware/test_ip_reservation_controller.py new file mode 100644 index 0000000..d29cf18 --- /dev/null +++ b/ooi/tests/functional/middleware/test_ip_reservation_controller.py @@ -0,0 +1,178 @@ +# -*- coding: utf-8 -*- + +# Copyright 2015 LIP - INDIGO-DataCloud +# +# 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.tests import fakes +from ooi.tests.functional.middleware import test_middleware +from ooi import utils + + +def build_occi_ip_reservation(ip, application_url): + name = ip["pool"] + network_id = ip["id"] + address = ip["ip"] + if ip["instance_id"]: + used = str(True).lower() + else: + used = str(False).lower() + cats = [] + cats.append('ipreservation; ' + 'scheme=' + '"http://schemas.ogf.org/occi/infrastructure#";' + ' class="kind"; title="IPReservation"; rel=' + '"http://schemas.ogf.org/occi/infrastructure#network";' + ' location="%s/ipreservation/"' % application_url) + links = [] + links.append('<%s/ipreservation/%s?action=up>; ' + 'rel="http://schemas.ogf.org/occi/' + 'infrastructure/network/action#up"' % + (application_url, network_id)) + links.append('<%s/ipreservation/%s?action=down>; ' + 'rel="http://schemas.ogf.org/occi/' + 'infrastructure/network/action#down"' % + (application_url, network_id)) + + attrs = [ + 'occi.core.title="%s"' % name, + 'occi.core.id="%s"' % network_id, + 'occi.ipreservation.address="%s"' % address, + 'occi.ipreservation.used="%s"' % used, + ] + result = [] + for c in cats: + result.append(("Category", c)) + for a in attrs: + result.append(("X-OCCI-Attribute", a)) + for l in links: + result.append(("Link", l)) + return result + + +class TestNetIPReservationController(test_middleware.TestMiddleware): + """Test OCCI IP Reservation controller.""" + def setUp(self): + super(TestNetIPReservationController, self).setUp() + self.application_url = fakes.application_url + self.app = self.get_app() + + def test_list_empty(self): + tenant = fakes.tenants["bar"] + + for url in ("/ipreservation/", "/ipreservation"): + req = self._build_req(url, tenant["id"], method="GET") + + req.environ["HTTP_X_PROJECT_ID"] = tenant["id"] + + resp = req.get_response(self.app) + + expected_result = "" + self.assertContentType(resp) + self.assertExpectedResult(expected_result, resp) + self.assertEqual(204, resp.status_code) + + def test_list(self): + tenant = fakes.tenants["baz"] + + for url in ("/ipreservation/", "/ipreservation"): + req = self._build_req(url, tenant["id"], method="GET") + resp = req.get_response(self.app) + + self.assertEqual(200, resp.status_code) + expected = [] + for ip in fakes.floating_ips[tenant["id"]]: + expected.append( + ("X-OCCI-Location", + utils.join_url(self.application_url + "/", + "ipreservation/%s" % ip["id"])) + ) + + self.assertExpectedResult(expected, resp) + + def test_show(self): + tenant = fakes.tenants["baz"] + for ip in fakes.floating_ips[tenant["id"]]: + ip_id = ip["id"] + req = self._build_req("/ipreservation/%s" % ip_id, + tenant["id"], method="GET") + + resp = req.get_response(self.app) + self.assertContentType(resp) + self.assertEqual(200, resp.status_code) + expected = build_occi_ip_reservation( + ip, + self.application_url) + self.assertExpectedResult(expected, resp) + + def test_show_invalid_id(self): + tenant = fakes.tenants["foo"] + link_id = uuid.uuid4().hex + req = self._build_req("/ipreservation/%s" % link_id, + tenant["id"], method="GET") + resp = req.get_response(self.app) + self.assertEqual(404, resp.status_code) + + def test_delete(self): + tenant = fakes.tenants["foo"] + link_id = uuid.uuid4().hex + req = self._build_req("/ipreservation/%s" % link_id, + tenant["id"], method="DELETE") + resp = req.get_response(self.app) + self.assertEqual(204, resp.status_code) + + def test_create(self): + tenant = fakes.tenants["baz"] + ip_id = fakes.allocated_ip["id"] + headers = { + 'Category': 'ipreservation;' + ' scheme=' + '"http://schemas.ogf.org/occi/infrastructure#";' + 'class="kind",' + } + req = self._build_req("/ipreservation/", + tenant["id"], + method="POST", + headers=headers) + resp = req.get_response(self.app) + expected = [("X-OCCI-Location", + utils.join_url(self.application_url + "/", + "ipreservation/%s" % ip_id))] + self.assertEqual(200, resp.status_code) + self.assertExpectedResult(expected, resp) + + def test_create_with_pool(self): + tenant = fakes.tenants["baz"] + ip_id = fakes.allocated_ip["id"] + pool_name = "public" + headers = { + 'Category': ('ipreservation;' + ' scheme=' + '"http://schemas.ogf.org/occi/infrastructure#";' + 'class="kind",' + '%s;' + 'scheme="http://schemas.openstack.org/network/' + 'floatingippool#"; class="mixin"') % pool_name, + } + req = self._build_req("/ipreservation/", + tenant["id"], + method="POST", + headers=headers) + resp = req.get_response(self.app) + expected = [("X-OCCI-Location", + utils.join_url(self.application_url + "/", + "ipreservation/%s" % ip_id))] + self.assertEqual(200, resp.status_code) + self.assertExpectedResult(expected, resp) \ No newline at end of file diff --git a/ooi/tests/functional/middleware/test_netinterface_controller.py b/ooi/tests/functional/middleware/test_netinterface_controller.py index cf5cdfe..9a7db12 100644 --- a/ooi/tests/functional/middleware/test_netinterface_controller.py +++ b/ooi/tests/functional/middleware/test_netinterface_controller.py @@ -52,42 +52,29 @@ class TestNetInterfaceController(test_middleware.TestMiddleware): resp = req.get_response(self.app) self.assertEqual(200, resp.status_code) + servers = fakes.servers[tenant["id"]] expected = [] - float_list = {} - for floating_ip in fakes.floating_ips[tenant["id"]]: - if floating_ip["instance_id"]: - float_list.update({floating_ip['fixed_ip']: floating_ip}) - instance_vm = fakes.linked_vm_id - for p in fakes.ports[tenant["id"]]: - for ip in p["fixed_ips"]: - link_id = '_'.join([instance_vm, - p["net_id"], - ip["ip_address"]]) - expected.append( - ("X-OCCI-Location", - utils.join_url(self.application_url + "/", - "networklink/%s" % link_id)) - ) - float_ip = float_list.get(ip['ip_address'], None) - if float_ip: + for server in servers: + server_addrs = server.get("addresses", {}) + instance_vm = server["id"] + for addr_set in server_addrs.values(): + for addr in addr_set: + address = addr['addr'] link_id = '_'.join([instance_vm, - "PUBLIC", - float_ip["ip"]]) + address]) expected.append( ("X-OCCI-Location", utils.join_url(self.application_url + "/", - "networklink/%s" % link_id)) - ) + "networklink/%s" % link_id))) self.assertExpectedResult(expected, resp) def test_show_iface(self): tenant = fakes.tenants["baz"] - instance_vm = fakes.linked_vm_id for p in fakes.ports[tenant["id"]]: for ip in p["fixed_ips"]: + instance_vm = p["server_id"] link_id = '_'.join([instance_vm, - p["net_id"], ip["ip_address"]] ) req = self._build_req("/networklink/%s" % link_id, @@ -178,7 +165,6 @@ class TestNetInterfaceController(test_middleware.TestMiddleware): resp = req.get_response(self.app) link_id = '_'.join([link_info['server_id'], - link_info['net_id'], link_info['fixed_ips'][0] ["ip_address"]]) expected = [("X-OCCI-Location", @@ -188,6 +174,28 @@ class TestNetInterfaceController(test_middleware.TestMiddleware): self.assertExpectedResult(expected, resp) self.assertDefaults(resp) + def test_create_link_ipreservation(self): + tenant = fakes.tenants["baz"] + net_id = fakes.floating_ips[tenant['id']][0]['id'] + occi_compute_id = utils.join_url( + self.application_url + "/", + "compute/%s" % fakes.linked_vm_id) + occi_net_id = utils.join_url(self.application_url + "/", + "ipreservation/%s" % net_id) + headers = { + 'Category': ( + 'networkinterface;' + 'scheme="http://schemas.ogf.org/occi/infrastructure#";' + 'class="kind"'), + 'X-OCCI-Attribute': ('occi.core.source="%s", ' + 'occi.core.target="%s"' + ) % (occi_compute_id, occi_net_id) + } + req = self._build_req("/networklink", tenant["id"], method="POST", + headers=headers) + resp = req.get_response(self.app) + self.assertEqual(200, resp.status_code) + def test_delete_fixed(self): tenant = fakes.tenants["baz"] @@ -195,7 +203,6 @@ class TestNetInterfaceController(test_middleware.TestMiddleware): if n["net_id"] != "PUBLIC": if n["server_id"]: link_id = '_'.join([n["server_id"], - n["net_id"], n["fixed_ips"] [0]["ip_address"]]) req = self._build_req( @@ -210,7 +217,18 @@ class TestNetInterfaceController(test_middleware.TestMiddleware): for n in fakes.floating_ips[tenant["id"]]: if n["instance_id"]: link_id = '_'.join([n["instance_id"], - "PUBLIC", + n["ip"]]) + req = self._build_req("/networklink/%s" % link_id, + tenant["id"], method="DELETE") + resp = req.get_response(self.app) + self.assertContentType(resp) + self.assertEqual(204, resp.status_code) + + def test_delete_ipreservation(self): + tenant = fakes.tenants["baz"] + for n in fakes.floating_ips[tenant["id"]]: + if n["instance_id"]: + link_id = '_'.join([n["instance_id"], n["ip"]]) req = self._build_req("/networklink/%s" % link_id, tenant["id"], method="DELETE") diff --git a/ooi/tests/unit/controllers/test_compute.py b/ooi/tests/unit/controllers/test_compute.py index ff7b413..7f8ba07 100644 --- a/ooi/tests/unit/controllers/test_compute.py +++ b/ooi/tests/unit/controllers/test_compute.py @@ -86,8 +86,7 @@ class TestComputeController(base.TestController): @mock.patch.object(compute.Controller, "_get_server_floating_ips") @mock.patch.object(helpers.OpenStackHelper, "get_floating_ips") @mock.patch.object(helpers.OpenStackHelper, "remove_floating_ip") - @mock.patch.object(helpers.OpenStackHelper, "release_floating_ip") - def test_release_floating_ips(self, mock_release, mock_remove, + def test_release_floating_ips(self, mock_remove, mock_get_floating, mock_server_floating): mock_server_floating.return_value = ["1.2.3.4", "5.6.7.8"] @@ -100,8 +99,6 @@ class TestComputeController(base.TestController): mock_get_floating.assert_called_with(None) mock_remove.assert_has_calls([mock.call(None, "foo", "1.2.3.4"), mock.call(None, "foo", "5.6.7.8")]) - mock_release.assert_has_calls([mock.call(None, "bar"), - mock.call(None, "baz")]) @mock.patch.object(compute.Controller, "_delete") def test_delete(self, mock_delete): @@ -179,7 +176,8 @@ class TestComputeController(base.TestController): @mock.patch.object(helpers.OpenStackHelper, "get_flavor") @mock.patch.object(helpers.OpenStackHelper, "get_server") @mock.patch.object(helpers.OpenStackHelper, "get_network_id") - def test_show(self, m_net_id, m_server, m_flavor, m_image, m_vol): + @mock.patch.object(helpers.OpenStackHelper, "get_floatingip_id") + def test_show(self, m_ipr, m_net_id, m_server, m_flavor, m_image, m_vol): for tenant in fakes.tenants.values(): servers = fakes.servers[tenant["id"]] for server in servers: @@ -194,6 +192,11 @@ class TestComputeController(base.TestController): net_id = fakes.networks.get(tenant["id"], []) if net_id: net_id = net_id[0]['id'] + floatip_ip = fakes.floating_ips.get(tenant["id"], []) + floatip_id = 0 + if floatip_ip.__len__() > 0: + floatip_id = floatip_ip[0]['id'] + m_ipr.return_value = floatip_id m_net_id.return_value = net_id m_server.return_value = server m_flavor.return_value = flavor @@ -207,13 +210,17 @@ class TestComputeController(base.TestController): m_flavor.assert_called_with(None, flavor["id"]) m_image.assert_called_with(None, image["id"]) m_vol.assert_called_with(None, server["id"]) + if floatip_ip: + m_ipr.assert_called_with(None, floatip_ip[0]["ip"]) @mock.patch.object(helpers.OpenStackHelper, "get_server_volumes_link") @mock.patch.object(helpers.OpenStackHelper, "get_image") @mock.patch.object(helpers.OpenStackHelper, "get_flavor") @mock.patch.object(helpers.OpenStackHelper, "get_server") @mock.patch.object(helpers.OpenStackHelper, "get_network_id") - def test_show_no_image(self, m_net_id, m_server, m_flavor, m_image, m_vol): + @mock.patch.object(helpers.OpenStackHelper, "get_floatingip_id") + def test_show_no_image(self, m_ipr, m_net_id, m_server, m_flavor, + m_image, m_vol): for tenant in fakes.tenants.values(): servers = fakes.servers[tenant["id"]] for server in servers: @@ -225,6 +232,11 @@ class TestComputeController(base.TestController): net_id = fakes.networks.get(tenant["id"], []) if net_id: net_id = net_id[0]['id'] + floatip_ip = fakes.floating_ips.get(tenant["id"], []) + floatip_id = 0 + if floatip_ip: + floatip_id = floatip_ip[0]['id'] + m_ipr.return_value = floatip_id m_net_id.return_value = net_id m_server.return_value = server m_flavor.return_value = flavor @@ -238,6 +250,8 @@ class TestComputeController(base.TestController): m_flavor.assert_called_with(None, flavor["id"]) m_image.assert_called_with(None, image["id"]) m_vol.assert_called_with(None, server["id"]) + if floatip_ip: + m_ipr.assert_called_with(None, floatip_ip[0]["ip"]) @mock.patch.object(helpers.OpenStackHelper, "create_server") @mock.patch.object(compute.Controller, "_get_network_from_req") diff --git a/ooi/tests/unit/controllers/test_helpers.py b/ooi/tests/unit/controllers/test_helpers.py index 18bcc14..1420102 100644 --- a/ooi/tests/unit/controllers/test_helpers.py +++ b/ooi/tests/unit/controllers/test_helpers.py @@ -1403,4 +1403,33 @@ class TestOpenStackHelperReqs(TestBaseHelper): self.assertEqual(net_id, ret['network_id']) self.assertEqual(device_id, ret['compute_id']) self.assertEqual(ip, ret['ip']) + + @mock.patch.object(helpers.OpenStackHelper, + "_get_req") + @mock.patch.object(helpers.OpenStackHelper, "tenant_from_req") + def test_associate_floating_ip_deprecated(self, m_ten, m_req): + m_ten.return_value = uuid.uuid4().hex + net_id = uuid.uuid4().hex + device_id = uuid.uuid4().hex + ip = uuid.uuid4().hex + ip_id = uuid.uuid4().hex + pool = uuid.uuid4().hex + resp = fakes.create_fake_json_resp( + {"floating_ip": {"ip": ip, "pool": pool, 'id': ip_id}}, + 202 + ) + req_all = mock.MagicMock() + req_all.get_response.return_value = resp + resp_ass = fakes.create_fake_json_resp({}, 202) + req_ass = mock.MagicMock() + req_ass.get_response.return_value = resp_ass + m_req.side_effect = [req_all, + req_ass] + ret = self.helper.assign_floating_ip_deprecated(None, + net_id, + device_id) + self.assertIsNotNone(ret) + self.assertEqual(net_id, ret['network_id']) + self.assertEqual(device_id, ret['compute_id']) + self.assertEqual(ip, ret['ip']) self.assertEqual(pool, ret['pool']) diff --git a/ooi/tests/unit/controllers/test_ip_reservation.py b/ooi/tests/unit/controllers/test_ip_reservation.py new file mode 100644 index 0000000..0beb9b1 --- /dev/null +++ b/ooi/tests/unit/controllers/test_ip_reservation.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- + +# Copyright 2015 LIP - INDIGO-DataCloud +# +# 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 mock + +from ooi.api import helpers +from ooi.api import ip_reservation as ip_reservation_control +from ooi.occi.infrastructure import ip_reservation +from ooi.openstack import network as os_network +from ooi.tests import base +from ooi.tests import fakes +from ooi.tests import fakes_network as fake_nets + + +class TestIPReservationController(base.TestController): + def setUp(self): + super(TestIPReservationController, self).setUp() + self.controller = ip_reservation_control.Controller( + mock.MagicMock(), None + ) + + @mock.patch.object(helpers.OpenStackHelper, "get_floating_ips") + def test_index_empty(self, m_iplist): + tenant = fakes.tenants["foo"] + floating_list = fakes.floating_ips[tenant["id"]] + m_iplist.return_value = floating_list + result = self.controller.index(None) + expected = self.controller._get_ipreservation_resources(floating_list) + self.assertEqual(expected, result.resources) + self.assertEqual([], result.resources) + m_iplist.assert_called_with(None) + + @mock.patch.object(helpers.OpenStackHelper, "get_floating_ips") + def test_index(self, m_iplist): + tenant = fakes.tenants["baz"] + floating_list = fakes.floating_ips[tenant["id"]] + m_iplist.return_value = floating_list + result = self.controller.index(None) + expected = self.controller._get_ipreservation_resources(floating_list) + self.assertEqual(expected, result.resources) + m_iplist.assert_called_with(None) + + @mock.patch.object(helpers.OpenStackHelper, "get_floating_ip") + def test_show(self, m_ip): + tenant = fakes.tenants["baz"] + floating_ip = fakes.floating_ips[tenant["id"]][0] + m_ip.return_value = floating_ip + result = self.controller.show(None, floating_ip["id"]) + expected = self.controller._get_ipreservation_resources( + [floating_ip])[0] + self.assertIsInstance(result, ip_reservation.IPReservation) + self.assertEqual(expected, result) + m_ip.assert_called_with(None, floating_ip["id"]) + + @mock.patch.object(helpers.OpenStackHelper, + "release_floating_ip") + def test_delete(self, mock_release): + tenant = fakes.tenants["baz"] + floating_ip = fakes.floating_ips[tenant["id"]][0] + mock_release.return_value = [] + self.controller.delete(None, floating_ip["id"]) + mock_release.assert_called_with(None, floating_ip["id"]) + + @mock.patch.object(helpers.OpenStackHelper, + "allocate_floating_ip") + def test_create(self, mock_allocate): + tenant = fakes.tenants["baz"] + floating_list = fakes.floating_ips[tenant["id"]][0] + mock_allocate.return_value = floating_list + parameters = {} + categories = {ip_reservation.IPReservation.kind} + req = fake_nets.create_req_test_occi(parameters, categories) + + result = self.controller.create(req) + + expected = self.controller._get_ipreservation_resources( + [floating_list]) + self.assertEqual(expected, result.resources) + mock_allocate.assert_called_with(req, None) + + @mock.patch.object(helpers.OpenStackHelper, "allocate_floating_ip") + @mock.patch("ooi.occi.validator.Validator") + def test_create_pool(self, mock_validator, mock_allocate): + tenant = fakes.tenants["baz"] + floating_list = fakes.floating_ips[tenant["id"]][0] + mock_allocate.return_value = floating_list + pool_name = "public" + obj = { + "attributes": {}, + "schemes": { + os_network.OSFloatingIPPool.scheme: [pool_name], + } + } + parameters = {} + categories = {ip_reservation.IPReservation.kind} + req = fake_nets.create_req_test_occi(parameters, categories) + + req.get_parser = mock.MagicMock() + req.get_parser.return_value.return_value.parse.return_value = obj + mock_validator.validate.return_value = True + + result = self.controller.create(req) + + expected = self.controller._get_ipreservation_resources( + [floating_list]) + self.assertEqual(expected, result.resources) + mock_allocate.assert_called_with(req, pool_name) \ No newline at end of file diff --git a/ooi/tests/unit/controllers/test_network_links.py b/ooi/tests/unit/controllers/test_network_links.py index 71efee1..6c06837 100644 --- a/ooi/tests/unit/controllers/test_network_links.py +++ b/ooi/tests/unit/controllers/test_network_links.py @@ -22,6 +22,7 @@ from ooi.api import network_link as network_link_api from ooi import exception from ooi.occi.core import collection from ooi.occi.infrastructure import compute +from ooi.occi.infrastructure import ip_reservation from ooi.occi.infrastructure import network from ooi.occi.infrastructure import network_link from ooi.openstack import network as os_network @@ -133,16 +134,14 @@ class TestNetworkLinkController(base.TestController): os_link['network_id'], os_link['instance_id'], os_link['ip'], mac=None, pool=os_link['pool'], state=os_link['status'] ) - link_id = '%s_%s_%s' % ( + link_id = '%s_%s' % ( os_link['instance_id'], - os_link['network_id'], os_link['ip']) ret = self.controller.show(None, link_id) self.assertIsInstance(ret, os_network.OSNetworkInterface) self.assertEqual(os_link["ip"], ret.address) mock_get.assert_called_with(None, str(os_link['instance_id']), - os_link['network_id'], os_link['ip']) @mock.patch.object(network_link_api.Controller, "_get_interface_from_id") @@ -173,13 +172,13 @@ class TestNetworkLinkController(base.TestController): server_id = uuid.uuid4().hex net_id = uuid.uuid4().hex server_addr = "1.1.1.1" - link_id = "%s_%s_%s" % (server_id, net_id, server_addr) + link_id = "%s_%s" % (server_id, server_addr) mock_get_server.return_value = fake_nets.fake_build_link( net_id, server_id, server_addr ) ret = self.controller.show(None, link_id) self.assertIsInstance(ret, os_network.OSNetworkInterface) - mock_get_server.assert_called_with(None, server_id, net_id, + mock_get_server.assert_called_with(None, server_id, server_addr) def test_get_network_link_resources_fixed(self): @@ -228,7 +227,8 @@ class TestNetworkLinkController(base.TestController): ret = network_link_api._get_network_link_resources(None) self.assertEqual(ret.__len__(), 0) - @mock.patch.object(helpers.OpenStackHelper, "assign_floating_ip") + @mock.patch.object(helpers.OpenStackHelper, + "assign_floating_ip_deprecated") @mock.patch("ooi.api.helpers.get_id_with_kind") def test_create_public(self, mock_get_id, mock_assign): server_id = uuid.uuid4().hex @@ -243,7 +243,7 @@ class TestNetworkLinkController(base.TestController): mock_assign.return_value = fake_nets.fake_build_link( net_id, server_id, ip ) - mock_get_id.side_effect = [('', net_id), ('', server_id)] + mock_get_id.side_effect = [('', server_id), ('', net_id)] ret = self.controller.create(req) self.assertIsNotNone(ret) link = ret.resources.pop() @@ -254,12 +254,39 @@ class TestNetworkLinkController(base.TestController): self.assertEqual(server_id, link.source.id) mock_assign.assert_called_with(mock.ANY, net_id, server_id, None) + @mock.patch.object(helpers.OpenStackHelper, "assign_floating_ip") + @mock.patch("ooi.api.helpers.get_id_with_kind") + def test_create_ipreservation(self, mock_get_id, mock_assign): + server_id = uuid.uuid4().hex + net_id = "foo/ipreservation/%s" % uuid.uuid4().hex + ip = '8.0.0.0' + parameters = { + "occi.core.target": net_id, + "occi.core.source": server_id, + } + categories = {network_link.NetworkInterface.kind} + req = fake_nets.create_req_test_occi(parameters, categories) + mock_assign.return_value = fake_nets.fake_build_link( + net_id, server_id, ip, public_ip=True + ) + mock_get_id.side_effect = [('', server_id), ('', net_id)] + ret = self.controller.create(req) + self.assertIsNotNone(ret) + link = ret.resources.pop() + self.assertIsInstance(link, os_network.OSNetworkInterface) + self.assertIsInstance(link.source, compute.ComputeResource) + self.assertIsInstance(link.target, network.NetworkResource) + self.assertIsInstance(link.target, ip_reservation.IPReservation) + self.assertEqual(net_id, link.target.id) + self.assertEqual(server_id, link.source.id) + mock_assign.assert_called_with(mock.ANY, net_id, server_id) + @mock.patch.object(helpers.OpenStackHelper, "create_port") @mock.patch("ooi.api.helpers.get_id_with_kind") def test_create_fixed(self, mock_get_id, mock_cre_port): server_id = uuid.uuid4().hex net_id = uuid.uuid4().hex - mock_get_id.side_effect = [('', net_id), ('', server_id)] + mock_get_id.side_effect = [('', server_id), ('', net_id)] ip = '8.0.0.0' parameters = { "occi.core.target": net_id, @@ -327,7 +354,7 @@ class TestNetworkLinkController(base.TestController): mock_validator.validate.return_value = True mock_allocate.return_value = ip mock_associate.return_value = None - mock_get_id.side_effect = [('', net_id), ('', server_id)] + mock_get_id.side_effect = [('', server_id), ('', net_id)] ret = self.controller.create(req, None) link = ret.resources.pop() @@ -368,7 +395,7 @@ class TestNetworkLinkController(base.TestController): mock_validator.validate.return_value = True mock_allocate.return_value = ip mock_associate.return_value = None - mock_get_id.side_effect = [('', net_id), ('', server_id)] + mock_get_id.side_effect = [('', server_id), ('', net_id)] ret = self.controller.create(req, None) link = ret.resources.pop() diff --git a/ooi/tests/unit/controllers/test_query.py b/ooi/tests/unit/controllers/test_query.py index fcbeb65..5e10137 100644 --- a/ooi/tests/unit/controllers/test_query.py +++ b/ooi/tests/unit/controllers/test_query.py @@ -21,6 +21,7 @@ from ooi.occi.core import link from ooi.occi.core import resource from ooi.occi.infrastructure import compute from ooi.occi.infrastructure import contextualization +from ooi.occi.infrastructure import ip_reservation from ooi.occi.infrastructure import network from ooi.occi.infrastructure import network_link from ooi.occi.infrastructure import storage @@ -65,6 +66,7 @@ class TestQueryController(base.TestController): storage_link.StorageLink.kind, network.NetworkResource.kind, network_link.NetworkInterface.kind, + ip_reservation.IPReservation.kind, ] expected_mixins = [ @@ -133,6 +135,7 @@ class TestQueryController(base.TestController): storage_link.StorageLink.kind, network.NetworkResource.kind, network_link.NetworkInterface.kind, + ip_reservation.IPReservation.kind, ] expected_mixins = [ diff --git a/ooi/tests/unit/occi/test_occi_infrastructure.py b/ooi/tests/unit/occi/test_occi_infrastructure.py index a9ecb9e..5b6f49c 100644 --- a/ooi/tests/unit/occi/test_occi_infrastructure.py +++ b/ooi/tests/unit/occi/test_occi_infrastructure.py @@ -19,6 +19,7 @@ from ooi.occi.core import mixin from ooi.occi.core import resource from ooi.occi.infrastructure import compute from ooi.occi.infrastructure import contextualization +from ooi.occi.infrastructure import ip_reservation from ooi.occi.infrastructure import network from ooi.occi.infrastructure import network_link from ooi.occi.infrastructure import securitygroup @@ -439,3 +440,45 @@ class TestOCCISecurityGroupLink(base.TestCase): id=uuid.uuid4().hex) l = securitygroup_link.SecurityGroupLink(c, s, state="foobar") self.assertEqual("foobar", l.state) + + +class TestOCCIIPReservation(base.TestCase): + def test_ipreservation_class(self): + ir = ip_reservation.IPReservation + self.assertIn(network.up, ir.actions) + self.assertIn(network.down, ir.actions) + self.assertIn("occi.ipreservation.address", ir.attributes) + self.assertIn("occi.ipreservation.used", ir.attributes) + self.assertIn("occi.ipreservation.state", ir.attributes) + self.assertEqual(network.NetworkResource.kind, ir.kind.parent) + self.assertEqual(ir.kind.location, "ipreservation/") + + def test_ip_reservation(self): + id = uuid.uuid4().hex + ir = ip_reservation.IPReservation("foo", + address="xx", + id=id) + self.assertEqual("foo", ir.title) + self.assertEqual(id, ir.id) + self.assertEqual("xx", ir.address) + self.assertEqual(False, ir.used) + self.assertIsNone(ir.state) + + def test_setters(self): + ir = ip_reservation.IPReservation("foo", address="xx") + ir.address = "zzz" + self.assertEqual( + "zzz", + ir.attributes["occi.ipreservation.address"].value) + + def test_getters(self): + id_ip = uuid.uuid4().hex + ir = ip_reservation.IPReservation("foo", + address="xx", + state="active", + used=True, + id=id_ip) + self.assertEqual("active", ir.state) + self.assertEqual("xx", ir.address) + self.assertEqual(True, ir.used) + self.assertEqual(id_ip, ir.id) diff --git a/ooi/tests/unit/occi/test_openstack.py b/ooi/tests/unit/occi/test_openstack.py index 58fb3f9..2b0cb6a 100644 --- a/ooi/tests/unit/occi/test_openstack.py +++ b/ooi/tests/unit/occi/test_openstack.py @@ -118,7 +118,7 @@ class TestOSNetworkInterface(base.TestCase): id=uuid.uuid4().hex) i = os_network.OSNetworkInterface(c, n, "00:01:02:03:04:05", "127.0.0.1", pool="foo") - self.assertEqual('_'.join([c.id, n.id, "127.0.0.1"]), i.id) + self.assertEqual('_'.join([c.id, "127.0.0.1"]), i.id) self.assertEqual(i.address, "127.0.0.1") self.assertEqual(i.interface, "eth0") self.assertEqual(i.mac, "00:01:02:03:04:05") diff --git a/ooi/wsgi/__init__.py b/ooi/wsgi/__init__.py index 14451f6..dd93e8d 100644 --- a/ooi/wsgi/__init__.py +++ b/ooi/wsgi/__init__.py @@ -20,6 +20,7 @@ import routes.middleware import webob.dec import ooi.api.compute +import ooi.api.ip_reservation import ooi.api.network import ooi.api.network_link from ooi.api import query @@ -257,6 +258,11 @@ class OCCIMiddleware(object): self._setup_resource_routes("network", self.resources["network"]) + self.resources["ipreservation"] = self._create_resource( + ooi.api.ip_reservation.Controller) + self._setup_resource_routes("ipreservation", + self.resources["ipreservation"]) + @webob.dec.wsgify(RequestClass=Request) def __call__(self, req): response = self.process_request(req)