diff --git a/openstack_dashboard/api/neutron.py b/openstack_dashboard/api/neutron.py
index 22efc2d4c1..bd84ab8d34 100644
--- a/openstack_dashboard/api/neutron.py
+++ b/openstack_dashboard/api/neutron.py
@@ -289,13 +289,13 @@ class SecurityGroupManager(object):
return [SecurityGroup(sg) for sg in secgroups.get('security_groups')]
@profiler.trace
- def list(self):
+ def list(self, **params):
"""Fetches a list all security groups.
:returns: List of SecurityGroup objects
"""
- tenant_id = self.request.user.tenant_id
- return self._list(tenant_id=tenant_id)
+ tenant_id = params.pop('tenant_id', self.request.user.tenant_id)
+ return self._list(tenant_id=tenant_id, **params)
def _sg_name_dict(self, sg_id, rules):
"""Create a mapping dict from secgroup id to its name."""
@@ -1310,8 +1310,8 @@ def floating_ip_supported(request):
@memoized
-def security_group_list(request):
- return SecurityGroupManager(request).list()
+def security_group_list(request, **params):
+ return SecurityGroupManager(request).list(**params)
def security_group_get(request, sg_id):
diff --git a/openstack_dashboard/dashboards/project/networks/ports/tests.py b/openstack_dashboard/dashboards/project/networks/ports/tests.py
index 40ce588126..237450bf8c 100644
--- a/openstack_dashboard/dashboards/project/networks/ports/tests.py
+++ b/openstack_dashboard/dashboards/project/networks/ports/tests.py
@@ -31,23 +31,27 @@ NETWORKS_DETAIL_URL = 'horizon:project:networks:detail'
class NetworkPortTests(test.TestCase):
- @test.create_stubs({api.neutron: ('network_get',
- 'port_get',
- 'is_extension_supported',)})
def test_port_detail(self):
self._test_port_detail()
- @test.create_stubs({api.neutron: ('network_get',
- 'port_get',
- 'is_extension_supported',)})
def test_port_detail_with_mac_learning(self):
self._test_port_detail(mac_learning=True)
+ @test.create_stubs({api.neutron: ('network_get',
+ 'port_get',
+ 'is_extension_supported',
+ 'security_group_list',)})
def _test_port_detail(self, mac_learning=False):
- port = self.ports.first()
+ # Use a port associated with security group
+ port = [p for p in self.ports.list() if p.security_groups][0]
+ sgs = [sg for sg in self.security_groups.list()
+ if sg.id in port.security_groups]
network_id = self.networks.first().id
api.neutron.port_get(IsA(http.HttpRequest), port.id)\
- .AndReturn(self.ports.first())
+ .AndReturn(port)
+ api.neutron.security_group_list(IsA(http.HttpRequest),
+ ids=port.security_groups)\
+ .AndReturn(sgs)
api.neutron.is_extension_supported(IsA(http.HttpRequest),
'mac-learning')\
.MultipleTimes().AndReturn(mac_learning)
diff --git a/openstack_dashboard/dashboards/project/networks/ports/views.py b/openstack_dashboard/dashboards/project/networks/ports/views.py
index c2c9a3eb27..2ed4559853 100644
--- a/openstack_dashboard/dashboards/project/networks/ports/views.py
+++ b/openstack_dashboard/dashboards/project/networks/ports/views.py
@@ -114,6 +114,20 @@ class DetailView(tabs.TabbedTableView):
return network
+ @memoized.memoized_method
+ def get_security_groups(self, sg_ids):
+ # Avoid extra API calls if no security group is associated.
+ if not sg_ids:
+ return []
+ try:
+ security_groups = api.neutron.security_group_list(self.request,
+ id=sg_ids)
+ except Exception:
+ security_groups = []
+ msg = _("Unable to retrieve security groups for the port.")
+ exceptions.handle(self.request, msg)
+ return security_groups
+
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
port = self.get_data()
@@ -124,6 +138,7 @@ class DetailView(tabs.TabbedTableView):
port.network_url = reverse(network_url, args=[port.network_id])
for ip in port.fixed_ips:
ip['subnet_url'] = reverse(subnet_url, args=[ip['subnet_id']])
+ port.security_groups = self.get_security_groups(port.security_groups)
table = project_tables.PortsTable(self.request,
network_id=port.network_id)
# TODO(robcresswell) Add URL for "Ports" crumb after bug/1416838
diff --git a/openstack_dashboard/dashboards/project/networks/templates/networks/ports/_detail_overview.html b/openstack_dashboard/dashboards/project/networks/templates/networks/ports/_detail_overview.html
index 87b716d4b9..80fe81c241 100644
--- a/openstack_dashboard/dashboards/project/networks/templates/networks/ports/_detail_overview.html
+++ b/openstack_dashboard/dashboards/project/networks/templates/networks/ports/_detail_overview.html
@@ -83,6 +83,23 @@
{% endif %}
+
{% trans "Security Groups" %}
+
+
+ {% for group in port.security_groups %}
+ - {{ group.name }}
+ -
+
+ {% for rule in group.rules %}
+ - {{ rule }}
+ {% endfor %}
+
+
+ {% empty %}
+ - {% trans "No security group is associated" %}
+ {% endfor %}
+
+
{% trans "Binding" %}
diff --git a/openstack_dashboard/test/api_tests/neutron_tests.py b/openstack_dashboard/test/api_tests/neutron_tests.py
index 2854f5d0c2..28d96b7cfc 100644
--- a/openstack_dashboard/test/api_tests/neutron_tests.py
+++ b/openstack_dashboard/test/api_tests/neutron_tests.py
@@ -767,19 +767,30 @@ class NeutronApiSecurityGroupTests(NeutronApiTestBase):
for (exprule, retrule) in six.moves.zip(exp_rules, ret_sg.rules):
self._cmp_sg_rule(exprule, retrule)
- def test_security_group_list(self):
+ def _test_security_group_list(self, **params):
sgs = self.api_security_groups.list()
- tenant_id = self.request.user.tenant_id
+ q_params = {'tenant_id': self.request.user.tenant_id}
+ # if tenant_id is specified, the passed tenant_id should be sent.
+ q_params.update(params)
# use deepcopy to ensure self.api_security_groups is not modified.
- self.qclient.list_security_groups(tenant_id=tenant_id) \
+ self.qclient.list_security_groups(**q_params) \
.AndReturn({'security_groups': copy.deepcopy(sgs)})
self.mox.ReplayAll()
- rets = api.neutron.security_group_list(self.request)
+ rets = api.neutron.security_group_list(self.request, **params)
self.assertEqual(len(sgs), len(rets))
for (exp, ret) in six.moves.zip(sgs, rets):
self._cmp_sg(exp, ret)
+ def test_security_group_list(self):
+ self._test_security_group_list()
+
+ def test_security_group_list_with_params(self):
+ self._test_security_group_list(name='sg1')
+
+ def test_security_group_list_with_tenant_id(self):
+ self._test_security_group_list(tenant_id='tenant1', name='sg1')
+
def test_security_group_get(self):
secgroup = self.api_security_groups.first()
sg_ids = set([secgroup['id']] +
diff --git a/openstack_dashboard/test/test_data/neutron_data.py b/openstack_dashboard/test/test_data/neutron_data.py
index cc5338f9d4..9edf8f36e8 100644
--- a/openstack_dashboard/test/test_data/neutron_data.py
+++ b/openstack_dashboard/test/test_data/neutron_data.py
@@ -114,56 +114,71 @@ def data(TEST):
TEST.subnets.add(subnet)
# Ports on 1st network.
- port_dict = {'admin_state_up': True,
- 'device_id': 'af75c8e5-a1cc-4567-8d04-44fcd6922890',
- 'device_owner': 'network:dhcp',
- 'fixed_ips': [{'ip_address': '10.0.0.3',
- 'subnet_id': subnet_dict['id']}],
- 'id': '063cf7f3-ded1-4297-bc4c-31eae876cc91',
- 'mac_address': 'fa:16:3e:9c:d5:7e',
- 'name': '',
- 'network_id': network_dict['id'],
- 'status': 'ACTIVE',
- 'tenant_id': network_dict['tenant_id'],
- 'binding:vnic_type': 'normal',
- 'binding:host_id': 'host',
- 'allowed_address_pairs': [{'ip_address': '174.0.0.201',
- 'mac_address': 'fa:16:3e:7a:7b:18'}]
- }
+ port_dict = {
+ 'admin_state_up': True,
+ 'device_id': 'af75c8e5-a1cc-4567-8d04-44fcd6922890',
+ 'device_owner': 'network:dhcp',
+ 'fixed_ips': [{'ip_address': '10.0.0.3',
+ 'subnet_id': subnet_dict['id']}],
+ 'id': '063cf7f3-ded1-4297-bc4c-31eae876cc91',
+ 'mac_address': 'fa:16:3e:9c:d5:7e',
+ 'name': '',
+ 'network_id': network_dict['id'],
+ 'status': 'ACTIVE',
+ 'tenant_id': network_dict['tenant_id'],
+ 'binding:vnic_type': 'normal',
+ 'binding:host_id': 'host',
+ 'allowed_address_pairs': [
+ {'ip_address': '174.0.0.201',
+ 'mac_address': 'fa:16:3e:7a:7b:18'}
+ ],
+ 'security_groups': [],
+ }
TEST.api_ports.add(port_dict)
TEST.ports.add(neutron.Port(port_dict))
- port_dict = {'admin_state_up': True,
- 'device_id': '1',
- 'device_owner': 'compute:nova',
- 'fixed_ips': [{'ip_address': '10.0.0.4',
- 'subnet_id': subnet_dict['id']}],
- 'id': '7e6ce62c-7ea2-44f8-b6b4-769af90a8406',
- 'mac_address': 'fa:16:3e:9d:e6:2f',
- 'name': '',
- 'network_id': network_dict['id'],
- 'status': 'ACTIVE',
- 'tenant_id': network_dict['tenant_id'],
- 'binding:vnic_type': 'normal',
- 'binding:host_id': 'host'}
+ port_dict = {
+ 'admin_state_up': True,
+ 'device_id': '1',
+ 'device_owner': 'compute:nova',
+ 'fixed_ips': [{'ip_address': '10.0.0.4',
+ 'subnet_id': subnet_dict['id']}],
+ 'id': '7e6ce62c-7ea2-44f8-b6b4-769af90a8406',
+ 'mac_address': 'fa:16:3e:9d:e6:2f',
+ 'name': '',
+ 'network_id': network_dict['id'],
+ 'status': 'ACTIVE',
+ 'tenant_id': network_dict['tenant_id'],
+ 'binding:vnic_type': 'normal',
+ 'binding:host_id': 'host',
+ 'security_groups': [
+ # sec_group_1 ID below
+ 'faad7c80-3b62-4440-967c-13808c37131d',
+ # sec_group_2 ID below
+ '27a5c9a1-bdbb-48ac-833a-2e4b5f54b31d'
+ ],
+ }
TEST.api_ports.add(port_dict)
TEST.ports.add(neutron.Port(port_dict))
assoc_port = port_dict
- port_dict = {'admin_state_up': True,
- 'device_id': '279989f7-54bb-41d9-ba42-0d61f12fda61',
- 'device_owner': 'network:router_interface',
- 'fixed_ips': [{'ip_address': '10.0.0.1',
- 'subnet_id': subnet_dict['id']}],
- 'id': '9036eedb-e7fa-458e-bc6e-d9d06d9d1bc4',
- 'mac_address': 'fa:16:3e:9c:d5:7f',
- 'name': '',
- 'network_id': network_dict['id'],
- 'status': 'ACTIVE',
- 'tenant_id': network_dict['tenant_id'],
- 'binding:vnic_type': 'normal',
- 'binding:host_id': 'host'}
+ port_dict = {
+ 'admin_state_up': True,
+ 'device_id': '279989f7-54bb-41d9-ba42-0d61f12fda61',
+ 'device_owner': 'network:router_interface',
+ 'fixed_ips': [{'ip_address': '10.0.0.1',
+ 'subnet_id': subnet_dict['id']}],
+ 'id': '9036eedb-e7fa-458e-bc6e-d9d06d9d1bc4',
+ 'mac_address': 'fa:16:3e:9c:d5:7f',
+ 'name': '',
+ 'network_id': network_dict['id'],
+ 'status': 'ACTIVE',
+ 'tenant_id': network_dict['tenant_id'],
+ 'binding:vnic_type': 'normal',
+ 'binding:host_id': 'host',
+ 'security_groups': [],
+ }
TEST.api_ports.add(port_dict)
TEST.ports.add(neutron.Port(port_dict))
@@ -201,19 +216,25 @@ def data(TEST):
TEST.networks.add(neutron.Network(network))
TEST.subnets.add(subnet)
- port_dict = {'admin_state_up': True,
- 'device_id': '2',
- 'device_owner': 'compute:nova',
- 'fixed_ips': [{'ip_address': '172.16.88.3',
- 'subnet_id': subnet_dict['id']}],
- 'id': '1db2cc37-3553-43fa-b7e2-3fc4eb4f9905',
- 'mac_address': 'fa:16:3e:56:e6:2f',
- 'name': '',
- 'network_id': network_dict['id'],
- 'status': 'ACTIVE',
- 'tenant_id': network_dict['tenant_id'],
- 'binding:vnic_type': 'normal',
- 'binding:host_id': 'host'}
+ port_dict = {
+ 'admin_state_up': True,
+ 'device_id': '2',
+ 'device_owner': 'compute:nova',
+ 'fixed_ips': [{'ip_address': '172.16.88.3',
+ 'subnet_id': subnet_dict['id']}],
+ 'id': '1db2cc37-3553-43fa-b7e2-3fc4eb4f9905',
+ 'mac_address': 'fa:16:3e:56:e6:2f',
+ 'name': '',
+ 'network_id': network_dict['id'],
+ 'status': 'ACTIVE',
+ 'tenant_id': network_dict['tenant_id'],
+ 'binding:vnic_type': 'normal',
+ 'binding:host_id': 'host',
+ 'security_groups': [
+ # sec_group_1 ID below
+ 'faad7c80-3b62-4440-967c-13808c37131d',
+ ],
+ }
TEST.api_ports.add(port_dict)
TEST.ports.add(neutron.Port(port_dict))
@@ -315,19 +336,22 @@ def data(TEST):
TEST.subnets.add(subnet)
# Set up router data.
- port_dict = {'admin_state_up': True,
- 'device_id': '7180cede-bcd8-4334-b19f-f7ef2f331f53',
- 'device_owner': 'network:router_gateway',
- 'fixed_ips': [{'ip_address': '10.0.0.3',
- 'subnet_id': subnet_dict['id']}],
- 'id': '44ec6726-4bdc-48c5-94d4-df8d1fbf613b',
- 'mac_address': 'fa:16:3e:9c:d5:7e',
- 'name': '',
- 'network_id': TEST.networks.get(name="ext_net")['id'],
- 'status': 'ACTIVE',
- 'tenant_id': '1',
- 'binding:vnic_type': 'normal',
- 'binding:host_id': 'host'}
+ port_dict = {
+ 'admin_state_up': True,
+ 'device_id': '7180cede-bcd8-4334-b19f-f7ef2f331f53',
+ 'device_owner': 'network:router_gateway',
+ 'fixed_ips': [{'ip_address': '10.0.0.3',
+ 'subnet_id': subnet_dict['id']}],
+ 'id': '44ec6726-4bdc-48c5-94d4-df8d1fbf613b',
+ 'mac_address': 'fa:16:3e:9c:d5:7e',
+ 'name': '',
+ 'network_id': TEST.networks.get(name="ext_net")['id'],
+ 'status': 'ACTIVE',
+ 'tenant_id': '1',
+ 'binding:vnic_type': 'normal',
+ 'binding:host_id': 'host',
+ 'security_groups': [],
+ }
TEST.api_ports.add(port_dict)
TEST.ports.add(neutron.Port(port_dict))
@@ -923,20 +947,23 @@ def data(TEST):
TEST.firewalls.add(fw2)
# ports on 4th network
- port_dict = {'admin_state_up': True,
- 'device_id': '9872faaa-b2b2-eeee-9911-21332eedaa77',
- 'device_owner': 'network:dhcp',
- 'fixed_ips': [{'ip_address': '11.10.0.3',
- 'subnet_id':
- TEST.subnets.first().id}],
- 'id': 'a21dcd22-6733-cccc-aa32-22adafaf16a2',
- 'mac_address': '78:22:ff:1a:ba:23',
- 'name': 'port5',
- 'network_id': TEST.networks.first().id,
- 'status': 'ACTIVE',
- 'tenant_id': TEST.networks.first().tenant_id,
- 'binding:vnic_type': 'normal',
- 'binding:host_id': 'host'}
+ port_dict = {
+ 'admin_state_up': True,
+ 'device_id': '9872faaa-b2b2-eeee-9911-21332eedaa77',
+ 'device_owner': 'network:dhcp',
+ 'fixed_ips': [{'ip_address': '11.10.0.3',
+ 'subnet_id':
+ TEST.subnets.first().id}],
+ 'id': 'a21dcd22-6733-cccc-aa32-22adafaf16a2',
+ 'mac_address': '78:22:ff:1a:ba:23',
+ 'name': 'port5',
+ 'network_id': TEST.networks.first().id,
+ 'status': 'ACTIVE',
+ 'tenant_id': TEST.networks.first().tenant_id,
+ 'binding:vnic_type': 'normal',
+ 'binding:host_id': 'host',
+ 'security_groups': [],
+ }
TEST.api_ports.add(port_dict)
TEST.ports.add(neutron.Port(port_dict))
diff --git a/releasenotes/notes/security-group-in-port-detail-10a7f5d6d50d1571.yaml b/releasenotes/notes/security-group-in-port-detail-10a7f5d6d50d1571.yaml
new file mode 100644
index 0000000000..5538824edb
--- /dev/null
+++ b/releasenotes/notes/security-group-in-port-detail-10a7f5d6d50d1571.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Security group association per port is now shown in the port detail page.
+ In Neutron different security groups can be associated on different ports
+ of a same server instance, but previously it cannot be referred in Horizon.