Detach interface support

Add interface detach support in the instance action list.
Note: the policy for detach interface not exists in nova.json,
once it's added, it should be added to the DetachInterface
table.

Closes-bug: #1445004
Change-Id: I4f37da6659c79808a5cdc061c8bc10f11a12118f
This commit is contained in:
liyingjun 2015-04-20 17:34:41 +08:00
parent a5946f1cce
commit 74645e1289
8 changed files with 139 additions and 1 deletions

View File

@ -925,6 +925,10 @@ def interface_attach(request,
fixed_ip)
def interface_detach(request, server, port_id):
return novaclient(request).servers.interface_detach(server, port_id)
@memoized
def list_extensions(request):
return nova_list_extensions.ListExtManager(novaclient(request)).show_all()

View File

@ -192,3 +192,44 @@ class AttachInterface(forms.SelfHandlingForm):
exceptions.handle(request, _("Unable to attach interface."),
redirect=redirect)
return True
class DetachInterface(forms.SelfHandlingForm):
instance_id = forms.CharField(widget=forms.HiddenInput())
port = forms.ChoiceField(label=_("Port"))
def __init__(self, request, *args, **kwargs):
super(DetachInterface, self).__init__(request, *args, **kwargs)
instance_id = kwargs.get('initial', {}).get('instance_id')
self.fields['instance_id'].initial = instance_id
ports = []
try:
ports = api.neutron.port_list(request, device_id=instance_id)
except Exception:
exceptions.handle(request, _('Unable to retrieve ports '
'information.'))
choices = []
for port in ports:
ips = []
for ip in port.fixed_ips:
ips.append(ip['ip_address'])
choices.append((port.id, ','.join(ips) or port.id))
if choices:
choices.insert(0, ("", _("Select Port")))
else:
choices.insert(0, ("", _("No Ports available")))
self.fields['port'].choices = choices
def handle(self, request, data):
instance = data.get('instance_id')
port = data.get('port')
try:
api.nova.interface_detach(request, instance, port)
msg = _('Detached interface %(port)s for instance '
'%(instance)s.') % {'port': port, 'instance': instance}
messages.success(request, msg)
except Exception:
redirect = reverse('horizon:project:instances:index')
exceptions.handle(request, _("Unable to detach interface."),
redirect=redirect)
return True

View File

@ -825,6 +825,25 @@ class AttachInterface(policy.PolicyTargetMixin, tables.LinkAction):
return urlresolvers.reverse(self.url, args=[instance_id])
# TODO(lyj): the policy for detach interface not exists in nova.json,
# once it's added, it should be added here.
class DetachInterface(policy.PolicyTargetMixin, tables.LinkAction):
name = "detach_interface"
verbose_name = _("Detach Interface")
classes = ("btn-confirm", "ajax-modal")
url = "horizon:project:instances:detach_interface"
def allowed(self, request, instance):
return ((instance.status in ACTIVE_STATES
or instance.status == 'SHUTOFF')
and not is_deleting(instance)
and api.base.is_service_enabled(request, 'network'))
def get_link_url(self, datum):
instance_id = self.table.get_object_id(datum)
return urlresolvers.reverse(self.url, args=[instance_id])
def get_ips(instance):
template_name = 'project/instances/_instance_ips.html'
ip_groups = {}
@ -1077,7 +1096,8 @@ class InstancesTable(tables.DataTable):
InstancesFilterAction)
row_actions = (StartInstance, ConfirmResize, RevertResize,
CreateSnapshot, SimpleAssociateIP, AssociateIP,
SimpleDisassociateIP, AttachInterface, EditInstance,
SimpleDisassociateIP, AttachInterface,
DetachInterface, EditInstance,
DecryptInstancePassword, EditInstanceSecurityGroups,
ConsoleLink, LogLink, TogglePause, ToggleSuspend,
ResizeLink, LockInstance, UnlockInstance,

View File

@ -0,0 +1,6 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block modal-body-right %}
<h3>{% trans "Description:" %}</h3>
<p>{% trans "Select the port to detach." %}</p>
{% endblock %}

View File

@ -0,0 +1,7 @@
{% extends "base.html" %}
{% load i18n %}
{% block title %}{% trans "Detach Interface" %}{% endblock %}
{% block main %}
{% include "project/instances/_detach_interface.html" %}
{% endblock %}

View File

@ -4372,3 +4372,41 @@ class ConsoleManagerTests(helpers.TestCase):
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, INDEX_URL)
@helpers.create_stubs({api.neutron: ('port_list',)})
def test_interface_detach_get(self):
server = self.servers.first()
api.neutron.port_list(IsA(http.HttpRequest),
device_id=server.id)\
.AndReturn([self.ports.first()])
self.mox.ReplayAll()
url = reverse('horizon:project:instances:detach_interface',
args=[server.id])
res = self.client.get(url)
self.assertTemplateUsed(res,
'project/instances/detach_interface.html')
@helpers.create_stubs({api.neutron: ('port_list',),
api.nova: ('interface_detach',)})
def test_interface_detach_post(self):
server = self.servers.first()
port = self.ports.first()
api.neutron.port_list(IsA(http.HttpRequest),
device_id=server.id)\
.AndReturn([port])
api.nova.interface_detach(IsA(http.HttpRequest), server.id, port.id)
self.mox.ReplayAll()
form_data = {'instance_id': server.id,
'port': port.id}
url = reverse('horizon:project:instances:detach_interface',
args=[server.id])
res = self.client.post(url, form_data)
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, INDEX_URL)

View File

@ -46,4 +46,6 @@ urlpatterns = patterns(
views.DecryptPasswordView.as_view(), name='decryptpassword'),
url(INSTANCES % 'attach_interface',
views.AttachInterfaceView.as_view(), name='attach_interface'),
url(INSTANCES % 'detach_interface',
views.DetachInterfaceView.as_view(), name='detach_interface'),
)

View File

@ -445,3 +445,23 @@ class AttachInterfaceView(forms.ModalFormView):
def get_initial(self):
return {'instance_id': self.kwargs['instance_id']}
class DetachInterfaceView(forms.ModalFormView):
form_class = project_forms.DetachInterface
template_name = 'project/instances/detach_interface.html'
modal_header = _("Detach Interface")
form_id = "detach_interface_form"
submit_label = _("Detach Interface")
submit_url = "horizon:project:instances:detach_interface"
success_url = reverse_lazy('horizon:project:instances:index')
def get_context_data(self, **kwargs):
context = super(DetachInterfaceView, self).get_context_data(**kwargs)
context['instance_id'] = self.kwargs['instance_id']
args = (self.kwargs['instance_id'],)
context['submit_url'] = reverse(self.submit_url, args=args)
return context
def get_initial(self):
return {'instance_id': self.kwargs['instance_id']}