Merge "Support security groups association per port"
This commit is contained in:
commit
ed6a22b0fd
@ -331,8 +331,12 @@ class SecurityGroupManager(object):
|
|||||||
|
|
||||||
:returns: List of SecurityGroup objects
|
:returns: List of SecurityGroup objects
|
||||||
"""
|
"""
|
||||||
|
# This is to ensure tenant_id key is not populated
|
||||||
|
# if tenant_id=None is specified.
|
||||||
tenant_id = params.pop('tenant_id', self.request.user.tenant_id)
|
tenant_id = params.pop('tenant_id', self.request.user.tenant_id)
|
||||||
return self._list(tenant_id=tenant_id, **params)
|
if tenant_id:
|
||||||
|
params['tenant_id'] = tenant_id
|
||||||
|
return self._list(**params)
|
||||||
|
|
||||||
def _sg_name_dict(self, sg_id, rules):
|
def _sg_name_dict(self, sg_id, rules):
|
||||||
"""Create a mapping dict from secgroup id to its name."""
|
"""Create a mapping dict from secgroup id to its name."""
|
||||||
|
@ -341,21 +341,18 @@ 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: ('port_get',
|
|
||||||
'is_extension_supported',)})
|
|
||||||
def test_port_update_get(self):
|
def test_port_update_get(self):
|
||||||
self._test_port_update_get()
|
self._test_port_update_get()
|
||||||
|
|
||||||
@test.create_stubs({api.neutron: ('port_get',
|
|
||||||
'is_extension_supported',)})
|
|
||||||
def test_port_update_get_with_mac_learning(self):
|
def test_port_update_get_with_mac_learning(self):
|
||||||
self._test_port_update_get(mac_learning=True)
|
self._test_port_update_get(mac_learning=True)
|
||||||
|
|
||||||
@test.create_stubs({api.neutron: ('port_get',
|
|
||||||
'is_extension_supported',)})
|
|
||||||
def test_port_update_get_with_port_security(self):
|
def test_port_update_get_with_port_security(self):
|
||||||
self._test_port_update_get(port_security=True)
|
self._test_port_update_get(port_security=True)
|
||||||
|
|
||||||
|
@test.create_stubs({api.neutron: ('port_get',
|
||||||
|
'security_group_list',
|
||||||
|
'is_extension_supported',)})
|
||||||
def _test_port_update_get(self, mac_learning=False, binding=False,
|
def _test_port_update_get(self, mac_learning=False, binding=False,
|
||||||
port_security=False):
|
port_security=False):
|
||||||
port = self.ports.first()
|
port = self.ports.first()
|
||||||
@ -371,6 +368,9 @@ class NetworkPortTests(test.BaseAdminViewTests):
|
|||||||
api.neutron.is_extension_supported(IsA(http.HttpRequest),
|
api.neutron.is_extension_supported(IsA(http.HttpRequest),
|
||||||
'port-security')\
|
'port-security')\
|
||||||
.AndReturn(port_security)
|
.AndReturn(port_security)
|
||||||
|
api.neutron.security_group_list(IsA(http.HttpRequest),
|
||||||
|
tenant_id=None)\
|
||||||
|
.AndReturn(self.security_groups.list())
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
url = reverse('horizon:admin:networks:editport',
|
url = reverse('horizon:admin:networks:editport',
|
||||||
@ -379,24 +379,19 @@ class NetworkPortTests(test.BaseAdminViewTests):
|
|||||||
|
|
||||||
self.assertTemplateUsed(res, views.WorkflowView.template_name)
|
self.assertTemplateUsed(res, views.WorkflowView.template_name)
|
||||||
|
|
||||||
@test.create_stubs({api.neutron: ('port_get',
|
|
||||||
'is_extension_supported',
|
|
||||||
'port_update')})
|
|
||||||
def test_port_update_post(self):
|
def test_port_update_post(self):
|
||||||
self._test_port_update_post()
|
self._test_port_update_post()
|
||||||
|
|
||||||
@test.create_stubs({api.neutron: ('port_get',
|
|
||||||
'is_extension_supported',
|
|
||||||
'port_update')})
|
|
||||||
def test_port_update_post_with_mac_learning(self):
|
def test_port_update_post_with_mac_learning(self):
|
||||||
self._test_port_update_post(mac_learning=True)
|
self._test_port_update_post(mac_learning=True)
|
||||||
|
|
||||||
@test.create_stubs({api.neutron: ('port_get',
|
|
||||||
'is_extension_supported',
|
|
||||||
'port_update')})
|
|
||||||
def test_port_update_post_with_port_security(self):
|
def test_port_update_post_with_port_security(self):
|
||||||
self._test_port_update_post(port_security=True)
|
self._test_port_update_post(port_security=True)
|
||||||
|
|
||||||
|
@test.create_stubs({api.neutron: ('port_get',
|
||||||
|
'is_extension_supported',
|
||||||
|
'security_group_list',
|
||||||
|
'port_update')})
|
||||||
def _test_port_update_post(self, mac_learning=False, binding=False,
|
def _test_port_update_post(self, mac_learning=False, binding=False,
|
||||||
port_security=False):
|
port_security=False):
|
||||||
port = self.ports.first()
|
port = self.ports.first()
|
||||||
@ -411,6 +406,9 @@ class NetworkPortTests(test.BaseAdminViewTests):
|
|||||||
api.neutron.is_extension_supported(IsA(http.HttpRequest),
|
api.neutron.is_extension_supported(IsA(http.HttpRequest),
|
||||||
'port-security')\
|
'port-security')\
|
||||||
.MultipleTimes().AndReturn(port_security)
|
.MultipleTimes().AndReturn(port_security)
|
||||||
|
api.neutron.security_group_list(IsA(http.HttpRequest),
|
||||||
|
tenant_id=None)\
|
||||||
|
.AndReturn(self.security_groups.list())
|
||||||
extension_kwargs = {}
|
extension_kwargs = {}
|
||||||
if binding:
|
if binding:
|
||||||
extension_kwargs['binding__vnic_type'] = port.binding__vnic_type
|
extension_kwargs['binding__vnic_type'] = port.binding__vnic_type
|
||||||
@ -451,24 +449,19 @@ 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: ('port_get',
|
|
||||||
'is_extension_supported',
|
|
||||||
'port_update')})
|
|
||||||
def test_port_update_post_exception(self):
|
def test_port_update_post_exception(self):
|
||||||
self._test_port_update_post_exception()
|
self._test_port_update_post_exception()
|
||||||
|
|
||||||
@test.create_stubs({api.neutron: ('port_get',
|
|
||||||
'is_extension_supported',
|
|
||||||
'port_update')})
|
|
||||||
def test_port_update_post_exception_with_mac_learning(self):
|
def test_port_update_post_exception_with_mac_learning(self):
|
||||||
self._test_port_update_post_exception(mac_learning=True, binding=False)
|
self._test_port_update_post_exception(mac_learning=True, binding=False)
|
||||||
|
|
||||||
@test.create_stubs({api.neutron: ('port_get',
|
|
||||||
'is_extension_supported',
|
|
||||||
'port_update')})
|
|
||||||
def test_port_update_post_exception_with_port_security(self):
|
def test_port_update_post_exception_with_port_security(self):
|
||||||
self._test_port_update_post_exception(port_security=True)
|
self._test_port_update_post_exception(port_security=True)
|
||||||
|
|
||||||
|
@test.create_stubs({api.neutron: ('port_get',
|
||||||
|
'is_extension_supported',
|
||||||
|
'security_group_list',
|
||||||
|
'port_update')})
|
||||||
def _test_port_update_post_exception(self, mac_learning=False,
|
def _test_port_update_post_exception(self, mac_learning=False,
|
||||||
binding=False,
|
binding=False,
|
||||||
port_security=False):
|
port_security=False):
|
||||||
@ -484,6 +477,9 @@ class NetworkPortTests(test.BaseAdminViewTests):
|
|||||||
api.neutron.is_extension_supported(IsA(http.HttpRequest),
|
api.neutron.is_extension_supported(IsA(http.HttpRequest),
|
||||||
'port-security')\
|
'port-security')\
|
||||||
.MultipleTimes().AndReturn(port_security)
|
.MultipleTimes().AndReturn(port_security)
|
||||||
|
api.neutron.security_group_list(IsA(http.HttpRequest),
|
||||||
|
tenant_id=None)\
|
||||||
|
.AndReturn(self.security_groups.list())
|
||||||
extension_kwargs = {}
|
extension_kwargs = {}
|
||||||
if binding:
|
if binding:
|
||||||
extension_kwargs['binding__vnic_type'] = port.binding__vnic_type
|
extension_kwargs['binding__vnic_type'] = port.binding__vnic_type
|
||||||
|
@ -30,25 +30,20 @@ LOG = logging.getLogger(__name__)
|
|||||||
class UpdatePortInfoAction(project_workflow.UpdatePortInfoAction):
|
class UpdatePortInfoAction(project_workflow.UpdatePortInfoAction):
|
||||||
device_id = forms.CharField(
|
device_id = forms.CharField(
|
||||||
max_length=100, label=_("Device ID"),
|
max_length=100, label=_("Device ID"),
|
||||||
help_text=_("Device ID attached to the port"),
|
|
||||||
required=False)
|
required=False)
|
||||||
device_owner = forms.CharField(
|
device_owner = forms.CharField(
|
||||||
max_length=100, label=_("Device Owner"),
|
max_length=100, label=_("Device Owner"),
|
||||||
help_text=_("Device owner attached to the port"),
|
|
||||||
required=False)
|
required=False)
|
||||||
binding__host_id = forms.CharField(
|
binding__host_id = forms.CharField(
|
||||||
label=_("Binding: Host"),
|
label=_("Binding: Host"),
|
||||||
help_text=_("The ID of the host where the port is allocated. In some "
|
|
||||||
"cases, different implementations can run on different "
|
|
||||||
"hosts."),
|
|
||||||
required=False)
|
required=False)
|
||||||
mac_address = forms.MACAddressField(
|
mac_address = forms.MACAddressField(
|
||||||
label=_("MAC Address"),
|
label=_("MAC Address"),
|
||||||
required=False,
|
required=False)
|
||||||
help_text=_("MAC address for the port"))
|
|
||||||
|
|
||||||
class Meta(object):
|
class Meta(object):
|
||||||
name = _("Info")
|
name = _("Info")
|
||||||
|
help_text_template = 'admin/networks/ports/_edit_port_help.html'
|
||||||
|
|
||||||
|
|
||||||
class UpdatePortInfo(project_workflow.UpdatePortInfo):
|
class UpdatePortInfo(project_workflow.UpdatePortInfo):
|
||||||
@ -60,7 +55,7 @@ class UpdatePortInfo(project_workflow.UpdatePortInfo):
|
|||||||
|
|
||||||
|
|
||||||
class UpdatePort(project_workflow.UpdatePort):
|
class UpdatePort(project_workflow.UpdatePort):
|
||||||
default_steps = (UpdatePortInfo, )
|
default_steps = (UpdatePortInfo, project_workflow.UpdatePortSecurityGroup)
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return reverse("horizon:admin:networks:detail",
|
return reverse("horizon:admin:networks:detail",
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
{% extends 'project/networks/ports/_edit_port_help.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block admin_fields %}
|
||||||
|
<dt>{% trans "Device ID" %}</dt>
|
||||||
|
<dd>{% blocktrans trimmed %}Device ID attached to the port.
|
||||||
|
{% endblocktrans %}</dd>
|
||||||
|
<dt>{% trans "Device Owner" %}</dt>
|
||||||
|
<dd>{% blocktrans trimmed %}Device owner attached to the port.
|
||||||
|
{% endblocktrans %}</dd>
|
||||||
|
<dt>{% trans "Binding: Host" %}</dt>
|
||||||
|
<dd>{% blocktrans trimmed %}The ID of the host where the port is allocated.
|
||||||
|
In some cases, different implementations can run on different hosts.
|
||||||
|
{% endblocktrans %}</dd>
|
||||||
|
<dt>{% trans "MAC Address" %}</dt>
|
||||||
|
<dd>{% blocktrans trimmed %}MAC address for the port.
|
||||||
|
{% endblocktrans %}</dd>
|
||||||
|
{% endblock admin_fields %}
|
@ -1509,7 +1509,8 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
|
|||||||
server = self.servers.first()
|
server = self.servers.first()
|
||||||
|
|
||||||
api.nova.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
|
api.nova.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
|
||||||
api.neutron.security_group_list(IsA(http.HttpRequest)) \
|
api.neutron.security_group_list(IsA(http.HttpRequest),
|
||||||
|
tenant_id=None) \
|
||||||
.AndReturn([])
|
.AndReturn([])
|
||||||
api.neutron.server_security_groups(IsA(http.HttpRequest),
|
api.neutron.server_security_groups(IsA(http.HttpRequest),
|
||||||
server.id).AndReturn([])
|
server.id).AndReturn([])
|
||||||
@ -1561,7 +1562,8 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
|
|||||||
wanted_groups = [secgroups[1].id, secgroups[2].id]
|
wanted_groups = [secgroups[1].id, secgroups[2].id]
|
||||||
|
|
||||||
api.nova.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
|
api.nova.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
|
||||||
api.neutron.security_group_list(IsA(http.HttpRequest)) \
|
api.neutron.security_group_list(IsA(http.HttpRequest),
|
||||||
|
tenant_id=None) \
|
||||||
.AndReturn(secgroups)
|
.AndReturn(secgroups)
|
||||||
api.neutron.server_security_groups(IsA(http.HttpRequest),
|
api.neutron.server_security_groups(IsA(http.HttpRequest),
|
||||||
server.id).AndReturn(server_groups)
|
server.id).AndReturn(server_groups)
|
||||||
|
@ -31,15 +31,14 @@ ADD_USER_URL = "horizon:projects:instances:create_user"
|
|||||||
INSTANCE_SEC_GROUP_SLUG = "update_security_groups"
|
INSTANCE_SEC_GROUP_SLUG = "update_security_groups"
|
||||||
|
|
||||||
|
|
||||||
class UpdateInstanceSecurityGroupsAction(workflows.MembershipAction):
|
class BaseSecurityGroupsAction(workflows.MembershipAction):
|
||||||
def __init__(self, request, *args, **kwargs):
|
def __init__(self, request, *args, **kwargs):
|
||||||
super(UpdateInstanceSecurityGroupsAction, self).__init__(request,
|
super(BaseSecurityGroupsAction, self).__init__(request,
|
||||||
*args,
|
*args,
|
||||||
**kwargs)
|
**kwargs)
|
||||||
err_msg = _('Unable to retrieve security group list. '
|
err_msg = _('Unable to retrieve security group list. '
|
||||||
'Please try again later.')
|
'Please try again later.')
|
||||||
context = args[0]
|
context = args[0]
|
||||||
instance_id = context.get('instance_id', '')
|
|
||||||
|
|
||||||
default_role_name = self.get_default_role_field_name()
|
default_role_name = self.get_default_role_field_name()
|
||||||
self.fields[default_role_name] = forms.CharField(required=False)
|
self.fields[default_role_name] = forms.CharField(required=False)
|
||||||
@ -48,22 +47,55 @@ class UpdateInstanceSecurityGroupsAction(workflows.MembershipAction):
|
|||||||
# Get list of available security groups
|
# Get list of available security groups
|
||||||
all_groups = []
|
all_groups = []
|
||||||
try:
|
try:
|
||||||
all_groups = api.neutron.security_group_list(request)
|
# target_tenant_id is required when the form is used as admin.
|
||||||
|
# Owner of security group and port should match.
|
||||||
|
tenant_id = context.get('target_tenant_id')
|
||||||
|
all_groups = api.neutron.security_group_list(request,
|
||||||
|
tenant_id=tenant_id)
|
||||||
except Exception:
|
except Exception:
|
||||||
exceptions.handle(request, err_msg)
|
exceptions.handle(request, err_msg)
|
||||||
groups_list = [(group.id, group.name) for group in all_groups]
|
groups_list = [(group.id, group.name) for group in all_groups]
|
||||||
|
|
||||||
instance_groups = []
|
|
||||||
try:
|
|
||||||
instance_groups = api.neutron.server_security_groups(request,
|
|
||||||
instance_id)
|
|
||||||
except Exception:
|
|
||||||
exceptions.handle(request, err_msg)
|
|
||||||
field_name = self.get_member_field_name('member')
|
field_name = self.get_member_field_name('member')
|
||||||
self.fields[field_name] = forms.MultipleChoiceField(required=False)
|
self.fields[field_name] = forms.MultipleChoiceField(required=False)
|
||||||
self.fields[field_name].choices = groups_list
|
self.fields[field_name].choices = groups_list
|
||||||
self.fields[field_name].initial = [group.id
|
sec_groups = []
|
||||||
for group in instance_groups]
|
try:
|
||||||
|
sec_groups = self._get_initial_security_groups(context)
|
||||||
|
except Exception:
|
||||||
|
exceptions.handle(request, err_msg)
|
||||||
|
self.fields[field_name].initial = sec_groups
|
||||||
|
|
||||||
|
def _get_initial_security_groups(self, context):
|
||||||
|
# This depends on each cases
|
||||||
|
pass
|
||||||
|
|
||||||
|
def handle(self, request, data):
|
||||||
|
# This depends on each cases
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BaseSecurityGroups(workflows.UpdateMembersStep):
|
||||||
|
available_list_title = _("All Security Groups")
|
||||||
|
no_available_text = _("No security groups found.")
|
||||||
|
no_members_text = _("No security groups enabled.")
|
||||||
|
show_roles = False
|
||||||
|
contributes = ("wanted_groups",)
|
||||||
|
|
||||||
|
def contribute(self, data, context):
|
||||||
|
request = self.workflow.request
|
||||||
|
if data:
|
||||||
|
field_name = self.get_member_field_name('member')
|
||||||
|
context["wanted_groups"] = request.POST.getlist(field_name)
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateInstanceSecurityGroupsAction(BaseSecurityGroupsAction):
|
||||||
|
def _get_initial_security_groups(self, context):
|
||||||
|
instance_id = context.get('instance_id', '')
|
||||||
|
sec_groups = api.neutron.server_security_groups(self.request,
|
||||||
|
instance_id)
|
||||||
|
return [group.id for group in sec_groups]
|
||||||
|
|
||||||
def handle(self, request, data):
|
def handle(self, request, data):
|
||||||
instance_id = data['instance_id']
|
instance_id = data['instance_id']
|
||||||
@ -81,28 +113,16 @@ class UpdateInstanceSecurityGroupsAction(workflows.MembershipAction):
|
|||||||
slug = INSTANCE_SEC_GROUP_SLUG
|
slug = INSTANCE_SEC_GROUP_SLUG
|
||||||
|
|
||||||
|
|
||||||
class UpdateInstanceSecurityGroups(workflows.UpdateMembersStep):
|
class UpdateInstanceSecurityGroups(BaseSecurityGroups):
|
||||||
action_class = UpdateInstanceSecurityGroupsAction
|
action_class = UpdateInstanceSecurityGroupsAction
|
||||||
|
members_list_title = _("Instance Security Groups")
|
||||||
help_text = _("Add and remove security groups to this instance "
|
help_text = _("Add and remove security groups to this instance "
|
||||||
"from the list of available security groups.")
|
"from the list of available security groups.")
|
||||||
available_list_title = _("All Security Groups")
|
|
||||||
members_list_title = _("Instance Security Groups")
|
|
||||||
no_available_text = _("No security groups found.")
|
|
||||||
no_members_text = _("No security groups enabled.")
|
|
||||||
show_roles = False
|
|
||||||
depends_on = ("instance_id",)
|
depends_on = ("instance_id",)
|
||||||
contributes = ("wanted_groups",)
|
|
||||||
|
|
||||||
def allowed(self, request):
|
def allowed(self, request):
|
||||||
return api.base.is_service_enabled(request, 'network')
|
return api.base.is_service_enabled(request, 'network')
|
||||||
|
|
||||||
def contribute(self, data, context):
|
|
||||||
request = self.workflow.request
|
|
||||||
if data:
|
|
||||||
field_name = self.get_member_field_name('member')
|
|
||||||
context["wanted_groups"] = request.POST.getlist(field_name)
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
class UpdateInstanceInfoAction(workflows.Action):
|
class UpdateInstanceInfoAction(workflows.Action):
|
||||||
name = forms.CharField(label=_("Name"),
|
name = forms.CharField(label=_("Name"),
|
||||||
|
@ -16,6 +16,7 @@ import logging
|
|||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django import template
|
from django import template
|
||||||
|
from django.utils.http import urlencode
|
||||||
from django.utils.translation import pgettext_lazy
|
from django.utils.translation import pgettext_lazy
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.utils.translation import ungettext_lazy
|
from django.utils.translation import ungettext_lazy
|
||||||
@ -55,7 +56,10 @@ class UpdatePort(policy.PolicyTargetMixin, tables.LinkAction):
|
|||||||
|
|
||||||
def get_link_url(self, port):
|
def get_link_url(self, port):
|
||||||
network_id = self.table.kwargs['network_id']
|
network_id = self.table.kwargs['network_id']
|
||||||
return reverse(self.url, args=(network_id, port.id))
|
base_url = reverse(self.url, args=(network_id, port.id))
|
||||||
|
params = {'step': 'update_info'}
|
||||||
|
param = urlencode(params)
|
||||||
|
return '?'.join([base_url, param])
|
||||||
|
|
||||||
|
|
||||||
DISPLAY_CHOICES = (
|
DISPLAY_CHOICES = (
|
||||||
|
@ -81,16 +81,15 @@ class NetworkPortTests(test.TestCase):
|
|||||||
|
|
||||||
self.assertRedirectsNoFollow(res, NETWORKS_INDEX_URL)
|
self.assertRedirectsNoFollow(res, NETWORKS_INDEX_URL)
|
||||||
|
|
||||||
@test.create_stubs({api.neutron: ('port_get',
|
|
||||||
'is_extension_supported',)})
|
|
||||||
def test_port_update_get(self):
|
def test_port_update_get(self):
|
||||||
self._test_port_update_get()
|
self._test_port_update_get()
|
||||||
|
|
||||||
@test.create_stubs({api.neutron: ('port_get',
|
|
||||||
'is_extension_supported',)})
|
|
||||||
def test_port_update_get_with_mac_learning(self):
|
def test_port_update_get_with_mac_learning(self):
|
||||||
self._test_port_update_get(mac_learning=True)
|
self._test_port_update_get(mac_learning=True)
|
||||||
|
|
||||||
|
@test.create_stubs({api.neutron: ('port_get',
|
||||||
|
'security_group_list',
|
||||||
|
'is_extension_supported',)})
|
||||||
def _test_port_update_get(self, mac_learning=False, binding=False):
|
def _test_port_update_get(self, mac_learning=False, binding=False):
|
||||||
port = self.ports.first()
|
port = self.ports.first()
|
||||||
api.neutron.port_get(IsA(http.HttpRequest), port.id) \
|
api.neutron.port_get(IsA(http.HttpRequest), port.id) \
|
||||||
@ -101,6 +100,9 @@ class NetworkPortTests(test.TestCase):
|
|||||||
api.neutron.is_extension_supported(IsA(http.HttpRequest),
|
api.neutron.is_extension_supported(IsA(http.HttpRequest),
|
||||||
'mac-learning')\
|
'mac-learning')\
|
||||||
.MultipleTimes().AndReturn(mac_learning)
|
.MultipleTimes().AndReturn(mac_learning)
|
||||||
|
api.neutron.security_group_list(IsA(http.HttpRequest),
|
||||||
|
tenant_id=None)\
|
||||||
|
.AndReturn(self.security_groups.list())
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
url = reverse('horizon:project:networks:editport',
|
url = reverse('horizon:project:networks:editport',
|
||||||
@ -109,27 +111,23 @@ class NetworkPortTests(test.TestCase):
|
|||||||
|
|
||||||
self.assertTemplateUsed(res, views.WorkflowView.template_name)
|
self.assertTemplateUsed(res, views.WorkflowView.template_name)
|
||||||
|
|
||||||
@test.create_stubs({api.neutron: ('port_get',
|
|
||||||
'is_extension_supported',
|
|
||||||
'port_update')})
|
|
||||||
def test_port_update_post(self):
|
def test_port_update_post(self):
|
||||||
self._test_port_update_post()
|
self._test_port_update_post()
|
||||||
|
|
||||||
@test.create_stubs({api.neutron: ('port_get',
|
|
||||||
'is_extension_supported',
|
|
||||||
'port_update')})
|
|
||||||
def test_port_update_post_with_mac_learning(self):
|
def test_port_update_post_with_mac_learning(self):
|
||||||
self._test_port_update_post(mac_learning=True)
|
self._test_port_update_post(mac_learning=True)
|
||||||
|
|
||||||
@test.create_stubs({api.neutron: ('port_get',
|
|
||||||
'is_extension_supported',
|
|
||||||
'port_update')})
|
|
||||||
def test_port_update_post_with_port_security(self):
|
def test_port_update_post_with_port_security(self):
|
||||||
self._test_port_update_post(port_security=True)
|
self._test_port_update_post(port_security=True)
|
||||||
|
|
||||||
|
@test.create_stubs({api.neutron: ('port_get',
|
||||||
|
'is_extension_supported',
|
||||||
|
'security_group_list',
|
||||||
|
'port_update')})
|
||||||
def _test_port_update_post(self, mac_learning=False, binding=False,
|
def _test_port_update_post(self, mac_learning=False, binding=False,
|
||||||
port_security=False):
|
port_security=False):
|
||||||
port = self.ports.first()
|
port = self.ports.first()
|
||||||
|
security_groups = self.security_groups.list()
|
||||||
api.neutron.port_get(IsA(http.HttpRequest), port.id)\
|
api.neutron.port_get(IsA(http.HttpRequest), port.id)\
|
||||||
.AndReturn(port)
|
.AndReturn(port)
|
||||||
api.neutron.is_extension_supported(IsA(http.HttpRequest),
|
api.neutron.is_extension_supported(IsA(http.HttpRequest),
|
||||||
@ -141,6 +139,9 @@ class NetworkPortTests(test.TestCase):
|
|||||||
api.neutron.is_extension_supported(IsA(http.HttpRequest),
|
api.neutron.is_extension_supported(IsA(http.HttpRequest),
|
||||||
'port-security')\
|
'port-security')\
|
||||||
.MultipleTimes().AndReturn(port_security)
|
.MultipleTimes().AndReturn(port_security)
|
||||||
|
api.neutron.security_group_list(IsA(http.HttpRequest),
|
||||||
|
tenant_id=None)\
|
||||||
|
.AndReturn(self.security_groups.list())
|
||||||
extension_kwargs = {}
|
extension_kwargs = {}
|
||||||
if binding:
|
if binding:
|
||||||
extension_kwargs['binding__vnic_type'] = port.binding__vnic_type
|
extension_kwargs['binding__vnic_type'] = port.binding__vnic_type
|
||||||
@ -148,6 +149,7 @@ class NetworkPortTests(test.TestCase):
|
|||||||
extension_kwargs['mac_learning_enabled'] = True
|
extension_kwargs['mac_learning_enabled'] = True
|
||||||
if port_security:
|
if port_security:
|
||||||
extension_kwargs['port_security_enabled'] = True
|
extension_kwargs['port_security_enabled'] = True
|
||||||
|
extension_kwargs['wanted_groups'] = security_groups
|
||||||
api.neutron.port_update(IsA(http.HttpRequest), port.id,
|
api.neutron.port_update(IsA(http.HttpRequest), port.id,
|
||||||
name=port.name,
|
name=port.name,
|
||||||
admin_state_up=port.admin_state_up,
|
admin_state_up=port.admin_state_up,
|
||||||
@ -165,6 +167,7 @@ class NetworkPortTests(test.TestCase):
|
|||||||
form_data['mac_state'] = True
|
form_data['mac_state'] = True
|
||||||
if port_security:
|
if port_security:
|
||||||
form_data['port_security_enabled'] = True
|
form_data['port_security_enabled'] = True
|
||||||
|
form_data['wanted_groups'] = security_groups
|
||||||
url = reverse('horizon:project:networks:editport',
|
url = reverse('horizon:project:networks:editport',
|
||||||
args=[port.network_id, port.id])
|
args=[port.network_id, port.id])
|
||||||
res = self.client.post(url, form_data)
|
res = self.client.post(url, form_data)
|
||||||
@ -172,24 +175,19 @@ class NetworkPortTests(test.TestCase):
|
|||||||
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: ('port_get',
|
|
||||||
'is_extension_supported',
|
|
||||||
'port_update')})
|
|
||||||
def test_port_update_post_exception(self):
|
def test_port_update_post_exception(self):
|
||||||
self._test_port_update_post_exception()
|
self._test_port_update_post_exception()
|
||||||
|
|
||||||
@test.create_stubs({api.neutron: ('port_get',
|
|
||||||
'is_extension_supported',
|
|
||||||
'port_update')})
|
|
||||||
def test_port_update_post_exception_with_mac_learning(self):
|
def test_port_update_post_exception_with_mac_learning(self):
|
||||||
self._test_port_update_post_exception(mac_learning=True)
|
self._test_port_update_post_exception(mac_learning=True)
|
||||||
|
|
||||||
@test.create_stubs({api.neutron: ('port_get',
|
|
||||||
'is_extension_supported',
|
|
||||||
'port_update')})
|
|
||||||
def test_port_update_post_exception_with_port_security(self):
|
def test_port_update_post_exception_with_port_security(self):
|
||||||
self._test_port_update_post_exception(port_security=True)
|
self._test_port_update_post_exception(port_security=True)
|
||||||
|
|
||||||
|
@test.create_stubs({api.neutron: ('port_get',
|
||||||
|
'is_extension_supported',
|
||||||
|
'security_group_list',
|
||||||
|
'port_update')})
|
||||||
def _test_port_update_post_exception(self, mac_learning=False,
|
def _test_port_update_post_exception(self, mac_learning=False,
|
||||||
binding=False,
|
binding=False,
|
||||||
port_security=False):
|
port_security=False):
|
||||||
@ -206,6 +204,9 @@ class NetworkPortTests(test.TestCase):
|
|||||||
api.neutron.is_extension_supported(IsA(http.HttpRequest),
|
api.neutron.is_extension_supported(IsA(http.HttpRequest),
|
||||||
'port-security')\
|
'port-security')\
|
||||||
.MultipleTimes().AndReturn(port_security)
|
.MultipleTimes().AndReturn(port_security)
|
||||||
|
api.neutron.security_group_list(IsA(http.HttpRequest),
|
||||||
|
tenant_id=None)\
|
||||||
|
.AndReturn(self.security_groups.list())
|
||||||
extension_kwargs = {}
|
extension_kwargs = {}
|
||||||
if binding:
|
if binding:
|
||||||
extension_kwargs['binding__vnic_type'] = port.binding__vnic_type
|
extension_kwargs['binding__vnic_type'] = port.binding__vnic_type
|
||||||
|
@ -188,7 +188,8 @@ class UpdateView(workflows.WorkflowView):
|
|||||||
'tenant_id': port['tenant_id'],
|
'tenant_id': port['tenant_id'],
|
||||||
'name': port['name'],
|
'name': port['name'],
|
||||||
'admin_state': port['admin_state_up'],
|
'admin_state': port['admin_state_up'],
|
||||||
'mac_address': port['mac_address']}
|
'mac_address': port['mac_address'],
|
||||||
|
'target_tenant_id': port['tenant_id']}
|
||||||
if port.get('binding__vnic_type'):
|
if port.get('binding__vnic_type'):
|
||||||
initial['binding__vnic_type'] = port['binding__vnic_type']
|
initial['binding__vnic_type'] = port['binding__vnic_type']
|
||||||
try:
|
try:
|
||||||
|
@ -24,6 +24,9 @@ from horizon import forms
|
|||||||
from horizon import workflows
|
from horizon import workflows
|
||||||
|
|
||||||
from openstack_dashboard import api
|
from openstack_dashboard import api
|
||||||
|
from openstack_dashboard.dashboards.project.instances.workflows import \
|
||||||
|
update_instance as base_sec_group
|
||||||
|
from openstack_dashboard.utils import filters
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@ -55,8 +58,6 @@ class UpdatePortInfoAction(workflows.Action):
|
|||||||
self.fields['binding__vnic_type'] = forms.ChoiceField(
|
self.fields['binding__vnic_type'] = forms.ChoiceField(
|
||||||
choices=vnic_type_choices,
|
choices=vnic_type_choices,
|
||||||
label=_("Binding: VNIC Type"),
|
label=_("Binding: VNIC Type"),
|
||||||
help_text=_(
|
|
||||||
"The VNIC type that is bound to the neutron port"),
|
|
||||||
required=False)
|
required=False)
|
||||||
except Exception:
|
except Exception:
|
||||||
msg = _("Unable to verify the VNIC types extension in Neutron")
|
msg = _("Unable to verify the VNIC types extension in Neutron")
|
||||||
@ -75,8 +76,13 @@ class UpdatePortInfoAction(workflows.Action):
|
|||||||
if api.neutron.is_extension_supported(request, 'port-security'):
|
if api.neutron.is_extension_supported(request, 'port-security'):
|
||||||
self.fields['port_security_enabled'] = forms.BooleanField(
|
self.fields['port_security_enabled'] = forms.BooleanField(
|
||||||
label=_("Port Security"),
|
label=_("Port Security"),
|
||||||
help_text=_("Enable anti-spoofing rules for the port"),
|
required=False,
|
||||||
required=False
|
widget=forms.CheckboxInput(attrs={
|
||||||
|
'class': 'switchable',
|
||||||
|
'data-slug': 'port_security_enabled',
|
||||||
|
'data-hide-tab': 'update_port__update_security_groups',
|
||||||
|
'data-hide-on-checked': 'false'
|
||||||
|
})
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
msg = _("Unable to retrieve port security state")
|
msg = _("Unable to retrieve port security state")
|
||||||
@ -84,6 +90,8 @@ class UpdatePortInfoAction(workflows.Action):
|
|||||||
|
|
||||||
class Meta(object):
|
class Meta(object):
|
||||||
name = _("Info")
|
name = _("Info")
|
||||||
|
slug = 'update_info'
|
||||||
|
help_text_template = 'project/networks/ports/_edit_port_help.html'
|
||||||
|
|
||||||
|
|
||||||
class UpdatePortInfo(workflows.Step):
|
class UpdatePortInfo(workflows.Step):
|
||||||
@ -91,7 +99,25 @@ class UpdatePortInfo(workflows.Step):
|
|||||||
depends_on = ("network_id", "port_id")
|
depends_on = ("network_id", "port_id")
|
||||||
contributes = ["name", "admin_state",
|
contributes = ["name", "admin_state",
|
||||||
"binding__vnic_type", "mac_state", "port_security_enabled"]
|
"binding__vnic_type", "mac_state", "port_security_enabled"]
|
||||||
help_text = _("You can update the editable properties of your port here.")
|
|
||||||
|
|
||||||
|
class UpdatePortSecurityGroupAction(base_sec_group.BaseSecurityGroupsAction):
|
||||||
|
def _get_initial_security_groups(self, context):
|
||||||
|
port_id = context.get('port_id', '')
|
||||||
|
port = api.neutron.port_get(self.request, port_id)
|
||||||
|
return port.security_groups
|
||||||
|
|
||||||
|
class Meta(object):
|
||||||
|
name = _("Security Groups")
|
||||||
|
slug = "update_security_groups"
|
||||||
|
|
||||||
|
|
||||||
|
class UpdatePortSecurityGroup(base_sec_group.BaseSecurityGroups):
|
||||||
|
action_class = UpdatePortSecurityGroupAction
|
||||||
|
members_list_title = _("Port Security Groups")
|
||||||
|
help_text = _("Add or remove security groups to this port "
|
||||||
|
"from the list of available security groups.")
|
||||||
|
depends_on = ("port_id", 'target_tenant_id')
|
||||||
|
|
||||||
|
|
||||||
class UpdatePort(workflows.Workflow):
|
class UpdatePort(workflows.Workflow):
|
||||||
@ -100,7 +126,7 @@ class UpdatePort(workflows.Workflow):
|
|||||||
finalize_button_name = _("Update")
|
finalize_button_name = _("Update")
|
||||||
success_message = _('Port %s was successfully updated.')
|
success_message = _('Port %s was successfully updated.')
|
||||||
failure_message = _('Failed to update port "%s".')
|
failure_message = _('Failed to update port "%s".')
|
||||||
default_steps = (UpdatePortInfo,)
|
default_steps = (UpdatePortInfo, UpdatePortSecurityGroup)
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return reverse("horizon:project:networks:detail",
|
return reverse("horizon:project:networks:detail",
|
||||||
@ -136,4 +162,18 @@ class UpdatePort(workflows.Workflow):
|
|||||||
if data['port_security_enabled'] is not None:
|
if data['port_security_enabled'] is not None:
|
||||||
params['port_security_enabled'] = data['port_security_enabled']
|
params['port_security_enabled'] = data['port_security_enabled']
|
||||||
|
|
||||||
|
# If port_security_enabled is set to False, security groups on the port
|
||||||
|
# must be cleared. We will clear the current security groups
|
||||||
|
# in this case.
|
||||||
|
if ('port_security_enabled' in params
|
||||||
|
and not params['port_security_enabled']):
|
||||||
|
params['security_groups'] = []
|
||||||
|
# In case of UpdatePortSecurityGroup registered, 'wanted_groups'
|
||||||
|
# exists in data.
|
||||||
|
elif 'wanted_groups' in data:
|
||||||
|
# If data has that key, we need to set its value
|
||||||
|
# even if its value is empty to clear sec group setting.
|
||||||
|
groups = map(filters.get_int_or_uuid, data['wanted_groups'])
|
||||||
|
params['security_groups'] = groups
|
||||||
|
|
||||||
return params
|
return params
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
<p>{% trans "You can edit the properties of your port here." %}</p>
|
||||||
|
<dl>
|
||||||
|
<dt>{% trans "Enable Admin State" %}</dt>
|
||||||
|
<dd>{% blocktrans trimmed %}When the admin state of the port is enabled, the
|
||||||
|
networking service forward packets on the port. Otherwise, it does not
|
||||||
|
forward any packets on the port.{% endblocktrans %}</dd>
|
||||||
|
{% block admin_fields %}{% endblock %}
|
||||||
|
<dt>{% trans "Binding: VNIC Type" %}</dt>
|
||||||
|
<dd>{% blocktrans trimmed %}It specified the VNIC type bound to the
|
||||||
|
networking port.{% endblocktrans %}</dd>
|
||||||
|
<dt>{% trans "Port Security" %}</dt>
|
||||||
|
<dd>{% blocktrans trimmed %}
|
||||||
|
Enables anti-spoofing rules for the port if enabled. In addition,
|
||||||
|
if port security is disabled, security groups
|
||||||
|
on the port will be automatically cleared. When you enable port security
|
||||||
|
of the port, you may want to associate some security groups on
|
||||||
|
the port.{% endblocktrans %}</dd>
|
||||||
|
<dt>{% trans "Security Groups" %}</dt>
|
||||||
|
<dd>{% blocktrans trimmed %}You can add or remove security groups
|
||||||
|
associated with the port in the next tab (if the port security is
|
||||||
|
enabled for the port).
|
||||||
|
{% endblocktrans %}</dd>
|
||||||
|
</dl>
|
@ -140,6 +140,7 @@ def data(TEST):
|
|||||||
{'ip_address': '174.0.0.201',
|
{'ip_address': '174.0.0.201',
|
||||||
'mac_address': 'fa:16:3e:7a:7b:18'}
|
'mac_address': 'fa:16:3e:7a:7b:18'}
|
||||||
],
|
],
|
||||||
|
'port_security_enabled': True,
|
||||||
'security_groups': [],
|
'security_groups': [],
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,6 +163,7 @@ def data(TEST):
|
|||||||
'tenant_id': network_dict['tenant_id'],
|
'tenant_id': network_dict['tenant_id'],
|
||||||
'binding:vnic_type': 'normal',
|
'binding:vnic_type': 'normal',
|
||||||
'binding:host_id': 'host',
|
'binding:host_id': 'host',
|
||||||
|
'port_security_enabled': True,
|
||||||
'security_groups': [
|
'security_groups': [
|
||||||
# sec_group_1 ID below
|
# sec_group_1 ID below
|
||||||
'faad7c80-3b62-4440-967c-13808c37131d',
|
'faad7c80-3b62-4440-967c-13808c37131d',
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Support security groups association per network port for operators
|
||||||
|
and users. Note that the current implementation only supports to edit
|
||||||
|
security groups of neutron port from the port tables in the network
|
||||||
|
detail page (Further improvement is planned).
|
Loading…
Reference in New Issue
Block a user