Support create subnet w/Neutron subnet allocation

The user is enabled to choose between entering a network address
manually or to allocate a network address from a pool. With the
advent of address scopes in Neutron this feature will be used for
enabling routing of globally unique addresses (GUA) all the way in
to the VM. This is a prerequisite for IPv6 to be routable, and will
also at some point allow GUA-IPv4 without NAT.

Workflow:
If subnet allocation is not supported in backend, there are no
subnet pools available, or the user chooses to enter network address
manually the form will behave like before.

If subnet allocation is supported in backend and there either are
subnet pools available or the label for the default subnet pool(s) is
configured, the user may choose to allocate a network address from a
pool. The user will be presented with two new drop-down menus. The
first drop-down menu will, in addition to the default pool (if
configured), present the names and prefixes of available pools. The
next drop-down menu will display available choices for prefix lengths
(it will not be displayed if the "default pool" is selected). The
available choices are defined by the subnet pool. Default prefix length
defined by the pool will be selected. The correct IP version is used
based on what IP version is defined by the selected subnet pool.

The "Create Subnet" checkbox has been moved from the Subnet step to
the first step of the workflow. This is because of how switchable
fields work, and how you can not safely define more than one slug in a
fields "switch-on" attribute. For the sake of User Experience, we need
to reliably switch fields based on selected Address source on the
Subnet tab. IMHO it also makes more sense to have this checkbox on the
first step of the workflow, if you just want to create a network you can
now uncheck the "Create Subnet" checkbox and click directly on "Create".

Change-Id: Ie392ccd9feae4dc02c3f30e2475457e755700b6b
Implements: blueprint neutron-subnet-allocation
This commit is contained in:
Frode Nordahl 2015-07-20 12:18:40 +02:00
parent d2f1592d0d
commit ef20a53f93
8 changed files with 658 additions and 105 deletions

View File

@ -750,6 +750,8 @@ Default::
'supported_vnic_types': ["*"],
'segmentation_id_range': {},
'enable_fip_topology_check': True,
'default_ipv4_subnet_pool_label': None,
'default_ipv6_subnet_pool_label': None,
}
A dictionary of settings which can be used to enable optional services provided
@ -918,6 +920,28 @@ subnet with no router if your Neutron backend allows it.
.. versionadded:: 2015.2(Liberty)
``default_ipv4_subnet_pool_label``:
.. versionadded:: 2015.2(Liberty)
Default: ``None`` (Disabled)
Neutron can be configured with a default Subnet Pool to be used for IPv4
subnet-allocation. Specify the label you wish to display in the Address pool
selector on the create subnet step if you want to use this feature.
``default_ipv6_subnet_pool_label``:
.. versionadded:: 2015.2(Liberty)
Default: ``None`` (Disabled)
Neutron can be configured with a default Subnet Pool to be used for IPv6
subnet-allocation. Specify the label you wish to display in the Address pool
selector on the create subnet step if you want to use this feature.
You must set this to enable IPv6 Prefix Delegation in a PD-capable environment.
``OPENSTACK_SSL_CACERT``
------------------------

View File

