From 4b7dde26ea5d56dd1e4f22e530b24a9e47e064d0 Mon Sep 17 00:00:00 2001 From: Taku Fukushima Date: Mon, 3 Aug 2015 19:11:19 +0200 Subject: [PATCH] Implement /NetworkDriver.CreateNetwork This patch replaces the mocked version of /NetworkDriver.CreateNetwork with the actual Neutron call. The unit test for the endpoint is also implemented. Change-Id: I58517069470f1689d486e6a96a094f0dac959116 Signed-off-by: Taku Fukushima --- kuryr/controllers.py | 41 +++++++++++++++++++++ kuryr/tests/base.py | 8 +++- kuryr/tests/test_kuryr.py | 43 +++++++++++++++++++++- kuryr/tests/test_kuryr_network.py | 61 +++++++++++++++++++++++++++++++ kuryr/utils.py | 5 ++- 5 files changed, 153 insertions(+), 5 deletions(-) create mode 100644 kuryr/tests/test_kuryr_network.py diff --git a/kuryr/controllers.py b/kuryr/controllers.py index 01f17514..fcd6b180 100644 --- a/kuryr/controllers.py +++ b/kuryr/controllers.py @@ -10,12 +10,24 @@ # License for the specific language governing permissions and limitations # under the License. +import os + from flask import jsonify +from flask import request +from neutronclient.neutron import client from kuryr import app from kuryr.constants import SCHEMA +OS_URL = os.environ.get('OS_URL', 'http://127.0.0.1:9696/') +OS_TOKEN = os.environ.get('OS_TOKEN', '9999888877776666') + +# TODO(tfukushima): Retrieve configuration info from a config file. +app.neutron = client.Client('2.0', endpoint_url=OS_URL, token=OS_TOKEN) +app.neutron.format = 'json' + + @app.route('/Plugin.Activate', methods=['POST']) def plugin_activate(): return jsonify(SCHEMA['PLUGIN_ACTIVATE']) @@ -23,6 +35,35 @@ def plugin_activate(): @app.route('/NetworkDriver.CreateNetwork', methods=['POST']) def network_driver_create_network(): + """Creates a new Neutron Network which name is the given NetworkID. + + This function takes the following JSON data and delegates the actual + network creation to the Neutron client. libnetwork's NetworkID is used as + the name of Network in Neutron. :: + + { + "NetworkID": string, + "Options": { + ... + } + } + + See the following link for more details about the spec: + + https://github.com/docker/libnetwork/blob/master/docs/remote.md#create-network # noqa + """ + json_data = request.get_json(force=True) + + app.logger.debug("Received JSON data {0} for /NetworkDriver.CreateNetwork" + .format(json_data)) + # TODO(tfukushima): Add a validation of the JSON data for the network. + neutron_network_name = json_data['NetworkID'] + + network = app.neutron.create_network( + {'network': {'name': neutron_network_name, "admin_state_up": True}}) + + app.logger.info("Created a new network with name {0} successfully: {1}" + .format(neutron_network_name, network)) return jsonify(SCHEMA['SUCCESS']) diff --git a/kuryr/tests/base.py b/kuryr/tests/base.py index 432516ef..9fa4ed0d 100644 --- a/kuryr/tests/base.py +++ b/kuryr/tests/base.py @@ -10,12 +10,12 @@ # License for the specific language governing permissions and limitations # under the License. -from oslotest import base +from neutronclient.tests.unit.test_cli20 import CLITestV20Base from kuryr import app -class TestCase(base.BaseTestCase): +class TestCase(CLITestV20Base): """Test case base class for all unit tests.""" def setUp(self): @@ -23,6 +23,7 @@ class TestCase(base.BaseTestCase): app.config['DEBUG'] = True app.config['TESTING'] = True self.app = app.test_client() + self.app.neutron = self.client class TestKuryrBase(TestCase): @@ -30,9 +31,12 @@ class TestKuryrBase(TestCase): def setUp(self): super(TestKuryrBase, self).setUp() + self.app.neutron.format = 'json' def tearDown(self): super(TestKuryrBase, self).tearDown() + self.mox.VerifyAll() + self.mox.UnsetStubs() class TestKuryrFailures(TestKuryrBase): diff --git a/kuryr/tests/test_kuryr.py b/kuryr/tests/test_kuryr.py index e1b274c2..e243263d 100644 --- a/kuryr/tests/test_kuryr.py +++ b/kuryr/tests/test_kuryr.py @@ -10,9 +10,13 @@ # License for the specific language governing permissions and limitations # under the License. +import hashlib +import random + from ddt import ddt, data, unpack from oslo_serialization import jsonutils +from kuryr import app from kuryr.constants import SCHEMA from kuryr.tests.base import TestKuryrBase @@ -36,7 +40,6 @@ class TestKuryr(TestKuryrBase): - POST /NetworkDriver.Leave """ @data(('/Plugin.Activate', SCHEMA['PLUGIN_ACTIVATE']), - ('/NetworkDriver.CreateNetwork', SCHEMA['SUCCESS']), ('/NetworkDriver.DeleteNetwork', SCHEMA['SUCCESS']), ('/NetworkDriver.CreateEndpoint', SCHEMA['CREATE_ENDPOINT']), ('/NetworkDriver.EndpointOperInfo', SCHEMA['ENDPOINT_OPER_INFO']), @@ -48,3 +51,41 @@ class TestKuryr(TestKuryrBase): response = self.app.post(endpoint) decoded_json = jsonutils.loads(response.data) self.assertEqual(expected, decoded_json) + + def test_network_driver_create_network(self): + docker_network_id = hashlib.sha256( + str(random.getrandbits(256))).hexdigest() + self.mox.StubOutWithMock(app.neutron, "create_network") + fake_request = { + "network": { + "name": docker_network_id, + "admin_state_up": True + } + } + # The following fake response is retrieved from the Neutron doc: + # http://developer.openstack.org/api-ref-networking-v2.html#createNetwork # noqa + fake_response = { + "network": { + "status": "ACTIVE", + "subnets": [], + "name": docker_network_id, + "admin_state_up": True, + "tenant_id": "9bacb3c5d39d41a79512987f338cf177", + "router:external": False, + "segments": [], + "shared": False, + "id": "4e8e5957-649f-477b-9e5b-f1f75b21c03c" + } + } + app.neutron.create_network(fake_request).AndReturn(fake_response) + + self.mox.ReplayAll() + + data = {'NetworkID': docker_network_id, 'Options': {}} + response = self.app.post('/NetworkDriver.CreateNetwork', + content_type='application/json', + data=jsonutils.dumps(data)) + + self.assertEqual(200, response.status_code) + decoded_json = jsonutils.loads(response.data) + self.assertEqual(SCHEMA['SUCCESS'], decoded_json) diff --git a/kuryr/tests/test_kuryr_network.py b/kuryr/tests/test_kuryr_network.py new file mode 100644 index 00000000..8e209bec --- /dev/null +++ b/kuryr/tests/test_kuryr_network.py @@ -0,0 +1,61 @@ +# 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 + +from neutronclient.common.exceptions import Unauthorized +from oslo_serialization import jsonutils + +from kuryr import app +from kuryr.tests.base import TestKuryrFailures + + +class TestKuryrNetworkFailures(TestKuryrFailures): + """Unittests for checking if Kuryr handles the failures for the networks. + + This test covers error responses listed in the spec: + http://developer.openstack.org/api-ref-networking-v2-ext.html#createProviderNetwork # noqa + """ + + def _create_network_with_exception(self, network_name, ex): + self.mox.StubOutWithMock(app.neutron, "create_network") + fake_bad_request_without_name = { + "network": { + "name": network_name, + "admin_state_up": True + } + } + app.neutron.create_network( + fake_bad_request_without_name).AndRaise(ex) + self.mox.ReplayAll() + + def _invoke_create_request(self, network_name): + data = {'NetworkID': network_name, 'Options': {}} + response = self.app.post('/NetworkDriver.CreateNetwork', + content_type='application/json', + data=jsonutils.dumps(data)) + return response + + def test_create_network_unauthorized(self): + docker_network_id = hashlib.sha256( + str(random.getrandbits(256))).hexdigest() + self._create_network_with_exception( + docker_network_id, Unauthorized()) + + response = self._invoke_create_request(docker_network_id) + + self.assertEqual(401, response.status_code) + decoded_json = jsonutils.loads(response.data) + self.assertTrue('Err' in decoded_json) + self.assertEqual( + {'Err': 'Unauthorized: bad credentials.'}, decoded_json) diff --git a/kuryr/utils.py b/kuryr/utils.py index 06206a98..8514fc35 100644 --- a/kuryr/utils.py +++ b/kuryr/utils.py @@ -36,8 +36,9 @@ def make_json_app(import_name, **kwargs): @app.errorhandler(NeutronClientException) def make_json_error(ex): response = jsonify({"Err": str(ex)}) - response.status_code = (ex.code - if isinstance(ex, HTTPException) + response.status_code = (ex.code if isinstance(ex, HTTPException) + else ex.status_code + if isinstance(ex, NeutronClientException) else 500) content_type = 'application/vnd.docker.plugins.v1+json; charset=utf-8' response.headers['Content-Type'] = content_type