Support fixed ip address when attaching interface
At the moment, when a user attaches an interface to an instance, only a network can be specified. But actually in nova, it is supported to specify a fixed IP address or a port. This patch will support these ways. Change-Id: I3535024a4f6ffd26b508f5e9afb25232202e1e02 Closes-Bug: #1595913
This commit is contained in:
parent
0ff122d830
commit
1e012e7fad
@ -287,19 +287,78 @@ class DetachVolume(forms.SelfHandlingForm):
|
||||
|
||||
class AttachInterface(forms.SelfHandlingForm):
|
||||
instance_id = forms.CharField(widget=forms.HiddenInput())
|
||||
network = forms.ThemableChoiceField(label=_("Network"))
|
||||
specification_method = forms.ThemableChoiceField(
|
||||
label=_("The way to specify an interface"),
|
||||
initial=False,
|
||||
widget=forms.ThemableSelectWidget(attrs={
|
||||
'class': 'switchable',
|
||||
'data-slug': 'specification_method',
|
||||
}))
|
||||
port = forms.ThemableChoiceField(
|
||||
label=_("Port"),
|
||||
required=False,
|
||||
widget=forms.ThemableSelectWidget(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'specification_method',
|
||||
'data-specification_method-port': _('Port'),
|
||||
}))
|
||||
network = forms.ThemableChoiceField(
|
||||
label=_("Network"),
|
||||
required=False,
|
||||
widget=forms.ThemableSelectWidget(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'specification_method',
|
||||
'data-specification_method-network': _('Network'),
|
||||
}))
|
||||
fixed_ip = forms.IPField(
|
||||
label=_("Fixed IP Address"),
|
||||
required=False,
|
||||
help_text=_("IP address for the new port"),
|
||||
version=forms.IPv4 | forms.IPv6,
|
||||
widget=forms.TextInput(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'specification_method',
|
||||
'data-specification_method-network': _('Fixed IP Address'),
|
||||
}))
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(AttachInterface, self).__init__(request, *args, **kwargs)
|
||||
networks = instance_utils.network_field_data(request,
|
||||
include_empty_option=True)
|
||||
include_empty_option=True,
|
||||
with_cidr=True)
|
||||
self.fields['network'].choices = networks
|
||||
|
||||
choices = [('network', _("by Network (and IP address)"))]
|
||||
ports = instance_utils.port_field_data(request, with_network=True)
|
||||
if len(ports) > 0:
|
||||
self.fields['port'].choices = ports
|
||||
choices.append(('port', _("by Port")))
|
||||
|
||||
self.fields['specification_method'].choices = choices
|
||||
|
||||
def clean_network(self):
|
||||
specification_method = self.cleaned_data.get('specification_method')
|
||||
network = self.cleaned_data.get('network')
|
||||
if specification_method == 'network' and not network:
|
||||
msg = _('This field is required.')
|
||||
self._errors['network'] = self.error_class([msg])
|
||||
return network
|
||||
|
||||
def handle(self, request, data):
|
||||
instance_id = data['instance_id']
|
||||
network = data.get('network')
|
||||
try:
|
||||
api.nova.interface_attach(request, instance_id, net_id=network)
|
||||
net_id = port_id = fixed_ip = None
|
||||
if data['specification_method'] == 'port':
|
||||
port_id = data.get('port')
|
||||
else:
|
||||
net_id = data.get('network')
|
||||
if data.get('fixed_ip'):
|
||||
fixed_ip = data.get('fixed_ip')
|
||||
api.nova.interface_attach(request,
|
||||
instance_id,
|
||||
net_id=net_id,
|
||||
fixed_ip=fixed_ip,
|
||||
port_id=port_id)
|
||||
msg = _('Attaching interface for instance %s.') % instance_id
|
||||
messages.success(request, msg)
|
||||
except Exception:
|
||||
|
@ -4423,7 +4423,9 @@ class ConsoleManagerTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
|
||||
api.neutron.network_list_for_tenant(IsA(http.HttpRequest),
|
||||
self.tenant.id) \
|
||||
.AndReturn(self.networks.list()[:1])
|
||||
|
||||
api.neutron.network_list_for_tenant(IsA(http.HttpRequest),
|
||||
self.tenant.id) \
|
||||
.AndReturn([])
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('horizon:project:instances:attach_interface',
|
||||
@ -4436,17 +4438,23 @@ class ConsoleManagerTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
|
||||
@helpers.create_stubs({api.neutron: ('network_list_for_tenant',),
|
||||
api.nova: ('interface_attach',)})
|
||||
def test_interface_attach_post(self):
|
||||
fixed_ip = '10.0.0.10'
|
||||
server = self.servers.first()
|
||||
network = api.neutron.network_list_for_tenant(IsA(http.HttpRequest),
|
||||
self.tenant.id) \
|
||||
.AndReturn(self.networks.list()[:1])
|
||||
api.neutron.network_list_for_tenant(IsA(http.HttpRequest),
|
||||
self.tenant.id) \
|
||||
.AndReturn([])
|
||||
api.nova.interface_attach(IsA(http.HttpRequest), server.id,
|
||||
net_id=network[0].id)
|
||||
net_id=network[0].id, fixed_ip=fixed_ip)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = {'instance_id': server.id,
|
||||
'network': network[0].id}
|
||||
'network': network[0].id,
|
||||
'specification_method': 'network',
|
||||
'fixed_ip': fixed_ip}
|
||||
|
||||
url = reverse('horizon:project:instances:attach_interface',
|
||||
args=[server.id])
|
||||
|
@ -11,6 +11,7 @@
|
||||
# under the License.
|
||||
|
||||
import logging
|
||||
from operator import itemgetter
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
@ -84,7 +85,7 @@ def server_group_list(request):
|
||||
return []
|
||||
|
||||
|
||||
def network_field_data(request, include_empty_option=False):
|
||||
def network_field_data(request, include_empty_option=False, with_cidr=False):
|
||||
"""Returns a list of tuples of all networks.
|
||||
|
||||
Generates a list of networks available to the user (request). And returns
|
||||
@ -93,6 +94,7 @@ def network_field_data(request, include_empty_option=False):
|
||||
:param request: django http request object
|
||||
:param include_empty_option: flag to include a empty tuple in the front of
|
||||
the list
|
||||
:param with_cidr: flag to include subnets cidr in field name
|
||||
:return: list of (id, name) tuples
|
||||
"""
|
||||
tenant_id = request.user.tenant_id
|
||||
@ -100,12 +102,24 @@ def network_field_data(request, include_empty_option=False):
|
||||
if api.base.is_service_enabled(request, 'network'):
|
||||
try:
|
||||
networks = api.neutron.network_list_for_tenant(request, tenant_id)
|
||||
networks = [(n.id, n.name_or_id) for n in networks if n['subnets']]
|
||||
networks.sort(key=lambda obj: obj[1])
|
||||
except Exception as e:
|
||||
msg = _('Failed to get network list {0}').format(six.text_type(e))
|
||||
exceptions.handle(request, msg)
|
||||
|
||||
_networks = []
|
||||
for n in networks:
|
||||
if not n['subnets']:
|
||||
continue
|
||||
v = n.name_or_id
|
||||
if with_cidr:
|
||||
cidrs = ([subnet.cidr for subnet in n['subnets']
|
||||
if subnet.ip_version == 4] +
|
||||
[subnet.cidr for subnet in n['subnets']
|
||||
if subnet.ip_version == 6])
|
||||
v += ' (%s)' % ', '.join(cidrs)
|
||||
_networks.append((n.id, v))
|
||||
networks = sorted(_networks, key=itemgetter(1))
|
||||
|
||||
if not networks:
|
||||
if include_empty_option:
|
||||
return [("", _("No networks available")), ]
|
||||
@ -167,21 +181,25 @@ def flavor_field_data(request, include_empty_option=False):
|
||||
return []
|
||||
|
||||
|
||||
def port_field_data(request):
|
||||
def port_field_data(request, with_network=False):
|
||||
"""Returns a list of tuples of all ports available for the tenant.
|
||||
|
||||
Generates a list of ports that have no device_owner based on the networks
|
||||
available to the tenant doing the request.
|
||||
|
||||
:param request: django http request object
|
||||
:param with_network: include network name in field name
|
||||
:return: list of (id, name) tuples
|
||||
"""
|
||||
|
||||
def add_more_info_port_name(port):
|
||||
def add_more_info_port_name(port, network):
|
||||
# add more info to the port for the display
|
||||
return "{} ({})".format(port.name_or_id,
|
||||
",".join([ip['ip_address']
|
||||
for ip in port['fixed_ips']]))
|
||||
port_name = "{} ({})".format(
|
||||
port.name_or_id, ",".join(
|
||||
[ip['ip_address'] for ip in port['fixed_ips']]))
|
||||
if with_network and network:
|
||||
port_name += " - {}".format(network.name_or_id)
|
||||
return port_name
|
||||
|
||||
ports = []
|
||||
if api.base.is_service_enabled(request, 'network'):
|
||||
@ -189,7 +207,7 @@ def port_field_data(request):
|
||||
request, request.user.tenant_id)
|
||||
for network in network_list:
|
||||
ports.extend(
|
||||
[(port.id, add_more_info_port_name(port))
|
||||
[(port.id, add_more_info_port_name(port, network))
|
||||
for port in api.neutron.port_list_with_trunk_types(
|
||||
request, network_id=network.id,
|
||||
tenant_id=request.user.tenant_id)
|
||||
|
5
releasenotes/notes/bug-1595913-5f0cd019b7c2173a.yaml
Normal file
5
releasenotes/notes/bug-1595913-5f0cd019b7c2173a.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
features:
|
||||
- Added the way to specify an interface when attaching it
|
||||
to an instance. It can be specified by a network and a
|
||||
fixed IP address (optional) or a port.
|
Loading…
x
Reference in New Issue
Block a user