Add a parameter fixed ip when creating a port

When we create a port, we can specify fixed ip in CLI.
However, we cannot do it in Horizon.
This patch will support this param.

Change-Id: I3b2fa8609ec27edcb8d3ecc400b93ea7870a48a1
Closes-Bug: #1588663
This commit is contained in:
Kenji Ishii 2016-06-03 16:44:51 +09:00
parent 09b745a4bb
commit 5894e159cf
3 changed files with 141 additions and 9 deletions

View File

@ -59,11 +59,51 @@ class CreatePort(forms.SelfHandlingForm):
"cases, different implementations can run on different " "cases, different implementations can run on different "
"hosts."), "hosts."),
required=False) required=False)
specify_ip = forms.ThemableChoiceField(
label=_("Specify IP address or subnet"),
help_text=_("To specify a subnet or a fixed IP, select any options."),
initial=False,
required=False,
choices=[('', _("Unspecified")),
('subnet_id', _("Subnet")),
('fixed_ip', _("Fixed IP Address"))],
widget=forms.Select(attrs={
'class': 'switchable',
'data-slug': 'specify_ip',
}))
subnet_id = forms.ThemableChoiceField(
label=_("Subnet"),
required=False,
widget=forms.Select(attrs={
'class': 'switched',
'data-switch-on': 'specify_ip',
'data-specify_ip-subnet_id': _('Subnet'),
}))
fixed_ip = forms.IPField(
label=_("Fixed IP Address"),
required=False,
help_text=_("Specify the subnet IP address for the new port"),
version=forms.IPv4 | forms.IPv6,
widget=forms.TextInput(attrs={
'class': 'switched',
'data-switch-on': 'specify_ip',
'data-specify_ip-fixed_ip': _('Fixed IP Address'),
}))
failure_url = 'horizon:admin:networks:detail' failure_url = 'horizon:admin:networks:detail'
def __init__(self, request, *args, **kwargs): def __init__(self, request, *args, **kwargs):
super(CreatePort, self).__init__(request, *args, **kwargs) super(CreatePort, self).__init__(request, *args, **kwargs)
# prepare subnet choices and input area for each subnet
subnet_choices = self._get_subnet_choices(kwargs['initial'])
if subnet_choices:
subnet_choices.insert(0, ('', _("Select a subnet")))
self.fields['subnet_id'].choices = subnet_choices
else:
self.fields['specify_ip'].widget = forms.HiddenInput()
self.fields['subnet_id'].widget = forms.HiddenInput()
self.fields['fixed_ip'].widget = forms.HiddenInput()
try: try:
if api.neutron.is_extension_supported(request, 'binding'): if api.neutron.is_extension_supported(request, 'binding'):
neutron_settings = getattr(settings, neutron_settings = getattr(settings,
@ -100,20 +140,45 @@ class CreatePort(forms.SelfHandlingForm):
msg = _("Unable to retrieve MAC learning state") msg = _("Unable to retrieve MAC learning state")
exceptions.handle(self.request, msg) exceptions.handle(self.request, msg)
def _get_subnet_choices(self, kwargs):
try:
network_id = kwargs['network_id']
network = api.neutron.network_get(self.request, network_id)
except Exception:
return []
return [(subnet.id, '%s %s' % (subnet.name_or_id, subnet.cidr))
for subnet in network.subnets]
def handle(self, request, data): def handle(self, request, data):
try: try:
# We must specify tenant_id of the network which a subnet is # We must specify tenant_id of the network which a subnet is
# created for if admin user does not belong to the tenant. # created for if admin user does not belong to the tenant.
network = api.neutron.network_get(request, data['network_id']) network = api.neutron.network_get(request, data['network_id'])
data['tenant_id'] = network.tenant_id params = {
data['admin_state_up'] = (data['admin_state'] == 'True') 'tenant_id': network.tenant_id,
del data['network_name'] 'network_id': data['network_id'],
del data['admin_state'] 'admin_state_up': data['admin_state'] == 'True',
if 'mac_state' in data: 'name': data['name'],
data['mac_learning_enabled'] = data['mac_state'] 'device_id': data['device_id'],
del data['mac_state'] 'device_owner': data['device_owner'],
'binding__host_id': data['binding__host_id']
}
port = api.neutron.port_create(request, **data) if data.get('specify_ip') == 'subnet_id':
if data.get('subnet_id'):
params['fixed_ips'] = [{"subnet_id": data['subnet_id']}]
elif data.get('specify_ip') == 'fixed_ip':
if data.get('fixed_ip'):
params['fixed_ips'] = [{"ip_address": data['fixed_ip']}]
if data.get('binding__vnic_type'):
params['binding__vnic_type'] = data['binding__vnic_type']
if data.get('mac_state'):
params['mac_learning_enabled'] = data['mac_state']
port = api.neutron.port_create(request, **params)
msg = _('Port %s was successfully created.') % port['id'] msg = _('Port %s was successfully created.') % port['id']
LOG.debug(msg) LOG.debug(msg)
messages.success(request, msg) messages.success(request, msg)

View File

@ -124,6 +124,9 @@ class NetworkPortTests(test.BaseAdminViewTests):
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.network_get(IsA(http.HttpRequest),
network.id)\
.AndReturn(self.networks.first())
api.neutron.is_extension_supported(IsA(http.HttpRequest), api.neutron.is_extension_supported(IsA(http.HttpRequest),
'binding')\ 'binding')\
.AndReturn(binding) .AndReturn(binding)
@ -167,6 +170,63 @@ class NetworkPortTests(test.BaseAdminViewTests):
redir_url = reverse(NETWORKS_DETAIL_URL, args=[port.network_id]) redir_url = reverse(NETWORKS_DETAIL_URL, args=[port.network_id])
self.assertRedirectsNoFollow(res, redir_url) self.assertRedirectsNoFollow(res, redir_url)
@test.create_stubs({api.neutron: ('network_get',
'is_extension_supported',
'port_create',)})
def test_port_create_post_with_fixed_ip(self):
network = self.networks.first()
port = self.ports.first()
api.neutron.network_get(IsA(http.HttpRequest),
network.id)\
.AndReturn(self.networks.first())
api.neutron.network_get(IsA(http.HttpRequest),
network.id)\
.AndReturn(self.networks.first())
api.neutron.network_get(IsA(http.HttpRequest),
network.id)\
.AndReturn(self.networks.first())
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'binding')\
.AndReturn(True)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'mac-learning')\
.AndReturn(True)
extension_kwargs = {}
extension_kwargs['binding__vnic_type'] = \
port.binding__vnic_type
api.neutron.port_create(IsA(http.HttpRequest),
tenant_id=network.tenant_id,
network_id=network.id,
name=port.name,
admin_state_up=port.admin_state_up,
device_id=port.device_id,
device_owner=port.device_owner,
binding__host_id=port.binding__host_id,
fixed_ips=port.fixed_ips,
**extension_kwargs)\
.AndReturn(port)
self.mox.ReplayAll()
form_data = {'network_id': port.network_id,
'network_name': network.name,
'name': port.name,
'admin_state': port.admin_state_up,
'device_id': port.device_id,
'device_owner': port.device_owner,
'binding__host_id': port.binding__host_id,
'specify_ip': 'fixed_ip',
'fixed_ip': port.fixed_ips[0]['ip_address'],
'subnet_id': port.fixed_ips[0]['subnet_id']}
form_data['binding__vnic_type'] = port.binding__vnic_type
form_data['mac_state'] = True
url = reverse('horizon:admin:networks:addport',
args=[port.network_id])
res = self.client.post(url, form_data)
self.assertNoFormErrors(res)
redir_url = reverse(NETWORKS_DETAIL_URL, args=[port.network_id])
self.assertRedirectsNoFollow(res, redir_url)
@test.create_stubs({api.neutron: ('network_get', @test.create_stubs({api.neutron: ('network_get',
'port_create', 'port_create',
'is_extension_supported',)}) 'is_extension_supported',)})
@ -189,6 +249,9 @@ class NetworkPortTests(test.BaseAdminViewTests):
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.network_get(IsA(http.HttpRequest),
network.id)\
.AndReturn(self.networks.first())
api.neutron.is_extension_supported(IsA(http.HttpRequest), api.neutron.is_extension_supported(IsA(http.HttpRequest),
'binding')\ 'binding')\
.AndReturn(binding) .AndReturn(binding)

View File

@ -0,0 +1,4 @@
---
features:
- Support a parameter to specify subnet or fixed IP address
when creating port.