From daaa97fbdaefc0f9b96e0cf60431b1945fb9bcec Mon Sep 17 00:00:00 2001 From: Taku Fukushima Date: Thu, 6 Aug 2015 19:01:03 +0200 Subject: [PATCH] Implement /NetworkDriver.CreateEndpoint This patch replaces the mocked version of /NetworkDriver.CreateEndpoint with the actual Neutron call. This unit test for the endpoint is also implemented. libnetwork's Endpoint is mapped into Neutron's subnets and port. The request to create an Endpoint contains the information of libnetwork's Interfaces to be managed, however in Neutron there's no single resource corresponds to the Endpoint. Therefore Kuryr breaks the Endpoint into the following three pieces. 1. Subnet for Address property in the request - Kuryr passes the given Address property to CIDR for the request against Neutron API as it is 2. Subnet for AddressIPv6 property in the request - Kuryr passes the given AddressIPv6 property to CIDR for the request against Neutron API as it is 3. Port which MAC address is the same as MacAddress property in the request To distinguish Neutorn subnets created in 1. and 2., Kuryr names them by the given EndpointId with the their subnet addresses as the postfixes. For the Neutron port, Kuryr gives the EndpointID with the index of the interface and the postfix, "port". For instance, if Address, i.e., 10.0.1.1/24, and AddressIPV6, i.e., fe80::f816:3eff:fe20:57c4/64, which corresponding Neutron subnets don't exist are given in a request against /NetworkDriver.CreateEndpoint, the following subnets and a port will be created. * 98953db3f8e6628caf4a7cad3c866cb090654e3dee3e37206ad8c0a81355f1b7-10.0.1.0 * 98953db3f8e6628caf4a7cad3c866cb090654e3dee3e37206ad8c0a81355f1b7-fe80:: * 98953db3f8e6628caf4a7cad3c866cb090654e3dee3e37206ad8c0a81355f1b7-0-port In the process 1. and 2., Kuryr doesn't specify the allocation list for the addresses and it's the responsibility of Neutron to allocate an appropriate set of the addresses. In the latter workflow, where a container joins the network and be assinged for the specific address, Kuryr binds the container with the created port which IP address that is allocated in this phase. This patch implements the following IPAM blueprint in Kuryr partially except for retrieving the names of the default subnets from the config file: https://blueprints.launchpad.net/kuryr/+spec/ipam Change-Id: I1798b3baff56e285059435f2b8620f36811b580f Signed-off-by: Taku Fukushima --- kuryr/controllers.py | 181 ++++++++++++++++++++- kuryr/tests/test_kuryr.py | 145 ++++++++++++++++- kuryr/tests/test_kuryr_endpoint.py | 247 +++++++++++++++++++++++++++++ requirements.txt | 1 + 4 files changed, 572 insertions(+), 2 deletions(-) create mode 100644 kuryr/tests/test_kuryr_endpoint.py diff --git a/kuryr/controllers.py b/kuryr/controllers.py index c9dcdfe1..076e46ea 100644 --- a/kuryr/controllers.py +++ b/kuryr/controllers.py @@ -14,6 +14,8 @@ import os from flask import jsonify from flask import request +import netaddr +from neutronclient.common import exceptions as n_exceptions from kuryr import app from kuryr.constants import SCHEMA @@ -37,9 +39,138 @@ if OS_USERNAME and OS_PASSWORD: else: app.neutron = utils.get_neutron_client_simple(url=OS_URL, token=OS_TOKEN) +# TODO(tfukushima): Retrieve the following subnet names from the config file. +SUBNET_POOLS_V4 = [ + p.strip() for p in os.environ.get('SUBNET_POOLS_V4', 'kuryr').split(',')] +SUBNET_POOLS_V6 = [ + p.strip() for p in os.environ.get('SUBNET_POOLS_V6', 'kuryr6').split(',')] + app.neutron.format = 'json' +def _get_subnets_by_attrs(**attrs): + subnets = app.neutron.list_subnets(**attrs) + if len(subnets) > 1: + raise exceptions.DuplicatedResourceException( + "Multiple Neutron subnets exist for the params {0} " + .format(', '.join(['{0}={1}'.format(k, v) + for k, v in attrs.items()]))) + return subnets['subnets'] + + +def _handle_allocation_from_pools(neutron_network_id, existing_subnets): + for v4_subnet_name in SUBNET_POOLS_V4: + v4_subnets = _get_subnets_by_attrs( + network_id=neutron_network_id, name=v4_subnet_name) + existing_subnets += v4_subnets + + for v6_subnet_name in SUBNET_POOLS_V6: + v6_subnets = _get_subnets_by_attrs( + network_id=neutron_network_id, name=v6_subnet_name) + existing_subnets += v6_subnets + + +def _process_subnet(neutron_network_id, endpoint_id, interface_cidr, + new_subnets, existing_subnets): + subnets = _get_subnets_by_attrs( + network_id=neutron_network_id, cidr=interface_cidr) + if subnets: + existing_subnets += subnets + else: + cidr = netaddr.IPNetwork(interface_cidr) + subnet_network = str(cidr.network) + subnet_cidr = '/'.join([subnet_network, + str(cidr.prefixlen)]) + new_subnets.append({ + 'name': '-'.join([endpoint_id, subnet_network]), + # Allocate all IP addresses in the subnet. + 'allocation_pools': None, + 'network_id': neutron_network_id, + 'ip_version': cidr.version, + 'cidr': subnet_cidr, + }) + + +def _handle_explicit_allocation(neutron_network_id, endpoint_id, + interface_cidrv4, interface_cidrv6, + new_subnets, existing_subnets): + if interface_cidrv4: + _process_subnet(neutron_network_id, endpoint_id, interface_cidrv4, + new_subnets, existing_subnets) + + if interface_cidrv6: + _process_subnet(neutron_network_id, endpoint_id, interface_cidrv6, + new_subnets, existing_subnets) + + if new_subnets: + # Bulk create operation of subnets + created_subnets = app.neutron.create_subnet({'subnets': new_subnets}) + + return created_subnets + + +def _create_subnets_and_or_port(interfaces, neutron_network_id, endpoint_id): + response_interfaces = [] + for interface in interfaces: + existing_subnets = [] + created_subnets = {} + # v4 and v6 Subnets for bulk creation. + new_subnets = [] + + interface_id = interface['ID'] + interface_cidrv4 = interface.get('Address', '') + interface_cidrv6 = interface.get('AddressIPv6', '') + interface_mac = interface['MacAddress'] + + if interface_cidrv4 or interface_cidrv6: + created_subnets = _handle_explicit_allocation( + neutron_network_id, endpoint_id, interface_cidrv4, + interface_cidrv6, new_subnets, existing_subnets) + else: + _handle_allocation_from_pools( + neutron_network_id, existing_subnets) + + try: + port = { + 'name': '-'.join([endpoint_id, str(interface_id), 'port']), + 'admin_state_up': True, + 'mac_address': interface_mac, + 'network_id': neutron_network_id, + } + created_subnets = created_subnets.get('subnets', []) + all_subnets = created_subnets + existing_subnets + fixed_ips = port['fixed_ips'] = [] + for subnet in all_subnets: + fixed_ip = {'subnet_id': subnet['id']} + if subnet['ip_version'] == 4: + cidr = netaddr.IPNetwork(interface_cidrv4) + else: + cidr = netaddr.IPNetwork(interface_cidrv6) + subnet_cidr = '/'.join([str(cidr.network), + str(cidr.prefixlen)]) + if subnet['cidr'] != subnet_cidr: + continue + fixed_ip['ip_address'] = str(cidr.ip) + fixed_ips.append(fixed_ip) + app.neutron.create_port({'port': port}) + + response_interfaces.append({ + 'ID': interface_id, + 'Address': interface_cidrv4, + 'AddressIPv6': interface_cidrv6, + 'MacAddress': interface_mac + }) + except n_exceptions.NeutronClientException as ex: + app.logger.error("Error happend during creating a " + "Neutron port: {0}".format(ex)) + # Rollback the subnets creation + for subnet in created_subnets: + app.neutron.delete_subnet(subnet['id']) + raise + + return response_interfaces + + @app.route('/Plugin.Activate', methods=['POST']) def plugin_activate(): return jsonify(SCHEMA['PLUGIN_ACTIVATE']) @@ -122,7 +253,55 @@ def network_driver_delete_network(): @app.route('/NetworkDriver.CreateEndpoint', methods=['POST']) def network_driver_create_endpoint(): - return jsonify(SCHEMA['CREATE_ENDPOINT']) + """Creates new Neutron Subnets and a Port with the given EndpointID. + + This function takes the following JSON data and delegates the actual + endpoint creation to the Neutron client mapping it into Subnet and Port. :: + + { + "NetworkID": string, + "EndpointID": string, + "Options": { + ... + }, + "Interfaces": [{ + "ID": int, + "Address": string, + "AddressIPv6": string, + "MacAddress": string + }, ...] + } + + See the following link for more details about the spec: + + https://github.com/docker/libnetwork/blob/master/docs/remote.md#create-endpoint # noqa + """ + json_data = request.get_json(force=True) + + app.logger.debug("Received JSON data {0} for /NetworkDriver.CreateEndpoint" + .format(json_data)) + # TODO(tfukushima): Add a validation of the JSON data for the subnet. + neutron_network_name = json_data['NetworkID'] + endpoint_id = json_data['EndpointID'] + + filtered_networks = app.neutron.list_networks(name=neutron_network_name) + + if not filtered_networks: + return jsonify({ + 'Err': "Neutron network associated with ID {0} doesn't exit." + .format(neutron_network_name) + }) + elif len(filtered_networks) > 1: + raise exceptions.DuplicatedResourceException( + "Multiple Neutron Networks exist for NetworkID {0}" + .format(neutron_network_name)) + else: + neutron_network_id = filtered_networks['networks'][0]['id'] + interfaces = json_data['Interfaces'] + response_interfaces = _create_subnets_and_or_port( + interfaces, neutron_network_id, endpoint_id) + + return jsonify({'Interfaces': response_interfaces}) @app.route('/NetworkDriver.EndpointOperInfo', methods=['POST']) diff --git a/kuryr/tests/test_kuryr.py b/kuryr/tests/test_kuryr.py index 2e67d26a..ddd5ea6b 100644 --- a/kuryr/tests/test_kuryr.py +++ b/kuryr/tests/test_kuryr.py @@ -41,7 +41,6 @@ class TestKuryr(TestKuryrBase): - POST /NetworkDriver.Leave """ @data(('/Plugin.Activate', SCHEMA['PLUGIN_ACTIVATE']), - ('/NetworkDriver.CreateEndpoint', SCHEMA['CREATE_ENDPOINT']), ('/NetworkDriver.EndpointOperInfo', SCHEMA['ENDPOINT_OPER_INFO']), ('/NetworkDriver.DeleteEndpoint', SCHEMA['SUCCESS']), ('/NetworkDriver.Join', SCHEMA['JOIN']), @@ -108,3 +107,147 @@ class TestKuryr(TestKuryrBase): self.assertEqual(200, response.status_code) decoded_json = jsonutils.loads(response.data) self.assertEqual(SCHEMA['SUCCESS'], decoded_json) + + def test_network_driver_create_endpoint(self): + docker_network_id = hashlib.sha256( + str(random.getrandbits(256))).hexdigest() + docker_endpoint_id = hashlib.sha256( + str(random.getrandbits(256))).hexdigest() + + fake_neutron_network_id = str(uuid.uuid4()) + self._mock_out_network(fake_neutron_network_id, docker_network_id) + + self.mox.StubOutWithMock(app.neutron, 'list_subnets') + fake_existing_subnets_response = { + "subnets": [] + } + fake_cidr_v4 = '192.168.1.2/24' + app.neutron.list_subnets( + network_id=fake_neutron_network_id, + cidr=fake_cidr_v4).AndReturn(fake_existing_subnets_response) + + fake_cidr_v6 = 'fe80::f816:3eff:fe20:57c4/64' + app.neutron.list_subnets( + network_id=fake_neutron_network_id, + cidr=fake_cidr_v6).AndReturn(fake_existing_subnets_response) + + self.mox.StubOutWithMock(app.neutron, 'create_subnet') + fake_subnet_request = { + "subnets": [{ + 'name': '-'.join([docker_endpoint_id, + '192.168.1.0']), + 'network_id': fake_neutron_network_id, + 'allocation_pools': None, + 'ip_version': 4, + "cidr": '192.168.1.0/24' + }, { + 'name': '-'.join([docker_endpoint_id, + 'fe80::']), + 'network_id': fake_neutron_network_id, + 'allocation_pools': None, + 'ip_version': 6, + "cidr": 'fe80::/64' + }] + } + # The following fake response is retrieved from the Neutron doc: + # http://developer.openstack.org/api-ref-networking-v2.html#createSubnet # noqa + subnet_v4_id = "9436e561-47bf-436a-b1f1-fe23a926e031" + subnet_v6_id = "64dd4a98-3d7a-4bfd-acf4-91137a8d2f51" + fake_subnet_response = { + "subnets": [{ + "name": '-'.join([docker_endpoint_id, + '192.168.1.0']), + "network_id": docker_network_id, + "tenant_id": "c1210485b2424d48804aad5d39c61b8f", + "allocation_pools": [{ + "start": "192.168.1.2", + "end": "192.168.1.254" + }], + "gateway_ip": "192.168.1.1", + "ip_version": 4, + "cidr": '192.168.1.0/24', + "id": subnet_v4_id, + "enable_dhcp": True + }, { + "name": '-'.join([docker_endpoint_id, + 'fe80::']), + "network_id": docker_network_id, + "tenant_id": "c1210485b2424d48804aad5d39c61b8f", + "allocation_pools": [{ + "start": "fe80::f816:3eff:fe20:57c4", + "end": "fe80::ffff:ffff:ffff:ffff" + }], + "gateway_ip": "fe80::f816:3eff:fe20:57c3", + "ip_version": 6, + "cidr": 'fe80::/64', + "id": subnet_v6_id, + "enable_dhcp": True + }] + } + app.neutron.create_subnet( + fake_subnet_request).AndReturn(fake_subnet_response) + + subnet_v4_address = fake_cidr_v4.split('/')[0] + subnet_v6_address = fake_cidr_v6.split('/')[0] + self.mox.StubOutWithMock(app.neutron, 'create_port') + fake_port_request = { + 'port': { + 'name': '-'.join([docker_endpoint_id, '0', 'port']), + 'admin_state_up': True, + 'mac_address': "fa:16:3e:20:57:c3", + 'network_id': fake_neutron_network_id, + 'fixed_ips': [{ + 'subnet_id': subnet_v4_id, + 'ip_address': subnet_v4_address + }, { + 'subnet_id': subnet_v6_id, + 'ip_address': subnet_v6_address + }] + } + } + # The following fake response is retrieved from the Neutron doc: + # http://developer.openstack.org/api-ref-networking-v2.html#createPort # noqa + fake_port = { + "port": { + "status": "DOWN", + "name": '-'.join([docker_endpoint_id, '0', 'port']), + "allowed_address_pairs": [], + "admin_state_up": True, + "network_id": fake_neutron_network_id, + "tenant_id": "d6700c0c9ffa4f1cb322cd4a1f3906fa", + "device_owner": "", + "mac_address": "fa:16:3e:20:57:c3", + "fixed_ips": [{ + 'subnet_id': subnet_v4_id, + 'ip_address': subnet_v4_address + }, { + 'subnet_id': subnet_v6_id, + 'ip_address': subnet_v6_address + }], + "id": "65c0ee9f-d634-4522-8954-51021b570b0d", + "security_groups": [], + "device_id": "" + } + } + app.neutron.create_port(fake_port_request).AndReturn(fake_port) + self.mox.ReplayAll() + + data = { + 'NetworkID': docker_network_id, + 'EndpointID': docker_endpoint_id, + 'Options': {}, + 'Interfaces': [{ + 'ID': 0, + 'Address': '192.168.1.2/24', + 'AddressIPv6': 'fe80::f816:3eff:fe20:57c4/64', + 'MacAddress': "fa:16:3e:20:57:c3" + }] + } + response = self.app.post('/NetworkDriver.CreateEndpoint', + content_type='application/json', + data=jsonutils.dumps(data)) + + self.assertEqual(200, response.status_code) + decoded_json = jsonutils.loads(response.data) + expected = {'Interfaces': data['Interfaces']} + self.assertEqual(expected, decoded_json) diff --git a/kuryr/tests/test_kuryr_endpoint.py b/kuryr/tests/test_kuryr_endpoint.py new file mode 100644 index 00000000..889f5016 --- /dev/null +++ b/kuryr/tests/test_kuryr_endpoint.py @@ -0,0 +1,247 @@ +# 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 hashlib +import random +import uuid + +from ddt import data +from ddt import ddt +from neutronclient.common import exceptions +from oslo_serialization import jsonutils + +from kuryr import app +from kuryr.tests.base import TestKuryrFailures + + +@ddt +class TestKuryrEndpointCreateFailures(TestKuryrFailures): + """Unittests for the failures for creating endpoints. + + This test covers error responses listed in the spec: + http://developer.openstack.org/api-ref-networking-v2.html#createSubnet # noqa + http://developer.openstack.org/api-ref-networking-v2-ext.html#createPort # noqa + """ + @staticmethod + def _get_fake_subnets(docker_endpoint_id, neutron_network_id, + fake_neutron_subnet1_id, fake_neutron_subnet2_id): + # The following fake response is retrieved from the Neutron doc: + # http://developer.openstack.org/api-ref-networking-v2.html#createSubnet # noqa + fake_subnet_response = { + "subnets": [{ + "name": '-'.join([docker_endpoint_id, '192.168.1.0']), + "network_id": neutron_network_id, + "tenant_id": "c1210485b2424d48804aad5d39c61b8f", + "allocation_pools": [{"start": "192.168.1.2", + "end": "192.168.1.254"}], + "gateway_ip": "192.168.1.1", + "ip_version": 4, + "cidr": "192.168.1.0/24", + "id": fake_neutron_subnet1_id, + "enable_dhcp": True + }, { + "name": '-'.join([docker_endpoint_id, 'fe80::']), + "network_id": neutron_network_id, + "tenant_id": "c1210485b2424d48804aad5d39c61b8f", + "allocation_pools": [{"start": "fe80::f816:3eff:fe20:57c4", + "end": "fe80::ffff:ffff:ffff:ffff"}], + "gateway_ip": "fe80::f816:3eff:fe20:57c3", + "ip_version": 6, + "cidr": "fe80::/64", + "id": fake_neutron_subnet2_id, + "enable_dhcp": True + }] + } + return fake_subnet_response + + def _create_subnet_with_exception(self, neutron_network_id, + docker_endpoint_id, ex): + fake_neutron_subnet1_id = str(uuid.uuid4()) + fake_neutron_subnet2_id = str(uuid.uuid4()) + + self.mox.StubOutWithMock(app.neutron, 'create_subnet') + fake_subnet_request = { + 'subnets': [{ + 'name': '-'.join([docker_endpoint_id, '192.168.1.0']), + 'network_id': neutron_network_id, + 'allocation_pools': None, + 'ip_version': 4, + "cidr": '192.168.1.0/24' + }, { + 'name': '-'.join([docker_endpoint_id, 'fe80::']), + 'network_id': neutron_network_id, + 'allocation_pools': None, + 'ip_version': 6, + "cidr": 'fe80::/64' + }] + } + fake_subnets = self.__class__._get_fake_subnets( + docker_endpoint_id, neutron_network_id, + fake_neutron_subnet1_id, fake_neutron_subnet2_id) + + if ex: + app.neutron.create_subnet(fake_subnet_request).AndRaise(ex) + else: + app.neutron.create_subnet( + fake_subnet_request).AndReturn(fake_subnets) + self.mox.ReplayAll() + + return (fake_neutron_subnet1_id, fake_neutron_subnet2_id) + + def _delete_subnet_with_exception(self, neutron_subnet_id, ex): + self.mox.StubOutWithMock(app.neutron, 'delete_subnet') + if ex: + app.neutron.delete_subnet(neutron_subnet_id).AndRaise(ex) + else: + app.neutron.delete_subnet(neutron_subnet_id).AndReturn(None) + self.mox.ReplayAll() + + def _delete_subnets_with_exception(self, neutron_subnet_ids, ex): + self.mox.StubOutWithMock(app.neutron, 'delete_subnet') + for neutron_subnet_id in neutron_subnet_ids: + if ex: + app.neutron.delete_subnet(neutron_subnet_id).AndRaise(ex) + else: + app.neutron.delete_subnet(neutron_subnet_id).AndReturn(None) + self.mox.ReplayAll() + + def _create_port_with_exception(self, neutron_network_id, + docker_endpoint_id, neutron_subnetv4_id, + neutron_subnetv6_id, ex): + self.mox.StubOutWithMock(app.neutron, 'create_port') + fake_port_request = { + 'port': { + 'name': '-'.join([docker_endpoint_id, '0', 'port']), + 'admin_state_up': True, + 'fixed_ips': [{ + 'subnet_id': neutron_subnetv4_id, + 'ip_address': '192.168.1.2' + }, { + 'subnet_id': neutron_subnetv6_id, + 'ip_address': 'fe80::f816:3eff:fe20:57c4' + }], + 'mac_address': "fa:16:3e:20:57:c3", + 'network_id': neutron_network_id + } + } + # The following fake response is retrieved from the Neutron doc: + # http://developer.openstack.org/api-ref-networking-v2.html#createPort # noqa + fake_port = { + "port": { + "status": "DOWN", + "name": '-'.join([docker_endpoint_id, '0', 'port']), + "allowed_address_pairs": [], + "admin_state_up": True, + "network_id": neutron_network_id, + "tenant_id": "d6700c0c9ffa4f1cb322cd4a1f3906fa", + "device_owner": "", + "mac_address": "fa:16:3e:20:57:c3", + 'fixed_ips': [{ + 'subnet_id': neutron_subnetv4_id, + 'ip_address': '192.168.1.2' + }, { + 'subnet_id': neutron_subnetv6_id, + 'ip_address': 'fe80::f816:3eff:fe20:57c4' + }], + "id": "65c0ee9f-d634-4522-8954-51021b570b0d", + "security_groups": [], + "device_id": "" + } + } + if ex: + app.neutron.create_port(fake_port_request).AndRaise(ex) + else: + app.neutron.create_port(fake_port_request).AndReturn(fake_port) + self.mox.ReplayAll() + + def _invoke_create_request(self, docker_network_id, docker_endpoint_id): + data = { + 'NetworkID': docker_network_id, + 'EndpointID': docker_endpoint_id, + 'Options': {}, + 'Interfaces': [{ + 'ID': 0, + 'Address': '192.168.1.2/24', + 'AddressIPv6': 'fe80::f816:3eff:fe20:57c4/64', + 'MacAddress': "fa:16:3e:20:57:c3" + }] + } + response = self.app.post('/NetworkDriver.CreateEndpoint', + content_type='application/json', + data=jsonutils.dumps(data)) + return response + + @data(exceptions.Unauthorized, exceptions.Forbidden, exceptions.NotFound, + exceptions.Conflict) + def test_create_endpoint_subnet_failures(self, GivenException): + fake_docker_network_id = hashlib.sha256( + str(random.getrandbits(256))).hexdigest() + fake_docker_endpoint_id = hashlib.sha256( + str(random.getrandbits(256))).hexdigest() + fake_neutron_network_id = str(uuid.uuid4()) + + self.mox.StubOutWithMock(app.neutron, 'list_subnets') + app.neutron.list_subnets( + network_id=fake_neutron_network_id, + cidr='192.168.1.2/24').AndReturn({'subnets': []}) + app.neutron.list_subnets( + network_id=fake_neutron_network_id, + cidr='fe80::f816:3eff:fe20:57c4/64').AndReturn({'subnets': []}) + + self._create_subnet_with_exception( + fake_neutron_network_id, fake_docker_endpoint_id, GivenException()) + self._mock_out_network(fake_neutron_network_id, fake_docker_network_id) + + response = self._invoke_create_request( + fake_docker_network_id, fake_docker_endpoint_id) + + self.assertEqual(GivenException.status_code, response.status_code) + decoded_json = jsonutils.loads(response.data) + self.assertTrue('Err' in decoded_json) + self.assertEqual({'Err': GivenException.message}, decoded_json) + + @data(exceptions.Unauthorized, exceptions.Forbidden, exceptions.NotFound, + exceptions.ServiceUnavailable) + def test_create_endpoint_port_failures(self, GivenException): + fake_docker_network_id = hashlib.sha256( + str(random.getrandbits(256))).hexdigest() + fake_docker_endpoint_id = hashlib.sha256( + str(random.getrandbits(256))).hexdigest() + fake_neutron_network_id = str(uuid.uuid4()) + + self.mox.StubOutWithMock(app.neutron, 'list_subnets') + app.neutron.list_subnets( + network_id=fake_neutron_network_id, + cidr='192.168.1.2/24').AndReturn({'subnets': []}) + app.neutron.list_subnets( + network_id=fake_neutron_network_id, + cidr='fe80::f816:3eff:fe20:57c4/64').AndReturn({'subnets': []}) + + (fake_neutron_subnet1_id, + fake_neutron_subnet2_id) = self._create_subnet_with_exception( + fake_neutron_network_id, fake_docker_endpoint_id, None) + self._create_port_with_exception(fake_neutron_network_id, + fake_docker_endpoint_id, fake_neutron_subnet1_id, + fake_neutron_subnet2_id, GivenException()) + self._mock_out_network(fake_neutron_network_id, fake_docker_network_id) + + # The port creation is failed and Kuryr rolles the created subnet back. + self._delete_subnets_with_exception( + [fake_neutron_subnet1_id, fake_neutron_subnet2_id], None) + + response = self._invoke_create_request( + fake_docker_network_id, fake_docker_endpoint_id) + + self.assertEqual(GivenException.status_code, response.status_code) + decoded_json = jsonutils.loads(response.data) + self.assertTrue('Err' in decoded_json) + self.assertEqual({'Err': GivenException.message}, decoded_json) diff --git a/requirements.txt b/requirements.txt index d26de6b8..978fe1cb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,5 +5,6 @@ pbr<2.0,>=1.3 Babel>=1.3 Flask>=0.10,<1.0 +netaddr>=0.7.12 oslo.serialization>=1.4.0 # Apache-2.0 python-neutronclient>=2.3.11,<3