Add support for attaching interface
It would be nice to add attach interface support in horizon. Change-Id: Ibdf97247b1503d57606fc02c4e3a0e33e787d10e Closes-bug: #1445004
This commit is contained in:
parent
21d0f91a26
commit
a5946f1cce
@ -917,6 +917,14 @@ def remove_host_from_aggregate(request, aggregate_id, host):
|
|||||||
return novaclient(request).aggregates.remove_host(aggregate_id, host)
|
return novaclient(request).aggregates.remove_host(aggregate_id, host)
|
||||||
|
|
||||||
|
|
||||||
|
def interface_attach(request,
|
||||||
|
server, port_id=None, net_id=None, fixed_ip=None):
|
||||||
|
return novaclient(request).servers.interface_attach(server,
|
||||||
|
port_id,
|
||||||
|
net_id,
|
||||||
|
fixed_ip)
|
||||||
|
|
||||||
|
|
||||||
@memoized
|
@memoized
|
||||||
def list_extensions(request):
|
def list_extensions(request):
|
||||||
return nova_list_extensions.ListExtManager(novaclient(request)).show_all()
|
return nova_list_extensions.ListExtManager(novaclient(request)).show_all()
|
||||||
|
@ -24,7 +24,10 @@ from horizon import messages
|
|||||||
from horizon.utils import validators
|
from horizon.utils import validators
|
||||||
|
|
||||||
from openstack_dashboard import api
|
from openstack_dashboard import api
|
||||||
from openstack_dashboard.dashboards.project.images import utils
|
from openstack_dashboard.dashboards.project.images \
|
||||||
|
import utils as image_utils
|
||||||
|
from openstack_dashboard.dashboards.project.instances \
|
||||||
|
import utils as instance_utils
|
||||||
|
|
||||||
|
|
||||||
def _image_choice_title(img):
|
def _image_choice_title(img):
|
||||||
@ -58,7 +61,8 @@ class RebuildInstanceForm(forms.SelfHandlingForm):
|
|||||||
instance_id = kwargs.get('initial', {}).get('instance_id')
|
instance_id = kwargs.get('initial', {}).get('instance_id')
|
||||||
self.fields['instance_id'].initial = instance_id
|
self.fields['instance_id'].initial = instance_id
|
||||||
|
|
||||||
images = utils.get_available_images(request, request.user.tenant_id)
|
images = image_utils.get_available_images(request,
|
||||||
|
request.user.tenant_id)
|
||||||
choices = [(image.id, image) for image in images]
|
choices = [(image.id, image) for image in images]
|
||||||
if choices:
|
if choices:
|
||||||
choices.insert(0, ("", _("Select Image")))
|
choices.insert(0, ("", _("Select Image")))
|
||||||
@ -162,3 +166,29 @@ class DecryptPasswordInstanceForm(forms.SelfHandlingForm):
|
|||||||
|
|
||||||
def handle(self, request, data):
|
def handle(self, request, data):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class AttachInterface(forms.SelfHandlingForm):
|
||||||
|
instance_id = forms.CharField(widget=forms.HiddenInput())
|
||||||
|
network = forms.ChoiceField(label=_("Network"))
|
||||||
|
|
||||||
|
def __init__(self, request, *args, **kwargs):
|
||||||
|
super(AttachInterface, self).__init__(request, *args, **kwargs)
|
||||||
|
instance_id = kwargs.get('initial', {}).get('instance_id')
|
||||||
|
self.fields['instance_id'].initial = instance_id
|
||||||
|
networks = instance_utils.network_field_data(request,
|
||||||
|
include_empty_option=True)
|
||||||
|
self.fields['network'].choices = networks
|
||||||
|
|
||||||
|
def handle(self, request, data):
|
||||||
|
instance = data.get('instance_id')
|
||||||
|
network = data.get('network')
|
||||||
|
try:
|
||||||
|
api.nova.interface_attach(request, instance, net_id=network)
|
||||||
|
msg = _('Attaching interface for instance %s.') % instance
|
||||||
|
messages.success(request, msg)
|
||||||
|
except Exception:
|
||||||
|
redirect = reverse('horizon:project:instances:index')
|
||||||
|
exceptions.handle(request, _("Unable to attach interface."),
|
||||||
|
redirect=redirect)
|
||||||
|
return True
|
||||||
|
@ -807,6 +807,24 @@ class UnlockInstance(policy.PolicyTargetMixin, tables.BatchAction):
|
|||||||
api.nova.server_unlock(request, obj_id)
|
api.nova.server_unlock(request, obj_id)
|
||||||
|
|
||||||
|
|
||||||
|
class AttachInterface(policy.PolicyTargetMixin, tables.LinkAction):
|
||||||
|
name = "attach_interface"
|
||||||
|
verbose_name = _("Attach Interface")
|
||||||
|
classes = ("btn-confirm", "ajax-modal")
|
||||||
|
url = "horizon:project:instances:attach_interface"
|
||||||
|
policy_rules = (("compute", "compute_extension:attach_interfaces"),)
|
||||||
|
|
||||||
|
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):
|
def get_ips(instance):
|
||||||
template_name = 'project/instances/_instance_ips.html'
|
template_name = 'project/instances/_instance_ips.html'
|
||||||
ip_groups = {}
|
ip_groups = {}
|
||||||
@ -1059,7 +1077,7 @@ class InstancesTable(tables.DataTable):
|
|||||||
InstancesFilterAction)
|
InstancesFilterAction)
|
||||||
row_actions = (StartInstance, ConfirmResize, RevertResize,
|
row_actions = (StartInstance, ConfirmResize, RevertResize,
|
||||||
CreateSnapshot, SimpleAssociateIP, AssociateIP,
|
CreateSnapshot, SimpleAssociateIP, AssociateIP,
|
||||||
SimpleDisassociateIP, EditInstance,
|
SimpleDisassociateIP, AttachInterface, EditInstance,
|
||||||
DecryptInstancePassword, EditInstanceSecurityGroups,
|
DecryptInstancePassword, EditInstanceSecurityGroups,
|
||||||
ConsoleLink, LogLink, TogglePause, ToggleSuspend,
|
ConsoleLink, LogLink, TogglePause, ToggleSuspend,
|
||||||
ResizeLink, LockInstance, UnlockInstance,
|
ResizeLink, LockInstance, UnlockInstance,
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
{% extends "horizon/common/_modal_form.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block modal-body-right %}
|
||||||
|
<h3>{% trans "Description:" %}</h3>
|
||||||
|
<p>{% trans "Select the network for interface attaching." %}</p>
|
||||||
|
{% endblock %}
|
@ -0,0 +1,7 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block title %}{% trans "Attach Interface" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
{% include "project/instances/_attach_interface.html" %}
|
||||||
|
{% endblock %}
|
@ -4334,3 +4334,41 @@ class ConsoleManagerTests(helpers.TestCase):
|
|||||||
def test_invalid_console_type_raise_value_error(self):
|
def test_invalid_console_type_raise_value_error(self):
|
||||||
self.assertRaises(exceptions.NotAvailable,
|
self.assertRaises(exceptions.NotAvailable,
|
||||||
console.get_console, None, 'FAKE', None)
|
console.get_console, None, 'FAKE', None)
|
||||||
|
|
||||||
|
@helpers.create_stubs({api.neutron: ('network_list_for_tenant',)})
|
||||||
|
def test_interface_attach_get(self):
|
||||||
|
server = self.servers.first()
|
||||||
|
api.neutron.network_list_for_tenant(IsA(http.HttpRequest),
|
||||||
|
self.tenant.id) \
|
||||||
|
.AndReturn(self.networks.list()[:1])
|
||||||
|
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
|
url = reverse('horizon:project:instances:attach_interface',
|
||||||
|
args=[server.id])
|
||||||
|
res = self.client.get(url)
|
||||||
|
|
||||||
|
self.assertTemplateUsed(res,
|
||||||
|
'project/instances/attach_interface.html')
|
||||||
|
|
||||||
|
@helpers.create_stubs({api.neutron: ('network_list_for_tenant',),
|
||||||
|
api.nova: ('interface_attach',)})
|
||||||
|
def test_interface_attach_post(self):
|
||||||
|
server = self.servers.first()
|
||||||
|
network = api.neutron.network_list_for_tenant(IsA(http.HttpRequest),
|
||||||
|
self.tenant.id) \
|
||||||
|
.AndReturn(self.networks.list()[:1])
|
||||||
|
api.nova.interface_attach(IsA(http.HttpRequest), server.id,
|
||||||
|
net_id=network[0].id)
|
||||||
|
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
|
form_data = {'instance_id': server.id,
|
||||||
|
'network': network[0].id}
|
||||||
|
|
||||||
|
url = reverse('horizon:project:instances:attach_interface',
|
||||||
|
args=[server.id])
|
||||||
|
res = self.client.post(url, form_data)
|
||||||
|
|
||||||
|
self.assertNoFormErrors(res)
|
||||||
|
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||||
|
@ -44,4 +44,6 @@ urlpatterns = patterns(
|
|||||||
url(INSTANCES % 'resize', views.ResizeView.as_view(), name='resize'),
|
url(INSTANCES % 'resize', views.ResizeView.as_view(), name='resize'),
|
||||||
url(INSTANCES_KEYPAIR % 'decryptpassword',
|
url(INSTANCES_KEYPAIR % 'decryptpassword',
|
||||||
views.DecryptPasswordView.as_view(), name='decryptpassword'),
|
views.DecryptPasswordView.as_view(), name='decryptpassword'),
|
||||||
|
url(INSTANCES % 'attach_interface',
|
||||||
|
views.AttachInterfaceView.as_view(), name='attach_interface'),
|
||||||
)
|
)
|
||||||
|
@ -425,3 +425,23 @@ class ResizeView(workflows.WorkflowView):
|
|||||||
'old_flavor_name': getattr(_object, 'flavor_name', ''),
|
'old_flavor_name': getattr(_object, 'flavor_name', ''),
|
||||||
'flavors': self.get_flavors()})
|
'flavors': self.get_flavors()})
|
||||||
return initial
|
return initial
|
||||||
|
|
||||||
|
|
||||||
|
class AttachInterfaceView(forms.ModalFormView):
|
||||||
|
form_class = project_forms.AttachInterface
|
||||||
|
template_name = 'project/instances/attach_interface.html'
|
||||||
|
modal_header = _("Attach Interface")
|
||||||
|
form_id = "attach_interface_form"
|
||||||
|
submit_label = _("Attach Interface")
|
||||||
|
submit_url = "horizon:project:instances:attach_interface"
|
||||||
|
success_url = reverse_lazy('horizon:project:instances:index')
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(AttachInterfaceView, 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']}
|
||||||
|
Loading…
Reference in New Issue
Block a user