Merge "Support create subnet w/Neutron subnet allocation"

This commit is contained in:
Jenkins 2015-09-02 06:37:10 +00:00 committed by Gerrit Code Review
commit 07d549c299
8 changed files with 658 additions and 105 deletions

View File

@ -797,6 +797,8 @@ Default::
'supported_vnic_types': ["*"], 'supported_vnic_types': ["*"],
'segmentation_id_range': {}, 'segmentation_id_range': {},
'enable_fip_topology_check': True, '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 A dictionary of settings which can be used to enable optional services provided
@ -967,6 +969,28 @@ subnet with no router if your Neutron backend allows it.
.. versionadded:: 2015.2(Liberty) .. 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`` ``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 * 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 * 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_image_source();
horizon.forms.handle_object_upload_source(); horizon.forms.handle_object_upload_source();
horizon.forms.datepicker(); horizon.forms.datepicker();
horizon.forms.handle_subnet_address_source();
horizon.forms.handle_subnet_subnetpool();
if (!horizon.conf.disable_password_reveal) { if (!horizon.conf.disable_password_reveal) {
horizon.forms.add_password_fields_reveal_buttons($("body")); horizon.forms.add_password_fields_reveal_buttons($("body"));
@ -260,24 +324,25 @@ horizon.addInitFunction(horizon.forms.init = function () {
visible = $switchable.is(':visible'), visible = $switchable.is(':visible'),
slug = $switchable.data('slug'), slug = $switchable.data('slug'),
checked = $switchable.prop('checked'), checked = $switchable.prop('checked'),
hide_tab = $switchable.data('hide-tab'), hide_tab = String($switchable.data('hide-tab')).split(','),
hide_on = $switchable.data('hideOnChecked'); hide_on = $switchable.data('hideOnChecked');
// If checkbox is hidden then do not apply any further logic // If checkbox is hidden then do not apply any further logic
if (!visible) return; if (!visible) return;
// If the checkbox has hide-tab attribute then hide/show the tab // 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'); var $btnfinal = $('.button-final');
if(checked == hide_on) { if(checked == hide_on) {
// If the checkbox is not checked then hide the tab // 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(); $('.button-next').hide();
$btnfinal.show(); $btnfinal.show();
$btnfinal.data('show-on-tab', $fieldset.prop('id')); $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 // 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(); $btnfinal.hide();
$('.button-next').show(); $('.button-next').show();
$btnfinal.removeData('show-on-tab'); $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_ra_mode, subnet.ipv6_address_mode)
subnet.ipv6_modes_desc = utils.IPV6_MODE_MAP.get(ipv6_modes) 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 return subnet
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):

View File

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

View File

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

View File

@ -463,6 +463,7 @@ class NetworkTests(test.TestCase):
def test_network_create_post_with_subnet_network_exception( def test_network_create_post_with_subnet_network_exception(
self, self,
test_with_profile=False, test_with_profile=False,
test_with_subnetpool=False,
): ):
network = self.networks.first() network = self.networks.first()
subnet = self.subnets.first() subnet = self.subnets.first()
@ -499,7 +500,9 @@ class NetworkTests(test.TestCase):
@test.create_stubs({api.neutron: ('network_create', @test.create_stubs({api.neutron: ('network_create',
'network_delete', 'network_delete',
'subnet_create', 'subnet_create',
'profile_list')}) 'profile_list',
'is_extension_supported',
'subnetpool_list',)})
def test_network_create_post_with_subnet_subnet_exception( def test_network_create_post_with_subnet_subnet_exception(
self, self,
test_with_profile=False, test_with_profile=False,
@ -514,6 +517,11 @@ class NetworkTests(test.TestCase):
api.neutron.profile_list(IsA(http.HttpRequest), api.neutron.profile_list(IsA(http.HttpRequest),
'network').AndReturn(net_profiles) 'network').AndReturn(net_profiles)
params['net_profile_id'] = net_profile_id 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), api.neutron.network_create(IsA(http.HttpRequest),
**params).AndReturn(network) **params).AndReturn(network)
api.neutron.subnet_create(IsA(http.HttpRequest), api.neutron.subnet_create(IsA(http.HttpRequest),
@ -546,9 +554,12 @@ class NetworkTests(test.TestCase):
self.test_network_create_post_with_subnet_subnet_exception( self.test_network_create_post_with_subnet_subnet_exception(
test_with_profile=True) 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, 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() network = self.networks.first()
subnet = self.subnets.first() subnet = self.subnets.first()
if test_with_profile: if test_with_profile:
@ -556,6 +567,11 @@ class NetworkTests(test.TestCase):
net_profile_id = self.net_profiles.first().id net_profile_id = self.net_profiles.first().id
api.neutron.profile_list(IsA(http.HttpRequest), api.neutron.profile_list(IsA(http.HttpRequest),
'network').AndReturn(net_profiles) '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() self.mox.ReplayAll()
form_data = {'net_name': network.name, form_data = {'net_name': network.name,
@ -563,12 +579,16 @@ class NetworkTests(test.TestCase):
'with_subnet': True} 'with_subnet': True}
if test_with_profile: if test_with_profile:
form_data['net_profile_id'] = net_profile_id 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='', form_data.update(form_data_subnet(subnet, cidr='',
allocation_pools=[])) allocation_pools=[]))
url = reverse('horizon:project:networks:create') url = reverse('horizon:project:networks:create')
res = self.client.post(url, form_data) 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.')) 'clear "Create Subnet" checkbox.'))
@test.update_settings( @test.update_settings(
@ -577,10 +597,17 @@ class NetworkTests(test.TestCase):
self.test_network_create_post_with_subnet_nocidr( self.test_network_create_post_with_subnet_nocidr(
test_with_profile=True) 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( def test_network_create_post_with_subnet_cidr_without_mask(
self, self,
test_with_profile=False, test_with_profile=False,
test_with_subnetpool=False,
): ):
network = self.networks.first() network = self.networks.first()
subnet = self.subnets.first() subnet = self.subnets.first()
@ -589,13 +616,22 @@ class NetworkTests(test.TestCase):
net_profile_id = self.net_profiles.first().id net_profile_id = self.net_profiles.first().id
api.neutron.profile_list(IsA(http.HttpRequest), api.neutron.profile_list(IsA(http.HttpRequest),
'network').AndReturn(net_profiles) '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, form_data = {'net_name': network.name,
'admin_state': network.admin_state_up, 'admin_state': network.admin_state_up,
'with_subnet': True} 'with_subnet': True}
if test_with_profile: if test_with_profile:
form_data['net_profile_id'] = net_profile_id 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', form_data.update(form_data_subnet(subnet, cidr='10.0.0.0',
allocation_pools=[])) allocation_pools=[]))
url = reverse('horizon:project:networks:create') url = reverse('horizon:project:networks:create')
@ -610,10 +646,17 @@ class NetworkTests(test.TestCase):
self.test_network_create_post_with_subnet_cidr_without_mask( self.test_network_create_post_with_subnet_cidr_without_mask(
test_with_profile=True) 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( def test_network_create_post_with_subnet_cidr_inconsistent(
self, self,
test_with_profile=False, test_with_profile=False,
test_with_subnetpool=False
): ):
network = self.networks.first() network = self.networks.first()
subnet = self.subnets.first() subnet = self.subnets.first()
@ -622,6 +665,12 @@ class NetworkTests(test.TestCase):
net_profile_id = self.net_profiles.first().id net_profile_id = self.net_profiles.first().id
api.neutron.profile_list(IsA(http.HttpRequest), api.neutron.profile_list(IsA(http.HttpRequest),
'network').AndReturn(net_profiles) '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() self.mox.ReplayAll()
# dummy IPv6 address # dummy IPv6 address
@ -631,6 +680,10 @@ class NetworkTests(test.TestCase):
'with_subnet': True} 'with_subnet': True}
if test_with_profile: if test_with_profile:
form_data['net_profile_id'] = net_profile_id 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, form_data.update(form_data_subnet(subnet, cidr=cidr,
allocation_pools=[])) allocation_pools=[]))
url = reverse('horizon:project:networks:create') url = reverse('horizon:project:networks:create')
@ -645,10 +698,17 @@ class NetworkTests(test.TestCase):
self.test_network_create_post_with_subnet_cidr_inconsistent( self.test_network_create_post_with_subnet_cidr_inconsistent(
test_with_profile=True) 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( def test_network_create_post_with_subnet_gw_inconsistent(
self, self,
test_with_profile=False, test_with_profile=False,
test_with_subnetpool=False,
): ):
network = self.networks.first() network = self.networks.first()
subnet = self.subnets.first() subnet = self.subnets.first()
@ -657,6 +717,12 @@ class NetworkTests(test.TestCase):
net_profile_id = self.net_profiles.first().id net_profile_id = self.net_profiles.first().id
api.neutron.profile_list(IsA(http.HttpRequest), api.neutron.profile_list(IsA(http.HttpRequest),
'network').AndReturn(net_profiles) '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() self.mox.ReplayAll()
# dummy IPv6 address # dummy IPv6 address
@ -666,6 +732,10 @@ class NetworkTests(test.TestCase):
'with_subnet': True} 'with_subnet': True}
if test_with_profile: if test_with_profile:
form_data['net_profile_id'] = net_profile_id 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, form_data.update(form_data_subnet(subnet, gateway_ip=gateway_ip,
allocation_pools=[])) allocation_pools=[]))
url = reverse('horizon:project:networks:create') url = reverse('horizon:project:networks:create')
@ -679,6 +749,10 @@ class NetworkTests(test.TestCase):
self.test_network_create_post_with_subnet_gw_inconsistent( self.test_network_create_post_with_subnet_gw_inconsistent(
test_with_profile=True) 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',)}) @test.create_stubs({api.neutron: ('network_get',)})
def test_network_update_get(self): def test_network_update_get(self):
network = self.networks.first() network = self.networks.first()
@ -834,7 +908,8 @@ class NetworkTests(test.TestCase):
class NetworkSubnetTests(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): def test_subnet_detail(self):
network = self.networks.first() network = self.networks.first()
subnet = self.subnets.first() subnet = self.subnets.first()
@ -883,7 +958,7 @@ class NetworkSubnetTests(test.TestCase):
@test.create_stubs({api.neutron: ('network_get', @test.create_stubs({api.neutron: ('network_get',
'subnet_create',)}) 'subnet_create',)})
def test_subnet_create_post(self): def test_subnet_create_post(self, test_with_subnetpool=False):
network = self.networks.first() network = self.networks.first()
subnet = self.subnets.first() subnet = self.subnets.first()
api.neutron.network_get(IsA(http.HttpRequest), api.neutron.network_get(IsA(http.HttpRequest),
@ -972,7 +1047,8 @@ class NetworkSubnetTests(test.TestCase):
@test.create_stubs({api.neutron: ('network_get', @test.create_stubs({api.neutron: ('network_get',
'subnet_create',)}) '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() network = self.networks.first()
subnet = self.subnets.first() subnet = self.subnets.first()
api.neutron.network_get(IsA(http.HttpRequest), api.neutron.network_get(IsA(http.HttpRequest),
@ -980,8 +1056,12 @@ class NetworkSubnetTests(test.TestCase):
.AndRaise(self.exceptions.neutron) .AndRaise(self.exceptions.neutron)
self.mox.ReplayAll() self.mox.ReplayAll()
form_data = form_data_subnet(subnet, form_data = {}
allocation_pools=[]) 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', url = reverse('horizon:project:networks:addsubnet',
args=[subnet.network_id]) args=[subnet.network_id])
res = self.client.post(url, form_data) res = self.client.post(url, form_data)
@ -989,9 +1069,14 @@ class NetworkSubnetTests(test.TestCase):
self.assertNoFormErrors(res) self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, INDEX_URL) 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', @test.create_stubs({api.neutron: ('network_get',
'subnet_create',)}) '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() network = self.networks.first()
subnet = self.subnets.first() subnet = self.subnets.first()
api.neutron.network_get(IsA(http.HttpRequest), api.neutron.network_get(IsA(http.HttpRequest),
@ -1007,8 +1092,7 @@ class NetworkSubnetTests(test.TestCase):
.AndRaise(self.exceptions.neutron) .AndRaise(self.exceptions.neutron)
self.mox.ReplayAll() self.mox.ReplayAll()
form_data = form_data_subnet(subnet, form_data = form_data_subnet(subnet, allocation_pools=[])
allocation_pools=[])
url = reverse('horizon:project:networks:addsubnet', url = reverse('horizon:project:networks:addsubnet',
args=[subnet.network_id]) args=[subnet.network_id])
res = self.client.post(url, form_data) res = self.client.post(url, form_data)
@ -1017,19 +1101,34 @@ class NetworkSubnetTests(test.TestCase):
args=[subnet.network_id]) args=[subnet.network_id])
self.assertRedirectsNoFollow(res, redir_url) self.assertRedirectsNoFollow(res, redir_url)
@test.create_stubs({api.neutron: ('network_get',)}) @test.create_stubs({api.neutron: ('network_get',
def test_subnet_create_post_cidr_inconsistent(self): 'is_extension_supported',
'subnetpool_list',)})
def test_subnet_create_post_cidr_inconsistent(self,
test_with_subnetpool=False):
network = self.networks.first() network = self.networks.first()
subnet = self.subnets.first() subnet = self.subnets.first()
api.neutron.network_get(IsA(http.HttpRequest), api.neutron.network_get(IsA(http.HttpRequest),
network.id)\ network.id)\
.AndReturn(self.networks.first()) .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() self.mox.ReplayAll()
form_data = {}
if test_with_subnetpool:
subnetpool = self.subnetpools.first()
form_data['subnetpool'] = subnetpool.id
# dummy IPv6 address # dummy IPv6 address
cidr = '2001:0DB8:0:CD30:123:4567:89AB:CDEF/60' cidr = '2001:0DB8:0:CD30:123:4567:89AB:CDEF/60'
form_data = form_data_subnet(subnet, cidr=cidr, form_data.update(form_data_subnet(subnet, cidr=cidr,
allocation_pools=[]) allocation_pools=[]))
url = reverse('horizon:project:networks:addsubnet', url = reverse('horizon:project:networks:addsubnet',
args=[subnet.network_id]) args=[subnet.network_id])
res = self.client.post(url, form_data) res = self.client.post(url, form_data)
@ -1038,37 +1137,74 @@ class NetworkSubnetTests(test.TestCase):
self.assertFormErrors(res, 1, expected_msg) self.assertFormErrors(res, 1, expected_msg)
self.assertTemplateUsed(res, views.WorkflowView.template_name) self.assertTemplateUsed(res, views.WorkflowView.template_name)
@test.create_stubs({api.neutron: ('network_get',)}) def test_subnet_create_post_cidr_inconsistent_with_subnetpool(self):
def test_subnet_create_post_gw_inconsistent(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() network = self.networks.first()
subnet = self.subnets.first() subnet = self.subnets.first()
api.neutron.network_get(IsA(http.HttpRequest), api.neutron.network_get(IsA(http.HttpRequest),
network.id)\ network.id)\
.AndReturn(self.networks.first()) .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() self.mox.ReplayAll()
form_data = {}
if test_with_subnetpool:
subnetpool = self.subnetpools.first()
form_data['subnetpool'] = subnetpool.id
# dummy IPv6 address # dummy IPv6 address
gateway_ip = '2001:0DB8:0:CD30:123:4567:89AB:CDEF' gateway_ip = '2001:0DB8:0:CD30:123:4567:89AB:CDEF'
form_data = form_data_subnet(subnet, gateway_ip=gateway_ip, form_data.update(form_data_subnet(subnet, gateway_ip=gateway_ip,
allocation_pools=[]) allocation_pools=[]))
url = reverse('horizon:project:networks:addsubnet', url = reverse('horizon:project:networks:addsubnet',
args=[subnet.network_id]) args=[subnet.network_id])
res = self.client.post(url, form_data) res = self.client.post(url, form_data)
self.assertContains(res, 'Gateway IP and IP version are inconsistent.') self.assertContains(res, 'Gateway IP and IP version are inconsistent.')
@test.create_stubs({api.neutron: ('network_get',)}) def test_subnet_create_post_gw_inconsistent_with_subnetpool(self):
def test_subnet_create_post_invalid_pools_start_only(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() network = self.networks.first()
subnet = self.subnets.first() subnet = self.subnets.first()
api.neutron.network_get(IsA(http.HttpRequest), api.neutron.network_get(IsA(http.HttpRequest),
network.id).AndReturn(network) 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() self.mox.ReplayAll()
form_data = {}
if test_w_snpool:
subnetpool = self.subnetpools.first()
form_data['subnetpool'] = subnetpool.id
# Start only allocation_pools # Start only allocation_pools
allocation_pools = '10.0.0.2' allocation_pools = '10.0.0.2'
form_data = form_data_subnet(subnet, form_data.update(form_data_subnet(subnet,
allocation_pools=allocation_pools) allocation_pools=allocation_pools))
url = reverse('horizon:project:networks:addsubnet', url = reverse('horizon:project:networks:addsubnet',
args=[subnet.network_id]) args=[subnet.network_id])
res = self.client.post(url, form_data) res = self.client.post(url, form_data)
@ -1077,18 +1213,37 @@ class NetworkSubnetTests(test.TestCase):
'Start and end addresses must be specified ' 'Start and end addresses must be specified '
'(value=%s)' % allocation_pools) '(value=%s)' % allocation_pools)
@test.create_stubs({api.neutron: ('network_get',)}) def test_subnet_create_post_invalid_pools_start_only_with_subnetpool(self):
def test_subnet_create_post_invalid_pools_three_entries(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() network = self.networks.first()
subnet = self.subnets.first() subnet = self.subnets.first()
api.neutron.network_get(IsA(http.HttpRequest), api.neutron.network_get(IsA(http.HttpRequest),
network.id).AndReturn(network) 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() self.mox.ReplayAll()
form_data = {}
if t_w_snpool:
subnetpool = self.subnetpools.first()
form_data['subnetpool'] = subnetpool.id
# pool with three entries # pool with three entries
allocation_pools = '10.0.0.2,10.0.0.3,10.0.0.4' allocation_pools = '10.0.0.2,10.0.0.3,10.0.0.4'
form_data = form_data_subnet(subnet, form_data.update(form_data_subnet(subnet,
allocation_pools=allocation_pools) allocation_pools=allocation_pools))
url = reverse('horizon:project:networks:addsubnet', url = reverse('horizon:project:networks:addsubnet',
args=[subnet.network_id]) args=[subnet.network_id])
res = self.client.post(url, form_data) res = self.client.post(url, form_data)
@ -1097,18 +1252,37 @@ class NetworkSubnetTests(test.TestCase):
'Start and end addresses must be specified ' 'Start and end addresses must be specified '
'(value=%s)' % allocation_pools) '(value=%s)' % allocation_pools)
@test.create_stubs({api.neutron: ('network_get',)}) def test_subnet_create_post_invalid_pools_three_entries_w_subnetpool(self):
def test_subnet_create_post_invalid_pools_invalid_address(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() network = self.networks.first()
subnet = self.subnets.first() subnet = self.subnets.first()
api.neutron.network_get(IsA(http.HttpRequest), api.neutron.network_get(IsA(http.HttpRequest),
network.id).AndReturn(network) 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() 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 # end address is not a valid IP address
allocation_pools = '10.0.0.2,invalid_address' allocation_pools = '10.0.0.2,invalid_address'
form_data = form_data_subnet(subnet, form_data.update(form_data_subnet(subnet,
allocation_pools=allocation_pools) allocation_pools=allocation_pools))
url = reverse('horizon:project:networks:addsubnet', url = reverse('horizon:project:networks:addsubnet',
args=[subnet.network_id]) args=[subnet.network_id])
res = self.client.post(url, form_data) res = self.client.post(url, form_data)
@ -1117,18 +1291,37 @@ class NetworkSubnetTests(test.TestCase):
'allocation_pools: Invalid IP address ' 'allocation_pools: Invalid IP address '
'(value=%s)' % allocation_pools.split(',')[1]) '(value=%s)' % allocation_pools.split(',')[1])
@test.create_stubs({api.neutron: ('network_get',)}) def test_subnet_create_post_invalid_pools_invalid_address_w_snpool(self):
def test_subnet_create_post_invalid_pools_ip_network(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() network = self.networks.first()
subnet = self.subnets.first() subnet = self.subnets.first()
api.neutron.network_get(IsA(http.HttpRequest), api.neutron.network_get(IsA(http.HttpRequest),
network.id).AndReturn(network) 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() self.mox.ReplayAll()
form_data = {}
if test_w_snpool:
subnetpool = self.subnetpools.first()
form_data['subnetpool'] = subnetpool.id
# start address is CIDR # start address is CIDR
allocation_pools = '10.0.0.2/24,10.0.0.5' allocation_pools = '10.0.0.2/24,10.0.0.5'
form_data = form_data_subnet(subnet, form_data.update(form_data_subnet(subnet,
allocation_pools=allocation_pools) allocation_pools=allocation_pools))
url = reverse('horizon:project:networks:addsubnet', url = reverse('horizon:project:networks:addsubnet',
args=[subnet.network_id]) args=[subnet.network_id])
res = self.client.post(url, form_data) res = self.client.post(url, form_data)
@ -1137,14 +1330,33 @@ class NetworkSubnetTests(test.TestCase):
'allocation_pools: Invalid IP address ' 'allocation_pools: Invalid IP address '
'(value=%s)' % allocation_pools.split(',')[0]) '(value=%s)' % allocation_pools.split(',')[0])
@test.create_stubs({api.neutron: ('network_get',)}) def test_subnet_create_post_invalid_pools_ip_network_with_subnetpool(self):
def test_subnet_create_post_invalid_pools_start_larger_than_end(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() network = self.networks.first()
subnet = self.subnets.first() subnet = self.subnets.first()
api.neutron.network_get(IsA(http.HttpRequest), api.neutron.network_get(IsA(http.HttpRequest),
network.id).AndReturn(network) 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() self.mox.ReplayAll()
form_data = {}
if tsn:
subnetpool = self.subnetpools.first()
form_data['subnetpool'] = subnetpool.id
# start address is larger than end address # start address is larger than end address
allocation_pools = '10.0.0.254,10.0.0.2' allocation_pools = '10.0.0.254,10.0.0.2'
form_data = form_data_subnet(subnet, form_data = form_data_subnet(subnet,
@ -1157,18 +1369,38 @@ class NetworkSubnetTests(test.TestCase):
'Start address is larger than end address ' 'Start address is larger than end address '
'(value=%s)' % allocation_pools) '(value=%s)' % allocation_pools)
@test.create_stubs({api.neutron: ('network_get',)}) def test_subnet_create_post_invalid_pools_start_larger_than_end_tsn(self):
def test_subnet_create_post_invalid_nameservers(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() network = self.networks.first()
subnet = self.subnets.first() subnet = self.subnets.first()
api.neutron.network_get(IsA(http.HttpRequest), api.neutron.network_get(IsA(http.HttpRequest),
network.id).AndReturn(network) 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() self.mox.ReplayAll()
form_data = {}
if test_w_subnetpool:
subnetpool = self.subnetpools.first()
form_data['subnetpool'] = subnetpool.id
# invalid DNS server address # invalid DNS server address
dns_nameservers = ['192.168.0.2', 'invalid_address'] dns_nameservers = ['192.168.0.2', 'invalid_address']
form_data = form_data_subnet(subnet, dns_nameservers=dns_nameservers, form_data.update(form_data_subnet(subnet,
allocation_pools=[]) dns_nameservers=dns_nameservers,
allocation_pools=[]))
url = reverse('horizon:project:networks:addsubnet', url = reverse('horizon:project:networks:addsubnet',
args=[subnet.network_id]) args=[subnet.network_id])
res = self.client.post(url, form_data) res = self.client.post(url, form_data)
@ -1177,19 +1409,39 @@ class NetworkSubnetTests(test.TestCase):
'dns_nameservers: Invalid IP address ' 'dns_nameservers: Invalid IP address '
'(value=%s)' % dns_nameservers[1]) '(value=%s)' % dns_nameservers[1])
@test.create_stubs({api.neutron: ('network_get',)}) def test_subnet_create_post_invalid_nameservers_with_subnetpool(self):
def test_subnet_create_post_invalid_routes_destination_only(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() network = self.networks.first()
subnet = self.subnets.first() subnet = self.subnets.first()
api.neutron.network_get(IsA(http.HttpRequest), api.neutron.network_get(IsA(http.HttpRequest),
network.id).AndReturn(network) 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() self.mox.ReplayAll()
form_data = {}
if tsn:
subnetpool = self.subnetpools.first()
form_data['subnetpool'] = subnetpool.id
# Start only host_route # Start only host_route
host_routes = '192.168.0.0/24' host_routes = '192.168.0.0/24'
form_data = form_data_subnet(subnet, form_data.update(form_data_subnet(subnet,
allocation_pools=[], allocation_pools=[],
host_routes=host_routes) host_routes=host_routes))
url = reverse('horizon:project:networks:addsubnet', url = reverse('horizon:project:networks:addsubnet',
args=[subnet.network_id]) args=[subnet.network_id])
res = self.client.post(url, form_data) res = self.client.post(url, form_data)
@ -1199,19 +1451,38 @@ class NetworkSubnetTests(test.TestCase):
'Destination CIDR and nexthop must be specified ' 'Destination CIDR and nexthop must be specified '
'(value=%s)' % host_routes) '(value=%s)' % host_routes)
@test.create_stubs({api.neutron: ('network_get',)}) def test_subnet_create_post_invalid_routes_destination_only_w_snpool(self):
def test_subnet_create_post_invalid_routes_three_entries(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() network = self.networks.first()
subnet = self.subnets.first() subnet = self.subnets.first()
api.neutron.network_get(IsA(http.HttpRequest), api.neutron.network_get(IsA(http.HttpRequest),
network.id).AndReturn(network) 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() self.mox.ReplayAll()
form_data = {}
if tsn:
subnetpool = self.subnetpools.first()
form_data['subnetpool'] = subnetpool.id
# host_route with three entries # host_route with three entries
host_routes = 'aaaa,bbbb,cccc' host_routes = 'aaaa,bbbb,cccc'
form_data = form_data_subnet(subnet, form_data.update(form_data_subnet(subnet,
allocation_pools=[], allocation_pools=[],
host_routes=host_routes) host_routes=host_routes))
url = reverse('horizon:project:networks:addsubnet', url = reverse('horizon:project:networks:addsubnet',
args=[subnet.network_id]) args=[subnet.network_id])
res = self.client.post(url, form_data) res = self.client.post(url, form_data)
@ -1221,19 +1492,38 @@ class NetworkSubnetTests(test.TestCase):
'Destination CIDR and nexthop must be specified ' 'Destination CIDR and nexthop must be specified '
'(value=%s)' % host_routes) '(value=%s)' % host_routes)
@test.create_stubs({api.neutron: ('network_get',)}) def test_subnet_create_post_invalid_routes_three_entries_with_tsn(self):
def test_subnet_create_post_invalid_routes_invalid_destination(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() network = self.networks.first()
subnet = self.subnets.first() subnet = self.subnets.first()
api.neutron.network_get(IsA(http.HttpRequest), api.neutron.network_get(IsA(http.HttpRequest),
network.id).AndReturn(network) 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() self.mox.ReplayAll()
form_data = {}
if tsn:
subnetpool = self.subnetpools.first()
form_data['subnetpool'] = subnetpool.id
# invalid destination network # invalid destination network
host_routes = '172.16.0.0/64,10.0.0.253' host_routes = '172.16.0.0/64,10.0.0.253'
form_data = form_data_subnet(subnet, form_data.update(form_data_subnet(subnet,
host_routes=host_routes, host_routes=host_routes,
allocation_pools=[]) allocation_pools=[]))
url = reverse('horizon:project:networks:addsubnet', url = reverse('horizon:project:networks:addsubnet',
args=[subnet.network_id]) args=[subnet.network_id])
res = self.client.post(url, form_data) res = self.client.post(url, form_data)
@ -1242,19 +1532,38 @@ class NetworkSubnetTests(test.TestCase):
'host_routes: Invalid IP address ' 'host_routes: Invalid IP address '
'(value=%s)' % host_routes.split(',')[0]) '(value=%s)' % host_routes.split(',')[0])
@test.create_stubs({api.neutron: ('network_get',)}) def test_subnet_create_post_invalid_routes_invalid_destination_tsn(self):
def test_subnet_create_post_invalid_routes_nexthop_ip_network(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() network = self.networks.first()
subnet = self.subnets.first() subnet = self.subnets.first()
api.neutron.network_get(IsA(http.HttpRequest), api.neutron.network_get(IsA(http.HttpRequest),
network.id).AndReturn(network) 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() self.mox.ReplayAll()
form_data = {}
if tsn:
subnetpool = self.subnetpools.first()
form_data['subnetpool'] = subnetpool.id
# nexthop is not an IP address # nexthop is not an IP address
host_routes = '172.16.0.0/24,10.0.0.253/24' host_routes = '172.16.0.0/24,10.0.0.253/24'
form_data = form_data_subnet(subnet, form_data.update(form_data_subnet(subnet,
host_routes=host_routes, host_routes=host_routes,
allocation_pools=[]) allocation_pools=[]))
url = reverse('horizon:project:networks:addsubnet', url = reverse('horizon:project:networks:addsubnet',
args=[subnet.network_id]) args=[subnet.network_id])
res = self.client.post(url, form_data) res = self.client.post(url, form_data)
@ -1263,9 +1572,20 @@ class NetworkSubnetTests(test.TestCase):
'host_routes: Invalid IP address ' 'host_routes: Invalid IP address '
'(value=%s)' % host_routes.split(',')[1]) '(value=%s)' % host_routes.split(',')[1])
@test.create_stubs({api.neutron: ('network_get', def test_subnet_create_post_invalid_routes_nexthop_ip_network_tsn(self):
'subnet_create',)}) 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): 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") network = self.networks.get(name="v6_net1")
subnet = self.subnets.get(name="v6_subnet1") subnet = self.subnets.get(name="v6_subnet1")
api.neutron.network_get(IsA(http.HttpRequest), api.neutron.network_get(IsA(http.HttpRequest),

View File

@ -50,6 +50,20 @@ class CreateNetworkInfoAction(workflows.Action):
required=False, required=False,
help_text=_("The state to start" help_text=_("The state to start"
" the network in.")) " 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): def __init__(self, request, *args, **kwargs):
super(CreateNetworkInfoAction, self).__init__(request, super(CreateNetworkInfoAction, self).__init__(request,
@ -84,35 +98,56 @@ class CreateNetworkInfoAction(workflows.Action):
class CreateNetworkInfo(workflows.Step): class CreateNetworkInfo(workflows.Step):
action_class = CreateNetworkInfoAction 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): 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, subnet_name = forms.CharField(max_length=255,
widget=forms.TextInput(attrs={ widget=forms.TextInput(attrs={
'class': 'switched',
'data-switch-on': 'with_subnet',
}), }),
label=_("Subnet Name"), label=_("Subnet Name"),
required=False) 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"), cidr = forms.IPField(label=_("Network Address"),
required=False, required=False,
initial="", initial="",
widget=forms.TextInput(attrs={ widget=forms.TextInput(attrs={
'class': 'switched', 'class': 'switched',
'data-switch-on': 'with_subnet', 'data-switch-on': 'source',
'data-is-required': 'true' 'data-source-manual': _("Network Address"),
}), }),
help_text=_("Network address in CIDR format " help_text=_("Network address in CIDR format "
"(e.g. 192.168.0.0/24, 2001:DB8::/48)"), "(e.g. 192.168.0.0/24, 2001:DB8::/48)"),
@ -120,16 +155,17 @@ class CreateSubnetInfoAction(workflows.Action):
mask=True) mask=True)
ip_version = forms.ChoiceField(choices=[(4, 'IPv4'), (6, 'IPv6')], ip_version = forms.ChoiceField(choices=[(4, 'IPv4'), (6, 'IPv6')],
widget=forms.Select(attrs={ widget=forms.Select(attrs={
'class': 'switchable switched', 'class': 'switchable',
'data-slug': 'ipversion', 'data-slug': 'ipversion',
'data-switch-on': 'with_subnet'
}), }),
label=_("IP Version")) label=_("IP Version"),
required=False)
gateway_ip = forms.IPField( gateway_ip = forms.IPField(
label=_("Gateway IP"), label=_("Gateway IP"),
widget=forms.TextInput(attrs={ widget=forms.TextInput(attrs={
'class': 'switched', 'class': 'switched',
'data-switch-on': 'with_subnet gateway_ip' 'data-switch-on': 'source gateway_ip',
'data-source-manual': _("Gateway IP")
}), }),
required=False, required=False,
initial="", initial="",
@ -145,37 +181,109 @@ class CreateSubnetInfoAction(workflows.Action):
mask=False) mask=False)
no_gateway = forms.BooleanField(label=_("Disable Gateway"), no_gateway = forms.BooleanField(label=_("Disable Gateway"),
widget=forms.CheckboxInput(attrs={ widget=forms.CheckboxInput(attrs={
'class': 'switched switchable', 'class': 'switchable',
'data-slug': 'gateway_ip', 'data-slug': 'gateway_ip',
'data-switch-on': 'with_subnet',
'data-hide-on-checked': 'true' 'data-hide-on-checked': 'true'
}), }),
initial=False, initial=False,
required=False) required=False)
msg = _('Specify "Network Address" or ' msg = _('Specify "Network Address", "Address pool" or '
'clear "Create Subnet" checkbox.') 'clear "Create Subnet" checkbox.')
class Meta(object): class Meta(object):
name = _("Subnet") name = _("Subnet")
help_text = _('Create a subnet associated with the new network, ' help_text = _('Create a subnet associated with the network. '
'in which case "Network Address" must be specified. ' 'Advanced configuration is available by clicking on the '
'If you wish to create a network without a subnet, ' '"Subnet Details" tab.')
'uncheck the "Create Subnet" checkbox.')
def __init__(self, request, context, *args, **kwargs): def __init__(self, request, context, *args, **kwargs):
super(CreateSubnetInfoAction, self).__init__(request, context, *args, super(CreateSubnetInfoAction, self).__init__(request, context, *args,
**kwargs) **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', if not getattr(settings, 'OPENSTACK_NEUTRON_NETWORK',
{}).get('enable_ipv6', True): {}).get('enable_ipv6', True):
self.fields['ip_version'].widget = forms.HiddenInput() self.fields['ip_version'].widget = forms.HiddenInput()
self.fields['ip_version'].initial = 4 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): def _check_subnet_data(self, cleaned_data, is_create=True):
cidr = cleaned_data.get('cidr') cidr = cleaned_data.get('cidr')
ip_version = int(cleaned_data.get('ip_version')) ip_version = int(cleaned_data.get('ip_version'))
gateway_ip = cleaned_data.get('gateway_ip') gateway_ip = cleaned_data.get('gateway_ip')
no_gateway = cleaned_data.get('no_gateway') 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) raise forms.ValidationError(self.msg)
if cidr: if cidr:
subnet = netaddr.IPNetwork(cidr) subnet = netaddr.IPNetwork(cidr)
@ -207,8 +315,9 @@ class CreateSubnetInfoAction(workflows.Action):
class CreateSubnetInfo(workflows.Step): class CreateSubnetInfo(workflows.Step):
action_class = CreateSubnetInfoAction action_class = CreateSubnetInfoAction
contributes = ("with_subnet", "subnet_name", "cidr", contributes = ("subnet_name", "cidr", "ip_version",
"ip_version", "gateway_ip", "no_gateway") "gateway_ip", "no_gateway", "subnetpool",
"prefixlen", "address_source")
class CreateSubnetDetailAction(workflows.Action): class CreateSubnetDetailAction(workflows.Action):
@ -421,7 +530,7 @@ class CreateNetwork(workflows.Workflow):
try: try:
params = {'network_id': network_id, params = {'network_id': network_id,
'name': data['subnet_name'], 'name': data['subnet_name'],
'cidr': data['cidr'], 'cidr': data['cidr'] if len(data['cidr']) else None,
'ip_version': int(data['ip_version'])} 'ip_version': int(data['ip_version'])}
if tenant_id: if tenant_id:
params['tenant_id'] = tenant_id params['tenant_id'] = tenant_id
@ -429,6 +538,10 @@ class CreateNetwork(workflows.Workflow):
params['gateway_ip'] = None params['gateway_ip'] = None
elif data['gateway_ip']: elif data['gateway_ip']:
params['gateway_ip'] = 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) self._setup_subnet_parameters(params, data)

View File

@ -225,6 +225,18 @@ OPENSTACK_NEUTRON_NETWORK = {
'enable_vpn': True, 'enable_vpn': True,
'enable_fip_topology_check': 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 # The profile_support option is used to detect if an external router can be
# configured via the dashboard. When using specific plugins the # configured via the dashboard. When using specific plugins the
# profile_support can be turned on if needed. # profile_support can be turned on if needed.