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
This commit is contained in:
Alessio Ababilov
2012-07-16 14:07:29 +03:00
parent 0272c063bb
commit e00a398f84
8 changed files with 195 additions and 90 deletions
+15 -81
View File
@@ -83,6 +83,7 @@ from nova import db
from nova.db import migration
from nova import exception
from nova import flags
from nova.api.openstack.compute.contrib import networks
from nova.openstack.common import cfg
from nova.openstack.common import importutils
from nova.openstack.common import log as logging
@@ -94,6 +95,8 @@ from nova import utils
from nova import version
from nova.volume import volume_types
from webob import exc
FLAGS = flags.FLAGS
flags.DECLARE('flat_network_bridge', 'nova.network.manager')
flags.DECLARE('num_networks', 'nova.network.manager')
@@ -434,7 +437,7 @@ class NetworkCommands(object):
@args('--label', dest="label", metavar='<label>',
help='Label for network (ex: public)')
@args('--fixed_range_v4', dest="fixed_range_v4", metavar='<x.x.x.x/yy>',
@args('--fixed_range_v4', dest="cidr", metavar='<x.x.x.x/yy>',
help='IPv4 subnet (ex: 10.0.0.0/8)')
@args('--num_networks', dest="num_networks", metavar='<number>',
help='Number of networks to create')
@@ -442,7 +445,7 @@ class NetworkCommands(object):
help='Number of IPs per network')
@args('--vlan', dest="vlan_start", metavar='<vlan id>', help='vlan id')
@args('--vpn', dest="vpn_start", help='vpn start')
@args('--fixed_range_v6', dest="fixed_range_v6",
@args('--fixed_range_v6', dest="cidr_v6",
help='IPv6 subnet (ex: fe80::/64')
@args('--gateway', dest="gateway", help='gateway')
@args('--gateway_v6', dest="gateway_v6", help='ipv6 gateway')
@@ -464,91 +467,22 @@ class NetworkCommands(object):
help='Project id')
@args('--priority', dest="priority", metavar="<number>",
help='Network interface priority')
def create(self, label=None, fixed_range_v4=None, num_networks=None,
def create(self, label=None, cidr=None, num_networks=None,
network_size=None, multi_host=None, vlan_start=None,
vpn_start=None, fixed_range_v6=None, gateway=None,
vpn_start=None, cidr_v6=None, gateway=None,
gateway_v6=None, bridge=None, bridge_interface=None,
dns1=None, dns2=None, project_id=None, priority=None,
uuid=None, fixed_cidr=None):
"""Creates fixed ips for host by range"""
# check for certain required inputs
if not label:
raise exception.NetworkNotCreated(req='--label')
# Size of "label" column in nova.networks is 255, hence the restriction
if len(label) > 255:
reason = _("Maximum allowed length for 'label' is 255.")
raise exception.InvalidInput(reason=reason)
if not (fixed_range_v4 or fixed_range_v6):
req = '--fixed_range_v4 or --fixed_range_v6'
raise exception.NetworkNotCreated(req=req)
bridge = bridge or FLAGS.flat_network_bridge
if not bridge:
bridge_required = ['nova.network.manager.FlatManager',
'nova.network.manager.FlatDHCPManager']
if FLAGS.network_manager in bridge_required:
raise exception.NetworkNotCreated(req='--bridge')
bridge_interface = (bridge_interface or FLAGS.flat_interface or
FLAGS.vlan_interface)
if not bridge_interface:
interface_required = ['nova.network.manager.VlanManager']
if FLAGS.network_manager in interface_required:
raise exception.NetworkNotCreated(req='--bridge_interface')
# sanitize other input using FLAGS if necessary
if not num_networks:
num_networks = FLAGS.num_networks
if not network_size and fixed_range_v4:
fixnet = netaddr.IPNetwork(fixed_range_v4)
each_subnet_size = fixnet.size / int(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
print oversize_msg
else:
network_size = fixnet.size
if not multi_host:
multi_host = FLAGS.multi_host
else:
multi_host = multi_host == 'T'
if not vlan_start:
vlan_start = FLAGS.vlan_start
if not vpn_start:
vpn_start = FLAGS.vpn_start
if not dns1 and FLAGS.flat_network_dns:
dns1 = FLAGS.flat_network_dns
if not network_size:
network_size = FLAGS.network_size
if fixed_cidr:
fixed_cidr = netaddr.IPNetwork(fixed_cidr)
# create the network
kwargs = dict(((k, v) for k, v in locals().iteritems()
if v and k != "self"))
if multi_host is not None:
kwargs['multi_host'] = multi_host == 'T'
net_manager = importutils.import_object(FLAGS.network_manager)
net_manager.create_networks(context.get_admin_context(),
label=label,
cidr=fixed_range_v4,
multi_host=multi_host,
num_networks=int(num_networks),
network_size=int(network_size),
vlan_start=int(vlan_start),
vpn_start=int(vpn_start),
cidr_v6=fixed_range_v6,
gateway=gateway,
gateway_v6=gateway_v6,
bridge=bridge,
bridge_interface=bridge_interface,
dns1=dns1,
dns2=dns2,
project_id=project_id,
priority=priority,
uuid=uuid,
fixed_cidr=fixed_cidr)
try:
net_manager.create_networks(context.get_admin_context(), **kwargs)
except exc.HTTPBadRequest as ex:
print ex
def list(self):
"""List all created networks"""
+26 -3
View File
@@ -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']
+87 -3
View File
@@ -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)
+1 -1
View File
@@ -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,
@@ -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'])
+1
View File
@@ -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)
+1
View File
@@ -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")
+2 -2
View File
@@ -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',