From 32af49e0d3ed8f962b219a47cc644079aa9d7b7a Mon Sep 17 00:00:00 2001 From: Joe Mills Date: Tue, 1 Jul 2014 09:07:05 +0000 Subject: [PATCH] Implement Midonet Juno Network Api calls This patch is the first in a series of converting the current midonet plugin code to the new model described in blueprint midonet-plugin-juno. This patch focuses specifically on the "network" handlers. This patch also updates the midonet UT to handle the new objects that need to be mocked with the new model. Change-Id: I056d698ce2e312527e756ff56c962cff83a6bc04 Partially-Implements: blueprint midonet-plugin-juno --- neutron/plugins/midonet/plugin.py | 82 +++++++++++-------- .../tests/unit/midonet/test_midonet_plugin.py | 47 +++++++++-- 2 files changed, 88 insertions(+), 41 deletions(-) diff --git a/neutron/plugins/midonet/plugin.py b/neutron/plugins/midonet/plugin.py index ac699c37db0..4bbbf236dfe 100644 --- a/neutron/plugins/midonet/plugin.py +++ b/neutron/plugins/midonet/plugin.py @@ -20,9 +20,14 @@ # @author: Rossella Sblendido, Midokura Japan KK # @author: Duarte Nunes, Midokura Japan KK +import functools + from midonetclient import api +from midonetclient import exc +from midonetclient.neutron import client as n_client from oslo.config import cfg from sqlalchemy.orm import exc as sa_exc +from webob import exc as w_exc from neutron.api.v2 import attributes from neutron.common import constants @@ -66,6 +71,21 @@ SG_PORT_GROUP_NAME = "OS_PG_%s" SNAT_RULE = 'SNAT' +def handle_api_error(fn): + """Wrapper for methods that throws custom exceptions.""" + @functools.wraps(fn) + def wrapped(*args, **kwargs): + try: + return fn(*args, **kwargs) + except (w_exc.HTTPException, exc.MidoApiConnectionError) as ex: + raise MidonetApiException(msg=ex) + return wrapped + + +class MidonetApiException(n_exc.NeutronException): + message = _("MidoNet API error: %(msg)s") + + def _get_nat_ips(type, fip): """Get NAT IP address information. @@ -206,6 +226,10 @@ class MidonetPluginV2(db_base_plugin_v2.NeutronDbPluginV2, self.provider_router_id = midonet_conf.provider_router_id self.provider_router = None + self.api_cli = n_client.MidonetClient(midonet_conf.midonet_uri, + midonet_conf.username, + midonet_conf.password, + project_id=midonet_conf.project_id) self.mido_api = api.MidonetApi(midonet_uri, admin_user, admin_pass, project_id=admin_project_id) @@ -445,72 +469,58 @@ class MidonetPluginV2(db_base_plugin_v2.NeutronDbPluginV2, LOG.debug(_("MidonetPluginV2.delete_subnet exiting")) + @handle_api_error def create_network(self, context, network): """Create Neutron network. Create a new Neutron network and its corresponding MidoNet bridge. """ - LOG.debug(_('MidonetPluginV2.create_network called: network=%r'), + LOG.debug('MidonetPluginV2.create_network called: network=%r', network) + net_data = network['network'] tenant_id = self._get_tenant_id_for_create(context, net_data) net_data['tenant_id'] = tenant_id self._ensure_default_security_group(context, tenant_id) - bridge = self.client.create_bridge(**net_data) - net_data['id'] = bridge.get_id() - - session = context.session - with session.begin(subtransactions=True): + with context.session.begin(subtransactions=True): net = super(MidonetPluginV2, self).create_network(context, network) self._process_l3_create(context, net, net_data) + self.api_cli.create_network(net) - LOG.debug(_("MidonetPluginV2.create_network exiting: net=%r"), net) + LOG.debug("MidonetPluginV2.create_network exiting: net=%r", net) return net + @handle_api_error def update_network(self, context, id, network): """Update Neutron network. Update an existing Neutron network and its corresponding MidoNet bridge. """ - LOG.debug(_("MidonetPluginV2.update_network called: id=%(id)r, " - "network=%(network)r"), {'id': id, 'network': network}) - session = context.session - with session.begin(subtransactions=True): + LOG.debug("MidonetPluginV2.update_network called: id=%(id)r, " + "network=%(network)r", {'id': id, 'network': network}) + + with context.session.begin(subtransactions=True): net = super(MidonetPluginV2, self).update_network( context, id, network) self._process_l3_update(context, net, network['network']) - self.client.update_bridge(id, **network['network']) + self.api_cli.update_network(id, net) - LOG.debug(_("MidonetPluginV2.update_network exiting: net=%r"), net) + LOG.debug("MidonetPluginV2.update_network exiting: net=%r", net) return net - def get_network(self, context, id, fields=None): - """Get Neutron network. - - Retrieves a Neutron network and its corresponding MidoNet bridge. - """ - LOG.debug(_("MidonetPluginV2.get_network called: id=%(id)r, " - "fields=%(fields)r"), {'id': id, 'fields': fields}) - qnet = super(MidonetPluginV2, self).get_network(context, id, fields) - self.client.get_bridge(id) - - LOG.debug(_("MidonetPluginV2.get_network exiting: qnet=%r"), qnet) - return qnet - + @handle_api_error def delete_network(self, context, id): """Delete a network and its corresponding MidoNet bridge.""" - LOG.debug(_("MidonetPluginV2.delete_network called: id=%r"), id) - self.client.delete_bridge(id) - try: - with context.session.begin(subtransactions=True): - self._process_l3_delete(context, id) - super(MidonetPluginV2, self).delete_network(context, id) - except Exception: - with excutils.save_and_reraise_exception(): - LOG.error(_('Failed to delete neutron db, while Midonet ' - 'bridge=%r had been deleted'), id) + LOG.debug("MidonetPluginV2.delete_network called: id=%r", id) + + with context.session.begin(subtransactions=True): + self._process_l3_delete(context, id) + super(MidonetPluginV2, self).delete_network(context, id) + self.api_cli.delete_network(id) + + LOG.debug("MidonetPluginV2.delete_network exiting: id=%r", id) def create_port(self, context, port): """Create a L2 port in Neutron/MidoNet.""" diff --git a/neutron/tests/unit/midonet/test_midonet_plugin.py b/neutron/tests/unit/midonet/test_midonet_plugin.py index 80a6cb9727d..5129fef063a 100644 --- a/neutron/tests/unit/midonet/test_midonet_plugin.py +++ b/neutron/tests/unit/midonet/test_midonet_plugin.py @@ -53,12 +53,27 @@ class MidonetPluginV2TestCase(test_plugin.NeutronDbPluginV2TestCase): self.instance = self.mock_api.start() mock_cfg = mock_lib.MidonetLibMockConfig(self.instance.return_value) mock_cfg.setup() - super(MidonetPluginV2TestCase, self).setUp(plugin=plugin, - ext_mgr=ext_mgr) - def tearDown(self): - super(MidonetPluginV2TestCase, self).tearDown() - self.mock_api.stop() + self.midoclient_mock = mock.MagicMock() + self.midoclient_mock.midonetclient.neutron.client.return_value = True + modules = { + 'midonetclient': self.midoclient_mock, + 'midonetclient.neutron': self.midoclient_mock.neutron, + 'midonetclient.neutron.client': self.midoclient_mock.client, + } + + self.module_patcher = mock.patch.dict('sys.modules', modules) + self.module_patcher.start() + self.addCleanup(self.module_patcher.stop) + + # import midonetclient here because it needs proper mock objects to be + # assigned to this module first. 'midoclient_mock' object is the + # mock object used for this module. + from midonetclient.neutron.client import MidonetClient + client_class = MidonetClient + self.mock_class = client_class() + + super(MidonetPluginV2TestCase, self).setUp(plugin=plugin) class TestMidonetNetworksV2(test_plugin.TestNetworksV2, @@ -131,6 +146,9 @@ class TestMidonetL3NatTestCase(MidonetPluginV2TestCase, None) self.assertTrue(self.instance.return_value.add_static_nat.called) + def test_delete_ext_net_with_disassociated_floating_ips(self): + pass + class TestMidonetSecurityGroupsTestCase(sg.SecurityGroupDBTestCase): @@ -150,6 +168,25 @@ class TestMidonetSecurityGroupsTestCase(sg.SecurityGroupDBTestCase): p.start() # dict patches must be explicitly stopped self.addCleanup(p.stop) + self.midoclient_mock = mock.MagicMock() + self.midoclient_mock.midonetclient.neutron.client.return_value = True + modules = { + 'midonetclient': self.midoclient_mock, + 'midonetclient.neutron': self.midoclient_mock.neutron, + 'midonetclient.neutron.client': self.midoclient_mock.client, + } + + self.module_patcher = mock.patch.dict('sys.modules', modules) + self.module_patcher.start() + self.addCleanup(self.module_patcher.stop) + + # import midonetclient here because it needs proper mock objects to be + # assigned to this module first. 'midoclient_mock' object is the + # mock object used for this module. + from midonetclient.neutron.client import MidonetClient + client_class = MidonetClient + self.mock_class = client_class() + super(TestMidonetSecurityGroupsTestCase, self).setUp(self._plugin_name)