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 }}
+
+ +
+ {% 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.