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:
parent
09b745a4bb
commit
5894e159cf
@ -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)
|
||||||
|
@ -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)
|
||||||
|
4
releasenotes/notes/bug-1588663-6fab83e9d89b20d2.yaml
Normal file
4
releasenotes/notes/bug-1588663-6fab83e9d89b20d2.yaml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Support a parameter to specify subnet or fixed IP address
|
||||||
|
when creating port.
|
Loading…
Reference in New Issue
Block a user