Merge "Support fixed ip address when attaching interface"

This commit is contained in:
Zuul 2017-10-24 15:47:38 +00:00 committed by Gerrit Code Review
commit bed7a2194d
4 changed files with 106 additions and 16 deletions

View File

@ -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:

View File

@ -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])

View File

@ -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)

View 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.