From e00a398f84562f1a0da69c8c0fd33db538365dc3 Mon Sep 17 00:00:00 2001 From: Alessio Ababilov Date: Mon, 16 Jul 2012 14:07:29 +0300 Subject: Implement network creation in compute API Implements blueprint os-api-network-create The ability to create new networks is currently only exposed by the nova-manage CLI. Here we add support for network creation in the os-networks API extension. With the exception of num_networks and network_size, all the parameters supported by 'nova-manage network create' are supported. Only a single network may be created by each API call. To avoid code duplication, the nova-manage code is refactored and moved into NetworkManager so that it can be re-used by the API. DocImpact Change-Id: I682d498ab35ea43b553b64e13e677fe9eeb8e08b --- nova/api/openstack/compute/contrib/networks.py | 29 ++++++- nova/network/manager.py | 90 +++++++++++++++++++++- nova/network/quantum/nova_ipam_lib.py | 2 +- .../api/openstack/compute/contrib/test_networks.py | 62 +++++++++++++++ nova/tests/fake_flags.py | 1 + nova/tests/network/test_linux_net.py | 1 + nova/tests/test_nova_manage.py | 4 +- 7 files changed, 180 insertions(+), 9 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/compute/contrib/networks.py b/nova/api/openstack/compute/contrib/networks.py index fb1d3d77e..f673bf43a 100644 --- a/nova/api/openstack/compute/contrib/networks.py +++ b/nova/api/openstack/compute/contrib/networks.py @@ -16,7 +16,7 @@ # License for the specific language governing permissions and limitations # under the License. - +import netaddr import webob from webob import exc @@ -111,8 +111,31 @@ class NetworkController(object): raise exc.HTTPNotFound(_("Network not found")) return exc.HTTPAccepted() - def create(self, req, id, body=None): - raise exc.HTTPNotImplemented() + def create(self, req, body): + context = req.environ['nova.context'] + authorize(context) + + def bad(e): + return exc.HTTPUnprocessableEntity(explanation=e) + + if not (body and body.get("network")): + raise bad(_("Missing network in body")) + + params = body["network"] + if not params.get("label"): + raise bad(_("Network label is required")) + + cidr = params.get("cidr") or params.get("cidr_v6") + if not cidr: + raise bad(_("Network cidr or cidr_v6 is required")) + + LOG.debug(_("Creating network with label %s") % params["label"]) + + params["num_networks"] = 1 + params["network_size"] = netaddr.IPNetwork(cidr).size + + network = self.network_api.create(context, **params)[0] + return {"network": network_dict(context, network)} def add(self, req, body): context = req.environ['nova.context'] diff --git a/nova/network/manager.py b/nova/network/manager.py index c1f3a6418..862fb6f78 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -769,6 +769,8 @@ class NetworkManager(manager.SchedulerDependentManager): timeout_fixed_ips = True + required_create_args = [] + def __init__(self, network_driver=None, *args, **kwargs): if not network_driver: network_driver = FLAGS.network_driver @@ -1348,10 +1350,86 @@ class NetworkManager(manager.SchedulerDependentManager): if not fixed_ip['allocated']: self.db.fixed_ip_disassociate(context, address) - def create_networks(self, context, label, cidr, multi_host, num_networks, - network_size, cidr_v6, gateway, gateway_v6, bridge, - bridge_interface, dns1=None, dns2=None, + def create_networks(self, context, + label, cidr=None, multi_host=None, num_networks=None, + network_size=None, cidr_v6=None, + gateway=None, gateway_v6=None, bridge=None, + bridge_interface=None, dns1=None, dns2=None, fixed_cidr=None, **kwargs): + arg_names = ("label", "cidr", "multi_host", "num_networks", + "network_size", "cidr_v6", + "gateway", "gateway_v6", "bridge", + "bridge_interface", "dns1", "dns2", + "fixed_cidr") + for name in arg_names: + kwargs[name] = locals()[name] + int_args = ("network_size", "num_networks", + "vlan_start", "vpn_start") + for key in int_args: + try: + kwargs[key] = int(kwargs[key]) + except ValueError: + raise ValueError(_("%s must be an integer") % key) + except KeyError: + pass + + # check for certain required inputs + label = kwargs["label"] + if not label: + raise exception.NetworkNotCreated(req="label") + + # Size of "label" column in nova.networks is 255, hence the restriction + if len(label) > 255: + raise ValueError(_("Maximum allowed length for 'label' is 255.")) + + if not (kwargs["cidr"] or kwargs["cidr_v6"]): + raise exception.NetworkNotCreated(req="cidr or cidr_v6") + + kwargs["bridge"] = kwargs["bridge"] or FLAGS.flat_network_bridge + kwargs["bridge_interface"] = (kwargs["bridge_interface"] or + FLAGS.flat_interface) + + for fld in self.required_create_args: + if not kwargs[fld]: + raise exception.NetworkNotCreated(req=fld) + + num_networks = kwargs["num_networks"] or FLAGS.num_networks + network_size = kwargs["network_size"] + cidr = kwargs["cidr"] + if not network_size and cidr: + fixnet = netaddr.IPNetwork(cidr) + each_subnet_size = fixnet.size / num_networks + if each_subnet_size > FLAGS.network_size: + network_size = FLAGS.network_size + subnet = 32 - int(math.log(network_size, 2)) + oversize_msg = _( + 'Subnet(s) too large, defaulting to /%s.' + ' To override, specify network_size flag.') % subnet + LOG.warn(oversize_msg) + else: + network_size = fixnet.size + kwargs["num_networks"] = num_networks + kwargs["network_size"] = network_size + + kwargs["multi_host"] = (FLAGS.multi_host + if kwargs["multi_host"] is None + else + utils.bool_from_str(kwargs["multi_host"])) + kwargs["vlan_start"] = kwargs.get("vlan_start") or FLAGS.vlan_start + kwargs["vpn_start"] = kwargs.get("vpn_start") or FLAGS.vpn_start + kwargs["dns1"] = kwargs["dns1"] or FLAGS.flat_network_dns + kwargs["network_size"] = kwargs["network_size"] or FLAGS.network_size + + if kwargs["fixed_cidr"]: + kwargs["fixed_cidr"] = netaddr.IPNetwork(kwargs["fixed_cidr"]) + + return self._do_create_networks(context, **kwargs) + + def _do_create_networks(self, context, + label, cidr, multi_host, num_networks, + network_size, cidr_v6, gateway, gateway_v6, bridge, + bridge_interface, dns1=None, dns2=None, + fixed_cidr=None, **kwargs): """Create networks based on parameters.""" # NOTE(jkoelker): these are dummy values to make sure iter works # TODO(tr3buchet): disallow carving up networks @@ -1719,6 +1797,8 @@ class FlatManager(NetworkManager): timeout_fixed_ips = False + required_create_args = ['bridge'] + def _allocate_fixed_ips(self, context, instance_id, host, networks, **kwargs): """Calls allocate_fixed_ip once for each network.""" @@ -1793,6 +1873,7 @@ class FlatDHCPManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): SHOULD_CREATE_BRIDGE = True DHCP = True + required_create_args = ['bridge'] def init_host(self): """Do any initialization that needs to be run if this is a @@ -1862,6 +1943,7 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): SHOULD_CREATE_BRIDGE = True SHOULD_CREATE_VLAN = True DHCP = True + required_create_args = ['bridge_interface'] def init_host(self): """Do any initialization that needs to be run if this is a @@ -1941,6 +2023,8 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): '%(num_networks)s. Network size is %(network_size)s') % kwargs) + kwargs['bridge_interface'] = (kwargs.get('bridge_interface') or + FLAGS.vlan_interface) return NetworkManager.create_networks( self, context, vpn=True, **kwargs) diff --git a/nova/network/quantum/nova_ipam_lib.py b/nova/network/quantum/nova_ipam_lib.py index f398815e5..02157bf7c 100644 --- a/nova/network/quantum/nova_ipam_lib.py +++ b/nova/network/quantum/nova_ipam_lib.py @@ -59,7 +59,7 @@ class QuantumNovaIPAMLib(object): """ admin_context = context.elevated() subnet_size = len(netaddr.IPNetwork(cidr)) - networks = manager.FlatManager.create_networks(self.net_manager, + networks = manager.FlatManager._do_create_networks(self.net_manager, admin_context, label, cidr, False, 1, subnet_size, cidr_v6, gateway, gateway_v6, quantum_net_id, None, dns1, dns2, diff --git a/nova/tests/api/openstack/compute/contrib/test_networks.py b/nova/tests/api/openstack/compute/contrib/test_networks.py index efbb7ceba..808493f1b 100644 --- a/nova/tests/api/openstack/compute/contrib/test_networks.py +++ b/nova/tests/api/openstack/compute/contrib/test_networks.py @@ -15,6 +15,10 @@ # under the License. import copy +import itertools +import math +import netaddr +import uuid import webob @@ -23,6 +27,11 @@ from nova import exception from nova import test from nova.tests.api.openstack import fakes +from nova import flags + + +FLAGS = flags.FLAGS + FAKE_NETWORKS = [ { @@ -75,6 +84,15 @@ FAKE_USER_NETWORKS = [ }, ] +NEW_NETWORK = { + "network": { + "bridge_interface": "eth0", + "cidr": "10.20.105.0/24", + "label": "new net 111", + "vlan_start": 111, + } +} + class FakeNetworkAPI(object): @@ -117,6 +135,32 @@ class FakeNetworkAPI(object): return network raise exception.NetworkNotFound() + def create(self, context, **kwargs): + subnet_bits = int(math.ceil(math.log(kwargs.get( + 'network_size', FLAGS.network_size), 2))) + fixed_net_v4 = netaddr.IPNetwork(kwargs['cidr']) + prefixlen_v4 = 32 - subnet_bits + subnets_v4 = list(fixed_net_v4.subnet( + prefixlen_v4, + count=kwargs.get('num_networks', FLAGS.num_networks))) + new_networks = [] + new_id = max((net['id'] for net in self.networks)) + for index, subnet_v4 in enumerate(subnets_v4): + new_id += 1 + net = {'id': new_id, 'uuid': str(uuid.uuid4())} + + net['cidr'] = str(subnet_v4) + net['netmask'] = str(subnet_v4.netmask) + net['gateway'] = kwargs.get('gateway') or str(subnet_v4[1]) + net['broadcast'] = str(subnet_v4.broadcast) + net['dhcp_start'] = str(subnet_v4[2]) + + for key in FAKE_NETWORKS[0].iterkeys(): + net.setdefault(key, kwargs.get(key)) + new_networks.append(net) + self.networks += new_networks + return new_networks + class NetworksTest(test.TestCase): @@ -204,3 +248,21 @@ class NetworksTest(test.TestCase): req.environ["nova.context"].is_admin = True res_dict = self.controller.show(req, uuid) self.assertEqual(res_dict['network']['project_id'], 'fake') + + def test_network_create(self): + req = fakes.HTTPRequest.blank('/v2/1234/os-networks') + res_dict = self.controller.create(req, NEW_NETWORK) + self.assertTrue('network' in res_dict) + uuid = res_dict['network']['id'] + req = fakes.HTTPRequest.blank('/v2/1234/os-networks/%s' % uuid) + res_dict = self.controller.show(req, uuid) + self.assertTrue(res_dict['network']['label']. + startswith(NEW_NETWORK['network']['label'])) + + def test_network_create_large(self): + req = fakes.HTTPRequest.blank('/v2/1234/os-networks') + large_network = copy.deepcopy(NEW_NETWORK) + large_network['network']['cidr'] = '128.0.0.0/4' + res_dict = self.controller.create(req, large_network) + self.assertEqual(res_dict['network']['cidr'], + large_network['network']['cidr']) diff --git a/nova/tests/fake_flags.py b/nova/tests/fake_flags.py index f6d9496b1..f0fde3588 100644 --- a/nova/tests/fake_flags.py +++ b/nova/tests/fake_flags.py @@ -39,6 +39,7 @@ def set_defaults(conf): conf.set_default('iscsi_num_targets', 8) conf.set_default('network_size', 8) conf.set_default('num_networks', 2) + conf.set_default('vlan_interface', 'eth0') conf.set_default('rpc_backend', 'nova.openstack.common.rpc.impl_fake') conf.set_default('sql_connection', "sqlite://") conf.set_default('sqlite_synchronous', False) diff --git a/nova/tests/network/test_linux_net.py b/nova/tests/network/test_linux_net.py index 94b2ac8d9..47b853a8a 100644 --- a/nova/tests/network/test_linux_net.py +++ b/nova/tests/network/test_linux_net.py @@ -373,6 +373,7 @@ class LinuxNetworkTestCase(test.TestCase): "bridge_interface": "base_interface", "vlan": "fake" } + self.flags(vlan_interface="") driver.plug(network, "fakemac") self.assertEqual(info['passed_interface'], "base_interface") self.flags(vlan_interface="override_interface") diff --git a/nova/tests/test_nova_manage.py b/nova/tests/test_nova_manage.py index 805ac8e0d..6cadeb2ef 100644 --- a/nova/tests/test_nova_manage.py +++ b/nova/tests/test_nova_manage.py @@ -171,13 +171,13 @@ class NetworkCommandsTestCase(test.TestCase): fake_create_networks) self.commands.create( label='Test', - fixed_range_v4='10.2.0.0/24', + cidr='10.2.0.0/24', num_networks=1, network_size=256, multi_host='F', vlan_start=200, vpn_start=2000, - fixed_range_v6='fd00:2::/120', + cidr_v6='fd00:2::/120', gateway='10.2.0.1', gateway_v6='fd00:2::22', bridge='br200', -- cgit