From 0e7aa4588e566d727a80718f381e73ce8f7a0ff0 Mon Sep 17 00:00:00 2001 From: Matt Dietz Date: Tue, 8 Oct 2013 20:01:34 +0000 Subject: [PATCH] Allows easier configuration of default networks Updates the Quark networks extension to allow the id and ipam_strategy to be passed along with a network create. The id is specifically gated as admin only, but as an admin, allows deployers to configure special networks like the all 0s and 1s placeholder values we use at Rackspace. --- .../{networks.py => networks_quark.py} | 11 ++-- quark/exceptions.py | 8 +++ quark/ipam.py | 7 ++- quark/plugin.py | 3 +- quark/plugin_modules/networks.py | 19 +++++-- quark/tests/plugin_modules/test_networks.py | 51 ++++++++++++++++++- 6 files changed, 87 insertions(+), 12 deletions(-) rename quark/api/extensions/{networks.py => networks_quark.py} (82%) diff --git a/quark/api/extensions/networks.py b/quark/api/extensions/networks_quark.py similarity index 82% rename from quark/api/extensions/networks.py rename to quark/api/extensions/networks_quark.py index 951a352..4c351ec 100644 --- a/quark/api/extensions/networks.py +++ b/quark/api/extensions/networks_quark.py @@ -18,11 +18,12 @@ RESOURCE_NAME = "network" RESOURCE_COLLECTION = RESOURCE_NAME + "s" EXTENDED_ATTRIBUTES_2_0 = { RESOURCE_COLLECTION: { - "ipam_strategy": {"allow_post": False, "is_visible": True, - "default": False}}} + "ipam_strategy": {"allow_post": True, "is_visible": True, + "default": False}, + "id": {"allow_post": True, "is_visible": True, "default": False}}} -class Networks(object): +class Networks_quark(object): """Extends Networks for quark API purposes.""" @classmethod @@ -31,7 +32,7 @@ class Networks(object): @classmethod def get_alias(cls): - return "networks" + return "networks_quark" @classmethod def get_description(cls): @@ -40,7 +41,7 @@ class Networks(object): @classmethod def get_namespace(cls): return ("http://docs.openstack.org/network/ext/" - "networks/api/v2.0") + "networks_quark/api/v2.0") @classmethod def get_updated(cls): diff --git a/quark/exceptions.py b/quark/exceptions.py index 8dca374..9c6abd0 100644 --- a/quark/exceptions.py +++ b/quark/exceptions.py @@ -1,6 +1,10 @@ from neutron.common import exceptions +class NetworkAlreadyExists(exceptions.Conflict): + message = _("Network %(id)s already exists.") + + class InvalidMacAddressRange(exceptions.NeutronException): message = _("Invalid MAC address range %(cidr)s.") @@ -49,6 +53,10 @@ class PhysicalNetworkNotFound(exceptions.NeutronException): message = _("Physical network %(phys_net)s not found!") +class InvalidIpamStrategy(exceptions.BadRequest): + message = _("IPAM Strategy %(strat)s is invalid.") + + class ProvidernetParamError(exceptions.NeutronException): message = _("%(msg)s") diff --git a/quark/ipam.py b/quark/ipam.py index 6d161a2..6c6c7b0 100644 --- a/quark/ipam.py +++ b/quark/ipam.py @@ -306,8 +306,13 @@ class IpamRegistry(object): QuarkIpamBOTH.get_name(): QuarkIpamBOTH(), QuarkIpamBOTHREQ.get_name(): QuarkIpamBOTHREQ()} - def get_strategy(self, strategy_name): + def is_valid_strategy(self, strategy_name): if strategy_name in self.strategies: + return True + return False + + def get_strategy(self, strategy_name): + if self.is_valid_strategy(strategy_name): return self.strategies[strategy_name] fallback = CONF.QUARK.default_ipam_strategy LOG.warn("IPAM strategy %s not found, " diff --git a/quark/plugin.py b/quark/plugin.py index 5207258..df0e0fa 100644 --- a/quark/plugin.py +++ b/quark/plugin.py @@ -73,7 +73,8 @@ class Plugin(neutron_plugin_base_v2.NeutronPluginBaseV2, "ip_addresses", "ports_quark", "security-group", "diagnostics", "subnets_quark", "provider", - "ip_policies", "quotas"] + "ip_policies", "quotas", + "networks_quark"] def __init__(self): neutron_db_api.configure_db() diff --git a/quark/plugin_modules/networks.py b/quark/plugin_modules/networks.py index 7878be4..e855f62 100644 --- a/quark/plugin_modules/networks.py +++ b/quark/plugin_modules/networks.py @@ -24,6 +24,8 @@ from oslo.config import cfg from quark.db import api as db_api from quark.drivers import registry +from quark import exceptions as q_exc +from quark import ipam from quark import network_strategy from quark.plugin_modules import ports from quark.plugin_modules import subnets @@ -62,13 +64,24 @@ def create_network(context, network): with context.session.begin(): # Generate a uuid that we're going to hand to the backend and db - net_uuid = uuidutils.generate_uuid() + net_attrs = network["network"] + net_uuid = utils.pop_param(net_attrs, "id", None) + if net_uuid and context.is_admin: + net = db_api.network_find(context, id=net_uuid, scope=db_api.ONE) + if net: + raise q_exc.NetworkAlreadyExists(id=net_uuid) + else: + net_uuid = uuidutils.generate_uuid() #TODO(mdietz) this will be the first component registry hook, but # lets make it work first pnet_type, phys_net, seg_id = _adapt_provider_nets(context, network) - net_attrs = network["network"] - net_attrs["ipam_strategy"] = CONF.QUARK.default_ipam_strategy + if "ipam_strategy" not in net_attrs: + net_attrs["ipam_strategy"] = CONF.QUARK.default_ipam_strategy + + strat = net_attrs["ipam_strategy"] + if not ipam.IPAM_REGISTRY.is_valid_strategy(strat): + raise q_exc.InvalidIpamStrategy(strat=strat) # NOTE(mdietz) I think ideally we would create the providernet # elsewhere as a separate driver step that could be diff --git a/quark/tests/plugin_modules/test_networks.py b/quark/tests/plugin_modules/test_networks.py index a088040..0cd298a 100644 --- a/quark/tests/plugin_modules/test_networks.py +++ b/quark/tests/plugin_modules/test_networks.py @@ -17,8 +17,10 @@ import contextlib import mock from neutron.common import exceptions +from neutron import context from quark.db import models +from quark import exceptions as q_exc from quark.tests import test_quark_plugin @@ -179,7 +181,7 @@ class TestQuarkDeleteNetwork(test_quark_plugin.TestQuarkPlugin): class TestQuarkCreateNetwork(test_quark_plugin.TestQuarkPlugin): @contextlib.contextmanager - def _stubs(self, net=None, subnet=None, ports=None): + def _stubs(self, net=None, subnet=None, ports=None, find_net=False): net_mod = net subnet_mod = None if net: @@ -190,14 +192,20 @@ class TestQuarkCreateNetwork(test_quark_plugin.TestQuarkPlugin): subnet_mod = models.Subnet() subnet_mod.update(subnet) + found_net = None + if find_net: + found_net = models.Network() + db_mod = "quark.db.api" with contextlib.nested( mock.patch("%s.network_create" % db_mod), mock.patch("%s.subnet_create" % db_mod), mock.patch("quark.drivers.base.BaseDriver.create_network"), - ) as (net_create, sub_create, driver_net_create): + mock.patch("%s.network_find" % db_mod) + ) as (net_create, sub_create, driver_net_create, net_find): net_create.return_value = net_mod sub_create.return_value = subnet_mod + net_find.return_value = found_net yield net_create def test_create_network(self): @@ -234,6 +242,45 @@ class TestQuarkCreateNetwork(test_quark_plugin.TestQuarkPlugin): self.assertEqual(net["tenant_id"], 0) self.assertEqual(net["ipam_strategy"], None) + def test_create_network_with_id(self): + net = dict(id="abcdef", name="public", admin_state_up=True, + tenant_id=0) + ctxt = context.Context('fake', 'fake', is_admin=True, + load_admin_roles=False) + with self._stubs(net=net): + res = self.plugin.create_network(ctxt, dict(network=net)) + self.assertEqual(net["id"], res["id"]) + + def test_create_network_with_id_already_exists_raises(self): + net = dict(id="abcdef", name="public", admin_state_up=True, + tenant_id=0) + ctxt = context.Context('fake', 'fake', is_admin=True, + load_admin_roles=False) + with self._stubs(net=net, find_net=True): + with self.assertRaises(q_exc.NetworkAlreadyExists): + self.plugin.create_network(ctxt, dict(network=net)) + + def test_create_network_with_id_not_admin_ignores_id(self): + net = dict(id="abcdef", name="public", admin_state_up=True, + tenant_id=0) + with self._stubs(net=net): + res = self.plugin.create_network(self.context, dict(network=net)) + self.assertNotEqual(net["id"], res["id"]) + + def test_create_network_with_ipam_strategy(self): + net = dict(id="abcdef", name="public", admin_state_up=True, + tenant_id=0, ipam_strategy="BOTH") + with self._stubs(net=net): + res = self.plugin.create_network(self.context, dict(network=net)) + self.assertEqual(res["ipam_strategy"], net["ipam_strategy"]) + + def test_create_network_with_bad_ipam_strategy_raises(self): + net = dict(id="abcdef", name="public", admin_state_up=True, + tenant_id=0, ipam_strategy="BUSTED") + with self._stubs(net=net): + with self.assertRaises(q_exc.InvalidIpamStrategy): + self.plugin.create_network(self.context, dict(network=net)) + class TestQuarkDiagnoseNetworks(test_quark_plugin.TestQuarkPlugin): @contextlib.contextmanager