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:
+15
-81
@@ -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"""
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
|
||||
@@ -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'])
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user