@ -56,6 +56,68 @@ horizon.forms = {
});
},
handle_subnet_address_source: function() {
$("div.table_wrapper, #modal_wrapper").on("change", "select#id_address_source", function(evt) {
var $option = $(this).find("option:selected");
var $form = $(this).closest("form");
var $ipVersion = $form.find("select#id_ip_version");
if ($option.val() == "subnetpool") {
$ipVersion.attr("disabled", "disabled");
// disabled fields do not post, store the value in a hidden input
var el = document.createElement("input");
el.type='hidden';
el.id = "id_hidden_ip_version";
el.name = $ipVersion.attr('name');
el.value = $ipVersion.attr('value');
$form.append(el);
} else {
var $hiddenIpVersion = $form.find("hidden#id_hidden_ip_version");
$hiddenIpVersion.remove();
$ipVersion.removeAttr("disabled");
}
});
},
handle_subnet_subnetpool: function() {
$("div.table_wrapper, #modal_wrapper").on("change", "select#id_subnetpool", function(evt) {
var $option = $(this).find("option:selected");
var $form = $(this).closest("form");
var $ipVersion = $form.find("select#id_ip_version");
var $prefixLength = $form.find("select#id_prefixlen");
var $ipv6Modes = $form.find("select#id_ipv6_modes");
var subnetpoolIpVersion = parseInt($option.data("ip_version"), 10) || 4;
var minPrefixLen = parseInt($option.data("min_prefixlen"), 10) || 1;
var maxPrefixLen = parseInt($option.data("max_prefixlen"), 10);
var defaultPrefixLen = parseInt($option.data("default_prefixlen"), 10) ||
-1;
var optionsAsString = "";
$ipVersion.val(subnetpoolIpVersion);
if (!maxPrefixLen) {
if (subnetpoolIpVersion == 4) {
maxPrefixLen = 32;
} else {
maxPrefixLen = 128;
}
}
for (i = minPrefixLen; i <= maxPrefixLen; i++) {
optionsAsString += "<option value='" + i + "'>" + i;
if (i == defaultPrefixLen) {
optionsAsString += " (" + gettext("pool default") + ")";
}
optionsAsString += "</option>";
}
$prefixLength.empty().append(optionsAsString);
if (defaultPrefixLen >= 0) {
$prefixLength.val(defaultPrefixLen);
} else {
$prefixLength.val("");
}
});
},
/**
* In the container's upload object form, copy the selected file name in the
* object name field if the field is empty. The filename string is stored in
@ -196,6 +258,8 @@ horizon.addInitFunction(horizon.forms.init = function () {
horizon.forms.handle_image_source();
horizon.forms.handle_object_upload_source();
horizon.forms.datepicker();
horizon.forms.handle_subnet_address_source();
horizon.forms.handle_subnet_subnetpool();
if (!horizon.conf.disable_password_reveal) {
horizon.forms.add_password_fields_reveal_buttons($("body"));
@ -260,24 +324,25 @@ horizon.addInitFunction(horizon.forms.init = function () {
visible = $switchable.is(':visible'),
slug = $switchable.data('slug'),
checked = $switchable.prop('checked'),
hide_tab = $switchable.data('hide-tab'),
hide_tab = String($switchable.data('hide-tab')).split(','),
hide_on = $switchable.data('hideOnChecked');
// If checkbox is hidden then do not apply any further logic
if (!visible) return;
// If the checkbox has hide-tab attribute then hide/show the tab
if (hide_tab) {
var i, len;
for (i = 0, len = hide_tab.length; i < len; i++) {
var $btnfinal = $('.button-final');
if(checked == hide_on) {
// If the checkbox is not checked then hide the tab
$('*[data-target="#'+ hide_tab +'"]').parent().hide();
$('*[data-target="#'+ hide_tab[i] +'"]').parent().hide();
$('.button-next').hide();
$btnfinal.show();
$btnfinal.data('show-on-tab', $fieldset.prop('id'));
} else if (!$('*[data-target="#'+ hide_tab +'"]').parent().is(':visible')) {
} else if (!$('*[data-target="#'+ hide_tab[i] +'"]').parent().is(':visible')) {
// If the checkbox is checked and the tab is currently hidden then show the tab again
$('*[data-target="#'+ hide_tab +'"]').parent().show();
$('*[data-target="#'+ hide_tab[i] +'"]').parent().show();
$btnfinal.hide();
$('.button-next').show();
$btnfinal.removeData('show-on-tab');

View File

@ -118,6 +118,14 @@ class DetailView(tabs.TabView):
subnet.ipv6_ra_mode, subnet.ipv6_address_mode)
subnet.ipv6_modes_desc = utils.IPV6_MODE_MAP.get(ipv6_modes)
if ('subnetpool_id' in subnet and
subnet.subnetpool_id and
api.neutron.is_extension_supported(self.request,
'subnet_allocation')):
subnetpool = api.neutron.subnetpool_get(self.request,
subnet.subnetpool_id)
subnet.subnetpool_name = subnetpool.name
return subnet
def get_context_data(self, **kwargs):

View File

@ -36,7 +36,6 @@ class CreateSubnetInfoAction(network_workflows.CreateSubnetInfoAction):
def __init__(self, request, *args, **kwargs):
super(CreateSubnetInfoAction, self).__init__(request, *args, **kwargs)
self.fields['cidr'].required = True
class Meta(object):
name = _("Subnet")
@ -82,6 +81,12 @@ class CreateSubnet(network_workflows.CreateNetwork):
class UpdateSubnetInfoAction(CreateSubnetInfoAction):
address_source = forms.ChoiceField(widget=forms.HiddenInput(),
required=False)
subnetpool = forms.ChoiceField(widget=forms.HiddenInput(),
required=False)
prefixlen = forms.ChoiceField(widget=forms.HiddenInput(),
required=False)
cidr = forms.IPField(label=_("Network Address"),
required=False,
initial="",

View File

@ -14,6 +14,12 @@
{% url 'horizon:project:networks:detail' subnet.network_id as network_url %}
<dt>{% trans "Network ID" %}</dt>
<dd><a href="{{ network_url }}">{{ subnet.network_id|default:_("None") }}</a></dd>
<dt>{% trans "Subnetpool" %}</dt>
{% if subnet.subnetpool_id %}
<dd>{{ subnet.subnetpool_name }} ({{ subnet.subnetpool_id }})</dd>
{% else %}
<dd>{% trans "None" %}</dd>
{% endif %}
<dt>{% trans "IP version" %}</dt>
<dd>{{ subnet.ipver_str|default:_("-") }}</dd>
<dt>{% trans "CIDR" %}</dt>

View File

@ -461,6 +461,7 @@ class NetworkTests(test.TestCase):
def test_network_create_post_with_subnet_network_exception(
self,
test_with_profile=False,
test_with_subnetpool=False,
):
network = self.networks.first()
subnet = self.subnets.first()
@ -497,7 +498,9 @@ class NetworkTests(test.TestCase):
@test.create_stubs({api.neutron: ('network_create',
'network_delete',
'subnet_create',
'profile_list')})
'profile_list',
'is_extension_supported',
'subnetpool_list',)})
def test_network_create_post_with_subnet_subnet_exception(
self,
test_with_profile=False,
@ -512,6 +515,11 @@ class NetworkTests(test.TestCase):
api.neutron.profile_list(IsA(http.HttpRequest),
'network').AndReturn(net_profiles)
params['net_profile_id'] = net_profile_id
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'subnet_allocation').\
AndReturn(True)
api.neutron.subnetpool_list(IsA(http.HttpRequest)).\
AndReturn(self.subnetpools.list())
api.neutron.network_create(IsA(http.HttpRequest),
**params).AndReturn(network)
api.neutron.subnet_create(IsA(http.HttpRequest),
@ -544,9 +552,12 @@ class NetworkTests(test.TestCase):
self.test_network_create_post_with_subnet_subnet_exception(
test_with_profile=True)
@test.create_stubs({api.neutron: ('profile_list',)})
@test.create_stubs({api.neutron: ('profile_list',
'is_extension_supported',
'subnetpool_list',)})
def test_network_create_post_with_subnet_nocidr(self,
test_with_profile=False):
test_with_profile=False,
test_with_snpool=False):
network = self.networks.first()
subnet = self.subnets.first()
if test_with_profile:
@ -554,6 +565,11 @@ class NetworkTests(test.TestCase):
net_profile_id = self.net_profiles.first().id
api.neutron.profile_list(IsA(http.HttpRequest),
'network').AndReturn(net_profiles)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'subnet_allocation').\
AndReturn(True)
api.neutron.subnetpool_list(IsA(http.HttpRequest)).\
AndReturn(self.subnetpools.list())
self.mox.ReplayAll()
form_data = {'net_name': network.name,
@ -561,12 +577,16 @@ class NetworkTests(test.TestCase):
'with_subnet': True}
if test_with_profile:
form_data['net_profile_id'] = net_profile_id
if test_with_snpool:
form_data['subnetpool_id'] = ''
form_data['prefixlen'] = ''
form_data.update(form_data_subnet(subnet, cidr='',
allocation_pools=[]))
url = reverse('horizon:project:networks:create')
res = self.client.post(url, form_data)
self.assertContains(res, escape('Specify "Network Address" or '
self.assertContains(res, escape('Specify "Network Address", '
'"Address pool" or '
'clear "Create Subnet" checkbox.'))
@test.update_settings(
@ -575,10 +595,17 @@ class NetworkTests(test.TestCase):
self.test_network_create_post_with_subnet_nocidr(
test_with_profile=True)
@test.create_stubs({api.neutron: ('profile_list',)})
def test_network_create_post_with_subnet_nocidr_nosubnetpool(self):
self.test_network_create_post_with_subnet_nocidr(
test_with_snpool=True)
@test.create_stubs({api.neutron: ('profile_list',
'is_extension_supported',
'subnetpool_list',)})
def test_network_create_post_with_subnet_cidr_without_mask(
self,
test_with_profile=False,
test_with_subnetpool=False,
):
network = self.networks.first()
subnet = self.subnets.first()
@ -587,13 +614,22 @@ class NetworkTests(test.TestCase):
net_profile_id = self.net_profiles.first().id
api.neutron.profile_list(IsA(http.HttpRequest),
'network').AndReturn(net_profiles)
self.mox.ReplayAll()
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'subnet_allocation').\
AndReturn(True)
api.neutron.subnetpool_list(IsA(http.HttpRequest)).\
AndReturn(self.subnetpools.list())
self.mox.ReplayAll()
form_data = {'net_name': network.name,
'admin_state': network.admin_state_up,
'with_subnet': True}
if test_with_profile:
form_data['net_profile_id'] = net_profile_id
if test_with_subnetpool:
subnetpool = self.subnetpools.first()
form_data['subnetpool'] = subnetpool.id
form_data['prefixlen'] = subnetpool.default_prefixlen
form_data.update(form_data_subnet(subnet, cidr='10.0.0.0',
allocation_pools=[]))
url = reverse('horizon:project:networks:create')
@ -608,10 +644,17 @@ class NetworkTests(test.TestCase):
self.test_network_create_post_with_subnet_cidr_without_mask(
test_with_profile=True)
@test.create_stubs({api.neutron: ('profile_list',)})
def test_network_create_post_with_subnet_cidr_without_mask_w_snpool(self):
self.test_network_create_post_with_subnet_cidr_without_mask(
test_with_subnetpool=True)
@test.create_stubs({api.neutron: ('profile_list',
'is_extension_supported',
'subnetpool_list',)})
def test_network_create_post_with_subnet_cidr_inconsistent(
self,
test_with_profile=False,
test_with_subnetpool=False
):
network = self.networks.first()
subnet = self.subnets.first()
@ -620,6 +663,12 @@ class NetworkTests(test.TestCase):
net_profile_id = self.net_profiles.first().id
api.neutron.profile_list(IsA(http.HttpRequest),
'network').AndReturn(net_profiles)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'subnet_allocation').\
AndReturn(True)
api.neutron.subnetpool_list(IsA(http.HttpRequest)).\
AndReturn(self.subnetpools.list())
self.mox.ReplayAll()
# dummy IPv6 address
@ -629,6 +678,10 @@ class NetworkTests(test.TestCase):
'with_subnet': True}
if test_with_profile:
form_data['net_profile_id'] = net_profile_id
if test_with_subnetpool:
subnetpool = self.subnetpools.first()
form_data['subnetpool'] = subnetpool.id
form_data['prefixlen'] = subnetpool.default_prefixlen
form_data.update(form_data_subnet(subnet, cidr=cidr,
allocation_pools=[]))
url = reverse('horizon:project:networks:create')
@ -643,10 +696,17 @@ class NetworkTests(test.TestCase):
self.test_network_create_post_with_subnet_cidr_inconsistent(
test_with_profile=True)
@test.create_stubs({api.neutron: ('profile_list',)})
def test_network_create_post_with_subnet_cidr_inconsistent_w_snpool(self):
self.test_network_create_post_with_subnet_cidr_inconsistent(
test_with_subnetpool=True)
@test.create_stubs({api.neutron: ('profile_list',
'is_extension_supported',
'subnetpool_list',)})
def test_network_create_post_with_subnet_gw_inconsistent(
self,
test_with_profile=False,
test_with_subnetpool=False,
):
network = self.networks.first()
subnet = self.subnets.first()
@ -655,6 +715,12 @@ class NetworkTests(test.TestCase):
net_profile_id = self.net_profiles.first().id
api.neutron.profile_list(IsA(http.HttpRequest),
'network').AndReturn(net_profiles)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'subnet_allocation').\
AndReturn(True)
api.neutron.subnetpool_list(IsA(http.HttpRequest)).\
AndReturn(self.subnetpools.list())
self.mox.ReplayAll()
# dummy IPv6 address
@ -664,6 +730,10 @@ class NetworkTests(test.TestCase):
'with_subnet': True}
if test_with_profile:
form_data['net_profile_id'] = net_profile_id
if test_with_subnetpool:
subnetpool = self.subnetpools.first()
form_data['subnetpool'] = subnetpool.id
form_data['prefixlen'] = subnetpool.default_prefixlen
form_data.update(form_data_subnet(subnet, gateway_ip=gateway_ip,
allocation_pools=[]))
url = reverse('horizon:project:networks:create')
@ -677,6 +747,10 @@ class NetworkTests(test.TestCase):
self.test_network_create_post_with_subnet_gw_inconsistent(
test_with_profile=True)
def test_network_create_post_with_subnet_gw_inconsistent_w_snpool(self):
self.test_network_create_post_with_subnet_gw_inconsistent(
test_with_subnetpool=True)
@test.create_stubs({api.neutron: ('network_get',)})
def test_network_update_get(self):
network = self.networks.first()
@ -832,7 +906,8 @@ class NetworkTests(test.TestCase):
class NetworkSubnetTests(test.TestCase):
@test.create_stubs({api.neutron: ('network_get', 'subnet_get',)})
@test.create_stubs({api.neutron: ('network_get',
'subnet_get',)})
def test_subnet_detail(self):
network = self.networks.first()
subnet = self.subnets.first()
@ -881,7 +956,7 @@ class NetworkSubnetTests(test.TestCase):
@test.create_stubs({api.neutron: ('network_get',
'subnet_create',)})
def test_subnet_create_post(self):
def test_subnet_create_post(self, test_with_subnetpool=False):
network = self.networks.first()
subnet = self.subnets.first()
api.neutron.network_get(IsA(http.HttpRequest),
@ -970,7 +1045,8 @@ class NetworkSubnetTests(test.TestCase):
@test.create_stubs({api.neutron: ('network_get',
'subnet_create',)})
def test_subnet_create_post_network_exception(self):
def test_subnet_create_post_network_exception(self,
test_with_subnetpool=False):
network = self.networks.first()
subnet = self.subnets.first()
api.neutron.network_get(IsA(http.HttpRequest),
@ -978,8 +1054,12 @@ class NetworkSubnetTests(test.TestCase):
.AndRaise(self.exceptions.neutron)
self.mox.ReplayAll()
form_data = form_data_subnet(subnet,
allocation_pools=[])
form_data = {}
if test_with_subnetpool:
subnetpool = self.subnetpools.first()
form_data['subnetpool'] = subnetpool.id
form_data.update(form_data_subnet(subnet, allocation_pools=[]))
url = reverse('horizon:project:networks:addsubnet',
args=[subnet.network_id])
res = self.client.post(url, form_data)
@ -987,9 +1067,14 @@ class NetworkSubnetTests(test.TestCase):
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, INDEX_URL)
def test_subnet_create_post_network_exception_with_subnetpool(self):
self.test_subnet_create_post_network_exception(
test_with_subnetpool=True)
@test.create_stubs({api.neutron: ('network_get',
'subnet_create',)})
def test_subnet_create_post_subnet_exception(self):
def test_subnet_create_post_subnet_exception(self,
test_with_subnetpool=False):
network = self.networks.first()
subnet = self.subnets.first()
api.neutron.network_get(IsA(http.HttpRequest),
@ -1005,8 +1090,7 @@ class NetworkSubnetTests(test.TestCase):
.AndRaise(self.exceptions.neutron)
self.mox.ReplayAll()
form_data = form_data_subnet(subnet,
allocation_pools=[])
form_data = form_data_subnet(subnet, allocation_pools=[])
url = reverse('horizon:project:networks:addsubnet',
args=[subnet.network_id])
res = self.client.post(url, form_data)
@ -1015,19 +1099,34 @@ class NetworkSubnetTests(test.TestCase):
args=[subnet.network_id])
self.assertRedirectsNoFollow(res, redir_url)
@test.create_stubs({api.neutron: ('network_get',)})
def test_subnet_create_post_cidr_inconsistent(self):
@test.create_stubs({api.neutron: ('network_get',
'is_extension_supported',
'subnetpool_list',)})
def test_subnet_create_post_cidr_inconsistent(self,
test_with_subnetpool=False):
network = self.networks.first()
subnet = self.subnets.first()
api.neutron.network_get(IsA(http.HttpRequest),
network.id)\
.AndReturn(self.networks.first())
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'subnet_allocation').\
AndReturn(True)
api.neutron.subnetpool_list(IsA(http.HttpRequest)).\
AndReturn(self.subnetpools.list())
self.mox.ReplayAll()
form_data = {}
if test_with_subnetpool:
subnetpool = self.subnetpools.first()
form_data['subnetpool'] = subnetpool.id
# dummy IPv6 address
cidr = '2001:0DB8:0:CD30:123:4567:89AB:CDEF/60'
form_data = form_data_subnet(subnet, cidr=cidr,
allocation_pools=[])
form_data.update(form_data_subnet(subnet, cidr=cidr,
allocation_pools=[]))
url = reverse('horizon:project:networks:addsubnet',
args=[subnet.network_id])
res = self.client.post(url, form_data)
@ -1036,37 +1135,74 @@ class NetworkSubnetTests(test.TestCase):
self.assertFormErrors(res, 1, expected_msg)
self.assertTemplateUsed(res, views.WorkflowView.template_name)
@test.create_stubs({api.neutron: ('network_get',)})
def test_subnet_create_post_gw_inconsistent(self):
def test_subnet_create_post_cidr_inconsistent_with_subnetpool(self):
self.test_subnet_create_post_cidr_inconsistent(
test_with_subnetpool=True)
@test.create_stubs({api.neutron: ('network_get',
'is_extension_supported',
'subnetpool_list',)})
def test_subnet_create_post_gw_inconsistent(self,
test_with_subnetpool=False):
network = self.networks.first()
subnet = self.subnets.first()
api.neutron.network_get(IsA(http.HttpRequest),
network.id)\
.AndReturn(self.networks.first())
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'subnet_allocation').\
AndReturn(True)
api.neutron.subnetpool_list(IsA(http.HttpRequest)).\
AndReturn(self.subnetpools.list())
self.mox.ReplayAll()
form_data = {}
if test_with_subnetpool:
subnetpool = self.subnetpools.first()
form_data['subnetpool'] = subnetpool.id
# dummy IPv6 address
gateway_ip = '2001:0DB8:0:CD30:123:4567:89AB:CDEF'
form_data = form_data_subnet(subnet, gateway_ip=gateway_ip,
allocation_pools=[])
form_data.update(form_data_subnet(subnet, gateway_ip=gateway_ip,
allocation_pools=[]))
url = reverse('horizon:project:networks:addsubnet',
args=[subnet.network_id])
res = self.client.post(url, form_data)
self.assertContains(res, 'Gateway IP and IP version are inconsistent.')
@test.create_stubs({api.neutron: ('network_get',)})
def test_subnet_create_post_invalid_pools_start_only(self):
def test_subnet_create_post_gw_inconsistent_with_subnetpool(self):
self.test_subnet_create_post_gw_inconsistent(test_with_subnetpool=True)
@test.create_stubs({api.neutron: ('network_get',
'is_extension_supported',
'subnetpool_list',)})
def test_subnet_create_post_invalid_pools_start_only(self,
test_w_snpool=False):
network = self.networks.first()
subnet = self.subnets.first()
api.neutron.network_get(IsA(http.HttpRequest),
network.id).AndReturn(network)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'subnet_allocation').\
AndReturn(True)
api.neutron.subnetpool_list(IsA(http.HttpRequest)).\
AndReturn(self.subnetpools.list())
self.mox.ReplayAll()
form_data = {}
if test_w_snpool:
subnetpool = self.subnetpools.first()
form_data['subnetpool'] = subnetpool.id
# Start only allocation_pools
allocation_pools = '10.0.0.2'
form_data = form_data_subnet(subnet,
allocation_pools=allocation_pools)
form_data.update(form_data_subnet(subnet,
allocation_pools=allocation_pools))
url = reverse('horizon:project:networks:addsubnet',
args=[subnet.network_id])
res = self.client.post(url, form_data)
@ -1075,18 +1211,37 @@ class NetworkSubnetTests(test.TestCase):
'Start and end addresses must be specified '
'(value=%s)' % allocation_pools)
@test.create_stubs({api.neutron: ('network_get',)})
def test_subnet_create_post_invalid_pools_three_entries(self):
def test_subnet_create_post_invalid_pools_start_only_with_subnetpool(self):
self.test_subnet_create_post_invalid_pools_start_only(
test_w_snpool=True)
@test.create_stubs({api.neutron: ('network_get',
'is_extension_supported',
'subnetpool_list',)})
def test_subnet_create_post_invalid_pools_three_entries(self,
t_w_snpool=False):
network = self.networks.first()
subnet = self.subnets.first()
api.neutron.network_get(IsA(http.HttpRequest),
network.id).AndReturn(network)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'subnet_allocation').\
AndReturn(True)
api.neutron.subnetpool_list(IsA(http.HttpRequest)).\
AndReturn(self.subnetpools.list())
self.mox.ReplayAll()
form_data = {}
if t_w_snpool:
subnetpool = self.subnetpools.first()
form_data['subnetpool'] = subnetpool.id
# pool with three entries
allocation_pools = '10.0.0.2,10.0.0.3,10.0.0.4'
form_data = form_data_subnet(subnet,
allocation_pools=allocation_pools)
form_data.update(form_data_subnet(subnet,
allocation_pools=allocation_pools))
url = reverse('horizon:project:networks:addsubnet',
args=[subnet.network_id])
res = self.client.post(url, form_data)
@ -1095,18 +1250,37 @@ class NetworkSubnetTests(test.TestCase):
'Start and end addresses must be specified '
'(value=%s)' % allocation_pools)
@test.create_stubs({api.neutron: ('network_get',)})
def test_subnet_create_post_invalid_pools_invalid_address(self):
def test_subnet_create_post_invalid_pools_three_entries_w_subnetpool(self):
self.test_subnet_create_post_invalid_pools_three_entries(
t_w_snpool=True)
@test.create_stubs({api.neutron: ('network_get',
'is_extension_supported',
'subnetpool_list',)})
def test_subnet_create_post_invalid_pools_invalid_address(self,
t_w_snpl=False):
network = self.networks.first()
subnet = self.subnets.first()
api.neutron.network_get(IsA(http.HttpRequest),
network.id).AndReturn(network)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'subnet_allocation').\
AndReturn(True)
api.neutron.subnetpool_list(IsA(http.HttpRequest)).\
AndReturn(self.subnetpools.list())
self.mox.ReplayAll()
form_data = {}
if t_w_snpl:
subnetpool = self.subnetpools.first()
form_data['subnetpool'] = subnetpool.id
# end address is not a valid IP address
allocation_pools = '10.0.0.2,invalid_address'
form_data = form_data_subnet(subnet,
allocation_pools=allocation_pools)
form_data.update(form_data_subnet(subnet,
allocation_pools=allocation_pools))
url = reverse('horizon:project:networks:addsubnet',
args=[subnet.network_id])
res = self.client.post(url, form_data)
@ -1115,18 +1289,37 @@ class NetworkSubnetTests(test.TestCase):
'allocation_pools: Invalid IP address '
'(value=%s)' % allocation_pools.split(',')[1])
@test.create_stubs({api.neutron: ('network_get',)})
def test_subnet_create_post_invalid_pools_ip_network(self):
def test_subnet_create_post_invalid_pools_invalid_address_w_snpool(self):
self.test_subnet_create_post_invalid_pools_invalid_address(
t_w_snpl=True)
@test.create_stubs({api.neutron: ('network_get',
'is_extension_supported',
'subnetpool_list',)})
def test_subnet_create_post_invalid_pools_ip_network(self,
test_w_snpool=False):
network = self.networks.first()
subnet = self.subnets.first()
api.neutron.network_get(IsA(http.HttpRequest),
network.id).AndReturn(network)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'subnet_allocation').\
AndReturn(True)
api.neutron.subnetpool_list(IsA(http.HttpRequest)).\
AndReturn(self.subnetpools.list())
self.mox.ReplayAll()
form_data = {}
if test_w_snpool:
subnetpool = self.subnetpools.first()
form_data['subnetpool'] = subnetpool.id
# start address is CIDR
allocation_pools = '10.0.0.2/24,10.0.0.5'
form_data = form_data_subnet(subnet,
allocation_pools=allocation_pools)
form_data.update(form_data_subnet(subnet,
allocation_pools=allocation_pools))
url = reverse('horizon:project:networks:addsubnet',
args=[subnet.network_id])
res = self.client.post(url, form_data)
@ -1135,14 +1328,33 @@ class NetworkSubnetTests(test.TestCase):
'allocation_pools: Invalid IP address '
'(value=%s)' % allocation_pools.split(',')[0])
@test.create_stubs({api.neutron: ('network_get',)})
def test_subnet_create_post_invalid_pools_start_larger_than_end(self):
def test_subnet_create_post_invalid_pools_ip_network_with_subnetpool(self):
self.test_subnet_create_post_invalid_pools_ip_network(
test_w_snpool=True)
@test.create_stubs({api.neutron: ('network_get',
'is_extension_supported',
'subnetpool_list',)})
def test_subnet_create_post_invalid_pools_start_larger_than_end(self,
tsn=False):
network = self.networks.first()
subnet = self.subnets.first()
api.neutron.network_get(IsA(http.HttpRequest),
network.id).AndReturn(network)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'subnet_allocation').\
AndReturn(True)
api.neutron.subnetpool_list(IsA(http.HttpRequest)).\
AndReturn(self.subnetpools.list())
self.mox.ReplayAll()
form_data = {}
if tsn:
subnetpool = self.subnetpools.first()
form_data['subnetpool'] = subnetpool.id
# start address is larger than end address
allocation_pools = '10.0.0.254,10.0.0.2'
form_data = form_data_subnet(subnet,
@ -1155,18 +1367,38 @@ class NetworkSubnetTests(test.TestCase):
'Start address is larger than end address '
'(value=%s)' % allocation_pools)
@test.create_stubs({api.neutron: ('network_get',)})
def test_subnet_create_post_invalid_nameservers(self):
def test_subnet_create_post_invalid_pools_start_larger_than_end_tsn(self):
self.test_subnet_create_post_invalid_pools_start_larger_than_end(
tsn=True)
@test.create_stubs({api.neutron: ('network_get',
'is_extension_supported',
'subnetpool_list',)})
def test_subnet_create_post_invalid_nameservers(self,
test_w_subnetpool=False):
network = self.networks.first()
subnet = self.subnets.first()
api.neutron.network_get(IsA(http.HttpRequest),
network.id).AndReturn(network)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'subnet_allocation').\
AndReturn(True)
api.neutron.subnetpool_list(IsA(http.HttpRequest)).\
AndReturn(self.subnetpools.list())
self.mox.ReplayAll()
form_data = {}
if test_w_subnetpool:
subnetpool = self.subnetpools.first()
form_data['subnetpool'] = subnetpool.id
# invalid DNS server address
dns_nameservers = ['192.168.0.2', 'invalid_address']
form_data = form_data_subnet(subnet, dns_nameservers=dns_nameservers,
allocation_pools=[])
form_data.update(form_data_subnet(subnet,
dns_nameservers=dns_nameservers,
allocation_pools=[]))
url = reverse('horizon:project:networks:addsubnet',
args=[subnet.network_id])
res = self.client.post(url, form_data)
@ -1175,19 +1407,39 @@ class NetworkSubnetTests(test.TestCase):
'dns_nameservers: Invalid IP address '
'(value=%s)' % dns_nameservers[1])
@test.create_stubs({api.neutron: ('network_get',)})
def test_subnet_create_post_invalid_routes_destination_only(self):
def test_subnet_create_post_invalid_nameservers_with_subnetpool(self):
self.test_subnet_create_post_invalid_nameservers(
test_w_subnetpool=True)
@test.create_stubs({api.neutron: ('network_get',
'is_extension_supported',
'subnetpool_list',)})
def test_subnet_create_post_invalid_routes_destination_only(self,
tsn=False):
network = self.networks.first()
subnet = self.subnets.first()
api.neutron.network_get(IsA(http.HttpRequest),
network.id).AndReturn(network)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'subnet_allocation').\
AndReturn(True)
api.neutron.subnetpool_list(IsA(http.HttpRequest)).\
AndReturn(self.subnetpools.list())
self.mox.ReplayAll()
form_data = {}
if tsn:
subnetpool = self.subnetpools.first()
form_data['subnetpool'] = subnetpool.id
# Start only host_route
host_routes = '192.168.0.0/24'
form_data = form_data_subnet(subnet,
allocation_pools=[],
host_routes=host_routes)
form_data.update(form_data_subnet(subnet,
allocation_pools=[],
host_routes=host_routes))
url = reverse('horizon:project:networks:addsubnet',
args=[subnet.network_id])
res = self.client.post(url, form_data)
@ -1197,19 +1449,38 @@ class NetworkSubnetTests(test.TestCase):
'Destination CIDR and nexthop must be specified '
'(value=%s)' % host_routes)
@test.create_stubs({api.neutron: ('network_get',)})
def test_subnet_create_post_invalid_routes_three_entries(self):
def test_subnet_create_post_invalid_routes_destination_only_w_snpool(self):
self.test_subnet_create_post_invalid_routes_destination_only(
tsn=True)
@test.create_stubs({api.neutron: ('network_get',
'is_extension_supported',
'subnetpool_list',)})
def test_subnet_create_post_invalid_routes_three_entries(self,
tsn=False):
network = self.networks.first()
subnet = self.subnets.first()
api.neutron.network_get(IsA(http.HttpRequest),
network.id).AndReturn(network)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'subnet_allocation').\
AndReturn(True)
api.neutron.subnetpool_list(IsA(http.HttpRequest)).\
AndReturn(self.subnetpools.list())
self.mox.ReplayAll()
form_data = {}
if tsn:
subnetpool = self.subnetpools.first()
form_data['subnetpool'] = subnetpool.id
# host_route with three entries
host_routes = 'aaaa,bbbb,cccc'
form_data = form_data_subnet(subnet,
allocation_pools=[],
host_routes=host_routes)
form_data.update(form_data_subnet(subnet,
allocation_pools=[],
host_routes=host_routes))
url = reverse('horizon:project:networks:addsubnet',
args=[subnet.network_id])
res = self.client.post(url, form_data)
@ -1219,19 +1490,38 @@ class NetworkSubnetTests(test.TestCase):
'Destination CIDR and nexthop must be specified '
'(value=%s)' % host_routes)
@test.create_stubs({api.neutron: ('network_get',)})
def test_subnet_create_post_invalid_routes_invalid_destination(self):
def test_subnet_create_post_invalid_routes_three_entries_with_tsn(self):
self.test_subnet_create_post_invalid_routes_three_entries(
tsn=True)
@test.create_stubs({api.neutron: ('network_get',
'is_extension_supported',
'subnetpool_list',)})
def test_subnet_create_post_invalid_routes_invalid_destination(self,
tsn=False):
network = self.networks.first()
subnet = self.subnets.first()
api.neutron.network_get(IsA(http.HttpRequest),
network.id).AndReturn(network)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'subnet_allocation').\
AndReturn(True)
api.neutron.subnetpool_list(IsA(http.HttpRequest)).\
AndReturn(self.subnetpools.list())
self.mox.ReplayAll()
form_data = {}
if tsn:
subnetpool = self.subnetpools.first()
form_data['subnetpool'] = subnetpool.id
# invalid destination network
host_routes = '172.16.0.0/64,10.0.0.253'
form_data = form_data_subnet(subnet,
host_routes=host_routes,
allocation_pools=[])
form_data.update(form_data_subnet(subnet,
host_routes=host_routes,
allocation_pools=[]))
url = reverse('horizon:project:networks:addsubnet',
args=[subnet.network_id])
res = self.client.post(url, form_data)
@ -1240,19 +1530,38 @@ class NetworkSubnetTests(test.TestCase):
'host_routes: Invalid IP address '
'(value=%s)' % host_routes.split(',')[0])
@test.create_stubs({api.neutron: ('network_get',)})
def test_subnet_create_post_invalid_routes_nexthop_ip_network(self):
def test_subnet_create_post_invalid_routes_invalid_destination_tsn(self):
self.test_subnet_create_post_invalid_routes_invalid_destination(
tsn=True)
@test.create_stubs({api.neutron: ('network_get',
'is_extension_supported',
'subnetpool_list',)})
def test_subnet_create_post_invalid_routes_nexthop_ip_network(self,
tsn=False):
network = self.networks.first()
subnet = self.subnets.first()
api.neutron.network_get(IsA(http.HttpRequest),
network.id).AndReturn(network)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'subnet_allocation').\
AndReturn(True)
api.neutron.subnetpool_list(IsA(http.HttpRequest)).\
AndReturn(self.subnetpools.list())
self.mox.ReplayAll()
form_data = {}
if tsn:
subnetpool = self.subnetpools.first()
form_data['subnetpool'] = subnetpool.id
# nexthop is not an IP address
host_routes = '172.16.0.0/24,10.0.0.253/24'
form_data = form_data_subnet(subnet,
host_routes=host_routes,
allocation_pools=[])
form_data.update(form_data_subnet(subnet,
host_routes=host_routes,
allocation_pools=[]))
url = reverse('horizon:project:networks:addsubnet',
args=[subnet.network_id])
res = self.client.post(url, form_data)
@ -1261,9 +1570,20 @@ class NetworkSubnetTests(test.TestCase):
'host_routes: Invalid IP address '
'(value=%s)' % host_routes.split(',')[1])
@test.create_stubs({api.neutron: ('network_get',
'subnet_create',)})
def test_subnet_create_post_invalid_routes_nexthop_ip_network_tsn(self):
self.test_subnet_create_post_invalid_routes_nexthop_ip_network(
tsn=True)
@test.create_stubs({api.neutron: ('is_extension_supported',
'network_get',
'subnet_create',
'subnetpool_list',)})
def test_v6subnet_create_post(self):
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'subnet_allocation').\
AndReturn(True)
api.neutron.subnetpool_list(IsA(http.HttpRequest)).\
AndReturn(self.subnetpools.list())
network = self.networks.get(name="v6_net1")
subnet = self.subnets.get(name="v6_subnet1")
api.neutron.network_get(IsA(http.HttpRequest),

View File

@ -50,6 +50,20 @@ class CreateNetworkInfoAction(workflows.Action):
required=False,
help_text=_("The state to start"
" the network in."))
with_subnet = forms.BooleanField(label=_("Create Subnet"),
widget=forms.CheckboxInput(attrs={
'class': 'switchable',
'data-slug': 'with_subnet',
'data-hide-tab': 'create_network__'
'createsubnetinfo'
'action,'
'create_network__'
'createsubnetdetail'
'action,',
'data-hide-on-checked': 'false'
}),
initial=True,
required=False)
def __init__(self, request, *args, **kwargs):
super(CreateNetworkInfoAction, self).__init__(request,
@ -84,35 +98,56 @@ class CreateNetworkInfoAction(workflows.Action):
class CreateNetworkInfo(workflows.Step):
action_class = CreateNetworkInfoAction
contributes = ("net_name", "admin_state", "net_profile_id")
contributes = ("net_name", "admin_state", "net_profile_id", "with_subnet")
class CreateSubnetInfoAction(workflows.Action):
with_subnet = forms.BooleanField(label=_("Create Subnet"),
widget=forms.CheckboxInput(attrs={
'class': 'switchable',
'data-slug': 'with_subnet',
'data-hide-tab': 'create_network__'
'createsubnetdetail'
'action',
'data-hide-on-checked': 'false'
}),
initial=True,
required=False)
subnet_name = forms.CharField(max_length=255,
widget=forms.TextInput(attrs={
'class': 'switched',
'data-switch-on': 'with_subnet',
}),
label=_("Subnet Name"),
required=False)
address_source = forms.ChoiceField(
required=False,
label=_('Network Address Source'),
choices=[('manual', _('Enter Network Address manually')),
('subnetpool', _('Allocate Network Address from a pool'))],
widget=forms.Select(attrs={
'class': 'switchable',
'data-slug': 'source',
}))
subnetpool = forms.ChoiceField(
label=_("Address pool"),
widget=forms.SelectWidget(attrs={
'class': 'switched switchable',
'data-slug': 'subnetpool',
'data-switch-on': 'source',
'data-source-subnetpool': _('Address pool')},
data_attrs=('name', 'prefixes',
'ip_version',
'min_prefixlen',
'max_prefixlen',
'default_prefixlen'),
transform=lambda x: "%s (%s)" % (x.name, ", ".join(x.prefixes))
if 'prefixes' in x else "%s" % (x.name)),
required=False)
prefixlen = forms.ChoiceField(widget=forms.Select(attrs={
'class': 'switched',
'data-switch-on': 'subnetpool',
}),
label=_('Network Mask'),
required=False)
cidr = forms.IPField(label=_("Network Address"),
required=False,
initial="",
widget=forms.TextInput(attrs={
'class': 'switched',
'data-switch-on': 'with_subnet',
'data-is-required': 'true'
'data-switch-on': 'source',
'data-source-manual': _("Network Address"),
}),
help_text=_("Network address in CIDR format "
"(e.g. 192.168.0.0/24, 2001:DB8::/48)"),
@ -120,16 +155,17 @@ class CreateSubnetInfoAction(workflows.Action):
mask=True)
ip_version = forms.ChoiceField(choices=[(4, 'IPv4'), (6, 'IPv6')],
widget=forms.Select(attrs={
'class': 'switchable switched',
'class': 'switchable',
'data-slug': 'ipversion',
'data-switch-on': 'with_subnet'
}),
label=_("IP Version"))
label=_("IP Version"),
required=False)
gateway_ip = forms.IPField(
label=_("Gateway IP"),
widget=forms.TextInput(attrs={
'class': 'switched',
'data-switch-on': 'with_subnet gateway_ip'
'data-switch-on': 'source gateway_ip',
'data-source-manual': _("Gateway IP")
}),
required=False,
initial="",
@ -145,37 +181,109 @@ class CreateSubnetInfoAction(workflows.Action):
mask=False)
no_gateway = forms.BooleanField(label=_("Disable Gateway"),
widget=forms.CheckboxInput(attrs={
'class': 'switched switchable',
'class': 'switchable',
'data-slug': 'gateway_ip',
'data-switch-on': 'with_subnet',
'data-hide-on-checked': 'true'
}),
initial=False,
required=False)
msg = _('Specify "Network Address" or '
msg = _('Specify "Network Address", "Address pool" or '
'clear "Create Subnet" checkbox.')
class Meta(object):
name = _("Subnet")
help_text = _('Create a subnet associated with the new network, '
'in which case "Network Address" must be specified. '
'If you wish to create a network without a subnet, '
'uncheck the "Create Subnet" checkbox.')
help_text = _('Create a subnet associated with the network. '
'Advanced configuration is available by clicking on the '
'"Subnet Details" tab.')
def __init__(self, request, context, *args, **kwargs):
super(CreateSubnetInfoAction, self).__init__(request, context, *args,
**kwargs)
if 'with_subnet' in context:
self.fields['with_subnet'] = forms.BooleanField(
initial=context['with_subnet'],
required=False,
widget=forms.HiddenInput()
)
if not getattr(settings, 'OPENSTACK_NEUTRON_NETWORK',
{}).get('enable_ipv6', True):
self.fields['ip_version'].widget = forms.HiddenInput()
self.fields['ip_version'].initial = 4
try:
if api.neutron.is_extension_supported(request,
'subnet_allocation'):
self.fields['subnetpool'].choices = \
self.get_subnetpool_choices(request)
else:
self.hide_subnetpool_choices()
except Exception:
self.hide_subnetpool_choices()
msg = _('Unable to initialize subnetpools')
exceptions.handle(request, msg)
if len(self.fields['subnetpool'].choices):
# Pre-populate prefixlen choices to satisfy Django
# ChoiceField Validation. This is overridden w/data from
# subnetpool on select.
self.fields['prefixlen'].choices = \
zip(list(range(0, 128 + 1)),
list(range(0, 128 + 1)))
# Populate data-fields for switching the prefixlen field
# when user selects a subnetpool other than
# "Provider default pool"
for (id, name) in self.fields['subnetpool'].choices:
if not len(id):
continue
key = 'data-subnetpool-' + id
self.fields['prefixlen'].widget.attrs[key] = \
_('Network Mask')
else:
self.hide_subnetpool_choices()
def get_subnetpool_choices(self, request):
subnetpool_choices = [('', _('Select a pool'))]
default_ipv6_subnet_pool_label = \
getattr(settings, 'OPENSTACK_NEUTRON_NETWORK', {}).get(
'default_ipv6_subnet_pool_label', None)
default_ipv4_subnet_pool_label = \
getattr(settings, 'OPENSTACK_NEUTRON_NETWORK', {}).get(
'default_ipv4_subnet_pool_label', None)
if default_ipv6_subnet_pool_label:
subnetpool_dict = {'ip_version': 6,
'name': default_ipv6_subnet_pool_label}
subnetpool = api.neutron.SubnetPool(subnetpool_dict)
subnetpool_choices.append(('', subnetpool))
if default_ipv4_subnet_pool_label:
subnetpool_dict = {'ip_version': 4,
'name': default_ipv4_subnet_pool_label}
subnetpool = api.neutron.SubnetPool(subnetpool_dict)
subnetpool_choices.append(('', subnetpool))
for subnetpool in api.neutron.subnetpool_list(request):
subnetpool_choices.append((subnetpool.id, subnetpool))
return subnetpool_choices
def hide_subnetpool_choices(self):
self.fields['address_source'].widget = forms.HiddenInput()
self.fields['subnetpool'].choices = []
self.fields['subnetpool'].widget = forms.HiddenInput()
self.fields['prefixlen'].widget = forms.HiddenInput()
def _check_subnet_data(self, cleaned_data, is_create=True):
cidr = cleaned_data.get('cidr')
ip_version = int(cleaned_data.get('ip_version'))
gateway_ip = cleaned_data.get('gateway_ip')
no_gateway = cleaned_data.get('no_gateway')
if not cidr:
address_source = cleaned_data.get('address_source')
# When creating network from a pool it is allowed to supply empty
# subnetpool_id signalling that Neutron should choose the default
# pool configured by the operator. This is also part of the IPv6
# Prefix Delegation Workflow.
if not cidr and address_source != 'subnetpool':
raise forms.ValidationError(self.msg)
if cidr:
subnet = netaddr.IPNetwork(cidr)
@ -207,8 +315,9 @@ class CreateSubnetInfoAction(workflows.Action):
class CreateSubnetInfo(workflows.Step):
action_class = CreateSubnetInfoAction
contributes = ("with_subnet", "subnet_name", "cidr",
"ip_version", "gateway_ip", "no_gateway")
contributes = ("subnet_name", "cidr", "ip_version",
"gateway_ip", "no_gateway", "subnetpool",
"prefixlen", "address_source")
class CreateSubnetDetailAction(workflows.Action):
@ -421,7 +530,7 @@ class CreateNetwork(workflows.Workflow):
try:
params = {'network_id': network_id,
'name': data['subnet_name'],
'cidr': data['cidr'],
'cidr': data['cidr'] if len(data['cidr']) else None,
'ip_version': int(data['ip_version'])}
if tenant_id:
params['tenant_id'] = tenant_id
@ -429,6 +538,10 @@ class CreateNetwork(workflows.Workflow):
params['gateway_ip'] = None
elif data['gateway_ip']:
params['gateway_ip'] = data['gateway_ip']
if 'subnetpool' in data and len(data['subnetpool']):
params['subnetpool_id'] = data['subnetpool']
if 'prefixlen' in data and len(data['prefixlen']):
params['prefixlen'] = data['prefixlen']
self._setup_subnet_parameters(params, data)

View File

@ -238,6 +238,18 @@ OPENSTACK_NEUTRON_NETWORK = {
'enable_vpn': True,
'enable_fip_topology_check': True,
# Neutron can be configured with a default Subnet Pool to be used for IPv4
# subnet-allocation. Specify the label you wish to display in the Address
# pool selector on the create subnet step if you want to use this feature.
'default_ipv4_subnet_pool_label': None,
# Neutron can be configured with a default Subnet Pool to be used for IPv6
# subnet-allocation. Specify the label you wish to display in the Address
# pool selector on the create subnet step if you want to use this feature.
# You must set this to enable IPv6 Prefix Delegation in a PD-capable
# environment.
'default_ipv6_subnet_pool_label': None,
# The profile_support option is used to detect if an external router can be
# configured via the dashboard. When using specific plugins the
# profile_support can be turned on if needed.