From 3524b3d4f79e7a01cf855b91cc8f79e426a37531 Mon Sep 17 00:00:00 2001 From: Lajos Katona Date: Tue, 14 Mar 2017 08:31:28 +0100 Subject: [PATCH] New readonly panel for trunks Enable display of trunks on the project dashboard. To test it devstack needs to have neutron with trunk support, eg: local.conf: enable_plugin neutron https://git.openstack.org/openstack/neutron enable_service q-trunk No special horizon config is needed. As long as the 'trunk' API extension is available (openstack extension show trunk) the panel should automatically appear under Project/Network/Trunks. Co-Authored-By: Akihiro Motoki Co-Authored-By: Bence Romsics Change-Id: Iacb83f22f81e09457953622e61065f0bb2c27407 Partially-Implements: blueprint neutron-trunk-ui --- openstack_dashboard/api/neutron.py | 21 +++ openstack_dashboard/api/rest/neutron.py | 17 ++ .../dashboards/project/trunks/__init__.py | 0 .../dashboards/project/trunks/panel.py | 44 +++++ .../dashboards/project/trunks/urls.py | 24 +++ .../enabled/_1500_project_trunks_panel.py | 10 ++ .../static/app/core/core.module.js | 1 + .../openstack-service-api/neutron.service.js | 21 ++- .../neutron.service.spec.js | 7 + .../static/app/core/trunks/panel.html | 3 + .../static/app/core/trunks/summary.html | 17 ++ .../static/app/core/trunks/trunks.module.js | 160 ++++++++++++++++++ .../app/core/trunks/trunks.module.spec.js | 52 ++++++ .../static/app/core/trunks/trunks.service.js | 61 +++++++ .../app/core/trunks/trunks.service.spec.js | 48 ++++++ .../static/app/resources/resources.module.js | 2 + .../test/api_tests/neutron_rest_tests.py | 13 ++ .../test/api_tests/neutron_tests.py | 27 +++ openstack_dashboard/test/settings.py | 5 + .../test/test_data/neutron_data.py | 18 ++ 20 files changed, 549 insertions(+), 2 deletions(-) create mode 100644 openstack_dashboard/dashboards/project/trunks/__init__.py create mode 100644 openstack_dashboard/dashboards/project/trunks/panel.py create mode 100644 openstack_dashboard/dashboards/project/trunks/urls.py create mode 100644 openstack_dashboard/enabled/_1500_project_trunks_panel.py create mode 100644 openstack_dashboard/static/app/core/trunks/panel.html create mode 100644 openstack_dashboard/static/app/core/trunks/summary.html create mode 100644 openstack_dashboard/static/app/core/trunks/trunks.module.js create mode 100644 openstack_dashboard/static/app/core/trunks/trunks.module.spec.js create mode 100644 openstack_dashboard/static/app/core/trunks/trunks.service.js create mode 100644 openstack_dashboard/static/app/core/trunks/trunks.service.spec.js diff --git a/openstack_dashboard/api/neutron.py b/openstack_dashboard/api/neutron.py index ff20666a9e..c40118141a 100644 --- a/openstack_dashboard/api/neutron.py +++ b/openstack_dashboard/api/neutron.py @@ -107,6 +107,20 @@ class Subnet(NeutronAPIDictWrapper): super(Subnet, self).__init__(apidict) +class Trunk(NeutronAPIDictWrapper): + """Wrapper for neutron trunks.""" + + @property + def subport_count(self): + return len(self._apidict.get('sub_ports', [])) + + def to_dict(self): + trunk_dict = super(Trunk, self).to_dict() + trunk_dict['name_or_id'] = self.name_or_id + trunk_dict['subport_count'] = self.subport_count + return trunk_dict + + class SubnetPool(NeutronAPIDictWrapper): """Wrapper for neutron subnetpools.""" @@ -623,6 +637,13 @@ def list_resources_with_long_filters(list_method, return resources +@profiler.trace +def trunk_list(request, **params): + LOG.debug("trunk_list(): params=%s", params) + trunks = neutronclient(request).list_trunks(**params).get('trunks') + return [Trunk(t) for t in trunks] + + @profiler.trace def network_list(request, **params): LOG.debug("network_list(): params=%s", params) diff --git a/openstack_dashboard/api/rest/neutron.py b/openstack_dashboard/api/rest/neutron.py index f6bf110531..704fb8fde5 100644 --- a/openstack_dashboard/api/rest/neutron.py +++ b/openstack_dashboard/api/rest/neutron.py @@ -136,6 +136,23 @@ class Ports(generic.View): return{'items': [n.to_dict() for n in result]} +@urls.register +class Trunks(generic.View): + """API for neutron Trunks + """ + url_regex = r'neutron/trunks/$' + + @rest_utils.ajax() + def get(self, request): + """Get a list of trunks + + The listing result is an object with property "items". + Each item is a trunk. + """ + result = api.neutron.trunk_list(request, **request.GET) + return {'items': [n.to_dict() for n in result]} + + @urls.register class Services(generic.View): """API for Neutron agents diff --git a/openstack_dashboard/dashboards/project/trunks/__init__.py b/openstack_dashboard/dashboards/project/trunks/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openstack_dashboard/dashboards/project/trunks/panel.py b/openstack_dashboard/dashboards/project/trunks/panel.py new file mode 100644 index 0000000000..a37b178023 --- /dev/null +++ b/openstack_dashboard/dashboards/project/trunks/panel.py @@ -0,0 +1,44 @@ +# Copyright 2017 Ericsson +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging + +from django.utils.translation import ugettext_lazy as _ + +import horizon + +from openstack_dashboard.api import neutron + +LOG = logging.getLogger(__name__) + + +class Trunks(horizon.Panel): + name = _("Trunks") + slug = "trunks" + permissions = ('openstack.services.network',) + + def allowed(self, context): + request = context['request'] + try: + return ( + super(Trunks, self).allowed(context) + and request.user.has_perms(self.permissions) + and neutron.is_extension_supported(request, + extension_alias='trunk') + ) + except Exception: + LOG.error("Call to list enabled services failed. This is likely " + "due to a problem communicating with the Neutron " + "endpoint. Trunks panel will not be displayed.") + return False diff --git a/openstack_dashboard/dashboards/project/trunks/urls.py b/openstack_dashboard/dashboards/project/trunks/urls.py new file mode 100644 index 0000000000..5413fbc770 --- /dev/null +++ b/openstack_dashboard/dashboards/project/trunks/urls.py @@ -0,0 +1,24 @@ +# Copyright 2017 Ericsson +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from django.conf.urls import url +from django.utils.translation import ugettext_lazy as _ + +from horizon.browsers.views import AngularIndexView + + +title = _("Trunks") +urlpatterns = [ + url(r'^$', AngularIndexView.as_view(title=title), name='index'), +] diff --git a/openstack_dashboard/enabled/_1500_project_trunks_panel.py b/openstack_dashboard/enabled/_1500_project_trunks_panel.py new file mode 100644 index 0000000000..1775743815 --- /dev/null +++ b/openstack_dashboard/enabled/_1500_project_trunks_panel.py @@ -0,0 +1,10 @@ +# The slug of the panel to be added to HORIZON_CONFIG. Required. +PANEL = 'trunks' +# The slug of the dashboard the PANEL associated with. Required. +PANEL_DASHBOARD = 'project' +# The slug of the panel group the PANEL is associated with. +PANEL_GROUP = 'network' + +# Python panel class of the PANEL to be added. +ADD_PANEL = \ + 'openstack_dashboard.dashboards.project.trunks.panel.Trunks' diff --git a/openstack_dashboard/static/app/core/core.module.js b/openstack_dashboard/static/app/core/core.module.js index 867835659b..121c714ec5 100644 --- a/openstack_dashboard/static/app/core/core.module.js +++ b/openstack_dashboard/static/app/core/core.module.js @@ -38,6 +38,7 @@ 'horizon.app.core.images', 'horizon.app.core.metadata', 'horizon.app.core.openstack-service-api', + 'horizon.app.core.trunks', 'horizon.app.core.workflow', 'horizon.framework.conf', 'horizon.framework.util', diff --git a/openstack_dashboard/static/app/core/openstack-service-api/neutron.service.js b/openstack_dashboard/static/app/core/openstack-service-api/neutron.service.js index 02938da3ee..69120b3de9 100644 --- a/openstack_dashboard/static/app/core/openstack-service-api/neutron.service.js +++ b/openstack_dashboard/static/app/core/openstack-service-api/neutron.service.js @@ -43,7 +43,8 @@ getAgents: getAgents, getExtensions: getExtensions, getDefaultQuotaSets: getDefaultQuotaSets, - updateProjectQuota: updateProjectQuota + updateProjectQuota: updateProjectQuota, + getTrunks: getTrunks }; return service; @@ -332,6 +333,22 @@ toastService.add('error', gettext('Unable to update project quota data.')); }); } - } + // Trunks + + /** + * @name getTrunks + * @description + * Get a list of trunks for a tenant. + * + * @returns {Object} An object with property "items". Each item is a trunk. + */ + function getTrunks(params) { + var config = params ? {'params' : params} : {}; + return apiService.get('/api/neutron/trunks/', config) + .error(function () { + toastService.add('error', gettext('Unable to retrieve the trunks.')); + }); + } + } }()); diff --git a/openstack_dashboard/static/app/core/openstack-service-api/neutron.service.spec.js b/openstack_dashboard/static/app/core/openstack-service-api/neutron.service.spec.js index 9959a85f36..07598849f5 100644 --- a/openstack_dashboard/static/app/core/openstack-service-api/neutron.service.spec.js +++ b/openstack_dashboard/static/app/core/openstack-service-api/neutron.service.spec.js @@ -125,6 +125,13 @@ }, 42 ] + }, + { + "func": "getTrunks", + "method": "get", + "path": "/api/neutron/trunks/", + "data": {}, + "error": "Unable to retrieve the trunks." } ]; diff --git a/openstack_dashboard/static/app/core/trunks/panel.html b/openstack_dashboard/static/app/core/trunks/panel.html new file mode 100644 index 0000000000..bab87ec838 --- /dev/null +++ b/openstack_dashboard/static/app/core/trunks/panel.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/openstack_dashboard/static/app/core/trunks/summary.html b/openstack_dashboard/static/app/core/trunks/summary.html new file mode 100644 index 0000000000..263b2c1b31 --- /dev/null +++ b/openstack_dashboard/static/app/core/trunks/summary.html @@ -0,0 +1,17 @@ + + +
+
+
+
+
{$ key $}
+
{$ value $}
+
+
+
+
\ No newline at end of file diff --git a/openstack_dashboard/static/app/core/trunks/trunks.module.js b/openstack_dashboard/static/app/core/trunks/trunks.module.js new file mode 100644 index 0000000000..016672e2fb --- /dev/null +++ b/openstack_dashboard/static/app/core/trunks/trunks.module.js @@ -0,0 +1,160 @@ +/** + * Copyright 2017 Ericsson + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +(function() { + 'use strict'; + + /** + * @ngdoc overview + * @ngname horizon.app.core.trunks + * + * @description + * Provides all of the services and widgets required + * to support and display trunks related content. + */ + angular + .module('horizon.app.core.trunks', [ + 'ngRoute', + 'horizon.framework.conf', + 'horizon.app.core' + ]) + .constant('horizon.app.core.trunks.resourceType', 'OS::Neutron::Trunk') + .run(run) + .config(config); + + run.$inject = [ + 'horizon.framework.conf.resource-type-registry.service', + 'horizon.framework.util.i18n.gettext', + 'horizon.app.core.trunks.basePath', + 'horizon.app.core.trunks.service', + 'horizon.app.core.trunks.resourceType' + ]; + + function run(registry, + gettext, + basePath, + trunksService, + trunkResourceType) { + registry.getResourceType(trunkResourceType) + .setNames(gettext('Trunk'), gettext('Trunks')) + .setSummaryTemplateUrl(basePath + 'summary.html') + .setProperties(trunkProperties()) + .setListFunction(trunksService.getTrunksPromise) + .tableColumns + .append({ + id: 'name_or_id', + priority: 1, + sortDefault: true + }) + .append({ + id: 'port_id', + priority: 1 + }) + .append({ + id: 'subport_count', + priority: 1 + }) + .append({ + id: 'admin_state', + priority: 1 + }) + .append({ + id: 'status', + priority: 1 + }); + + /** + * Filtering - client-side MagicSearch + * all facets for trunks table + */ + registry.getResourceType(trunkResourceType).filterFacets + .append({ + label: gettext('Name'), + name: 'name', + singleton: true + }) + .append({ + label: gettext('Parent Port'), + name: 'port_id', + singleton: true + }) + .append({ + label: gettext('Status'), + name: 'status', + singleton: true, + options: [ + {label: gettext('Active'), key: 'ACTIVE'}, + {label: gettext('Down'), key: 'DOWN'}, + {label: gettext('Build'), key: 'BUILD'}, + {label: gettext('Degraded'), key: 'DEGRADED'}, + {label: gettext('Error'), key: 'ERROR'} + ] + }) + .append({ + label: gettext('Admin State'), + name: 'admin_state_up', + singleton: true, + options: [ + {label: gettext('Up'), key: 'true'}, + {label: gettext('Down'), key: 'false'} + ] + }); + } + + /** + * @name trunkProperties + * @description resource properties for trunk module + */ + function trunkProperties() { + return { + admin_state: gettext('Admin State'), + created_at: gettext('Created at'), + description: gettext('Description'), + id: gettext('ID'), + name: gettext('Name'), + name_or_id: gettext('Name'), + port_id: gettext('Parent Port'), + project_id: gettext('Project ID'), + status: gettext('Status'), + subport_count: gettext('Subport Count'), + updated_at: gettext('Updated at') + }; + } + + config.$inject = [ + '$provide', + '$windowProvider', + '$routeProvider' + ]; + + /** + * @name config + * @param {Object} $provide + * @param {Object} $windowProvider + * @param {Object} $routeProvider + * @description Routes used by this module. + * @returns {undefined} Returns nothing + */ + function config($provide, $windowProvider, $routeProvider) { + var path = $windowProvider.$get().STATIC_URL + 'app/core/trunks/'; + $provide.constant('horizon.app.core.trunks.basePath', path); + + $routeProvider.when('/project/trunks', { + templateUrl: path + 'panel.html' + }); + } + +})(); diff --git a/openstack_dashboard/static/app/core/trunks/trunks.module.spec.js b/openstack_dashboard/static/app/core/trunks/trunks.module.spec.js new file mode 100644 index 0000000000..0e00141b3b --- /dev/null +++ b/openstack_dashboard/static/app/core/trunks/trunks.module.spec.js @@ -0,0 +1,52 @@ +/** + * Copyright 2017 Ericsson + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +(function() { + "use strict"; + + describe('horizon.app.core.trunks', function () { + it('should exist', function () { + expect(angular.module('horizon.app.core.trunks')).toBeDefined(); + }); + }); + + describe('loading the trunk module', function () { + var registry; + + beforeEach(module('horizon.app.core.trunks')); + beforeEach(inject(function($injector) { + registry = $injector.get('horizon.framework.conf.resource-type-registry.service'); + })); + + it('registers names', function() { + expect(registry.getResourceType('OS::Neutron::Trunk').getName()).toBe("Trunks"); + }); + + it('should set facets for search', function () { + var names = registry.getResourceType('OS::Neutron::Trunk').filterFacets + .map(getName); + expect(names).toContain('name'); + expect(names).toContain('port_id'); + expect(names).toContain('status'); + expect(names).toContain('admin_state_up'); + + function getName(x) { + return x.name; + } + }); + }); + +})(); diff --git a/openstack_dashboard/static/app/core/trunks/trunks.service.js b/openstack_dashboard/static/app/core/trunks/trunks.service.js new file mode 100644 index 0000000000..dcb5ce47c4 --- /dev/null +++ b/openstack_dashboard/static/app/core/trunks/trunks.service.js @@ -0,0 +1,61 @@ +/** + * Copyright 2017 Ericsson + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +(function() { + "use strict"; + + angular.module('horizon.app.core.trunks') + .factory('horizon.app.core.trunks.service', trunksService); + + trunksService.$inject = [ + 'horizon.app.core.openstack-service-api.neutron', + 'horizon.app.core.openstack-service-api.userSession' + ]; + + /* + * @ngdoc factory + * @name horizon.app.core.trunks.service + * + * @description + * This service provides functions that are used through the Trunks + * features. These are primarily used in the module registrations + * but do not need to be restricted to such use. Each exposed function + * is documented below. + */ + function trunksService(neutron, userSession) { + + return { + getTrunksPromise: getTrunksPromise + }; + + /* + * @ngdoc function + * @name getTrunksPromise + * @description + * Given filter/query parameters, returns a promise for the matching + * trunks. This is used in displaying lists of Trunks. + */ + function getTrunksPromise(params) { + return userSession.get().then(getTrunksForProject); + + function getTrunksForProject(userSession) { + params.project_id = userSession.project_id; + return neutron.getTrunks(params); + } + } + } + +})(); diff --git a/openstack_dashboard/static/app/core/trunks/trunks.service.spec.js b/openstack_dashboard/static/app/core/trunks/trunks.service.spec.js new file mode 100644 index 0000000000..75f177af4d --- /dev/null +++ b/openstack_dashboard/static/app/core/trunks/trunks.service.spec.js @@ -0,0 +1,48 @@ +/** + * Copyright 2017 Ericsson + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +(function() { + "use strict"; + + describe('trunks service', function() { + var service; + beforeEach(module('horizon.framework.util')); + beforeEach(module('horizon.framework.conf')); + beforeEach(module('horizon.app.core.trunks')); + beforeEach(inject(function($injector) { + service = $injector.get('horizon.app.core.trunks.service'); + })); + + describe('getTrunksPromise', function() { + it("provides a promise that gets translated", inject(function($q, $injector, $timeout) { + var neutron = $injector.get('horizon.app.core.openstack-service-api.neutron'); + var session = $injector.get('horizon.app.core.openstack-service-api.userSession'); + var deferred = $q.defer(); + var deferredSession = $q.defer(); + spyOn(neutron, 'getTrunks').and.returnValue(deferred.promise); + spyOn(session, 'get').and.returnValue(deferredSession.promise); + var result = service.getTrunksPromise({}); + deferred.resolve({data: {items: [{id: 1, updated_at: 'Apr10'}]}}); + deferredSession.resolve({project_id: '42'}); + $timeout.flush(); + expect(neutron.getTrunks).toHaveBeenCalled(); + expect(result.$$state.value.data.items[0].updated_at).toBe('Apr10'); + expect(result.$$state.value.data.items[0].id).toBe(1); + })); + }); + + }); +})(); diff --git a/openstack_dashboard/static/app/resources/resources.module.js b/openstack_dashboard/static/app/resources/resources.module.js index c5c77baea3..4ab8d583c5 100644 --- a/openstack_dashboard/static/app/resources/resources.module.js +++ b/openstack_dashboard/static/app/resources/resources.module.js @@ -78,6 +78,8 @@ .setNames(gettext('Floating IP'), gettext('Floating IPs')); registry.getResourceType('OS::Neutron::SecurityGroup') .setNames(gettext('Security Group'), gettext('Security Groups')); + registry.getResourceType('OS::Neutron::Trunk') + .setNames(gettext('Trunk'), gettext('Trunks')); registry.getResourceType('OS::Keystone::User') .setNames(gettext('User'), gettext('Users')); registry.getResourceType('OS::Keystone::Group') diff --git a/openstack_dashboard/test/api_tests/neutron_rest_tests.py b/openstack_dashboard/test/api_tests/neutron_rest_tests.py index 6186fa7fae..069a3e5bd6 100644 --- a/openstack_dashboard/test/api_tests/neutron_rest_tests.py +++ b/openstack_dashboard/test/api_tests/neutron_rest_tests.py @@ -159,6 +159,19 @@ class NeutronPortsTestCase(test.TestCase): request, network_id=TEST.api_networks.first().get("id")) +class NeutronTrunksTestCase(test.TestCase): + + @mock.patch.object(neutron.api, 'neutron') + def test_get(self, client): + request = self.mock_rest_request(GET={}) + client.trunk_list.return_value = self.trunks.list() + response = neutron.Trunks().get(request) + self.assertStatusCode(response, 200) + self.assertItemsCollectionEqual( + response, + [t.to_dict() for t in self.trunks.list()]) + + class NeutronExtensionsTestCase(test.TestCase): def setUp(self): super(NeutronExtensionsTestCase, self).setUp() diff --git a/openstack_dashboard/test/api_tests/neutron_tests.py b/openstack_dashboard/test/api_tests/neutron_tests.py index cdf6bfcb8b..6512c78f34 100644 --- a/openstack_dashboard/test/api_tests/neutron_tests.py +++ b/openstack_dashboard/test/api_tests/neutron_tests.py @@ -415,6 +415,33 @@ class NeutronApiTests(test.APITestCase): api.neutron.port_delete(self.request, port_id) + def test_trunk_list(self): + trunks = {'trunks': self.api_trunks.list()} + neutron_client = self.stub_neutronclient() + neutron_client.list_trunks().AndReturn(trunks) + self.mox.ReplayAll() + + ret_val = api.neutron.trunk_list(self.request) + for t in ret_val: + self.assertIsInstance(t, api.neutron.Trunk) + + def test_trunk_object(self): + trunk = self.api_trunks.first().copy() + obj = api.neutron.Trunk(trunk) + self.assertEqual(0, obj.subport_count) + trunk_dict = obj.to_dict() + self.assertIsInstance(trunk_dict, dict) + self.assertEqual(trunk['name'], trunk_dict['name_or_id']) + self.assertEqual(0, trunk_dict['subport_count']) + + trunk['name'] = '' # to test name_or_id + trunk['sub_ports'] = [uuidutils.generate_uuid() for i in range(2)] + obj = api.neutron.Trunk(trunk) + self.assertEqual(2, obj.subport_count) + trunk_dict = obj.to_dict() + self.assertEqual(obj.name_or_id, trunk_dict['name_or_id']) + self.assertEqual(2, trunk_dict['subport_count']) + def test_router_list(self): routers = {'routers': self.api_routers.list()} diff --git a/openstack_dashboard/test/settings.py b/openstack_dashboard/test/settings.py index 372dbd598d..ffa04a42f5 100644 --- a/openstack_dashboard/test/settings.py +++ b/openstack_dashboard/test/settings.py @@ -278,6 +278,11 @@ TEST_GLOBAL_MOCKS_ON_PANELS = { '.firewalls.panel.Firewall.can_access'), 'return_value': True, }, + 'trunk': { + 'method': ('openstack_dashboard.dashboards.project' + '.trunks.panel.Trunks.can_access'), + 'return_value': True, + }, 'vpn': { 'method': ('openstack_dashboard.dashboards.project' '.vpn.panel.VPN.can_access'), diff --git a/openstack_dashboard/test/test_data/neutron_data.py b/openstack_dashboard/test/test_data/neutron_data.py index 4892412d73..4631fe07b2 100644 --- a/openstack_dashboard/test/test_data/neutron_data.py +++ b/openstack_dashboard/test/test_data/neutron_data.py @@ -31,6 +31,7 @@ def data(TEST): TEST.subnets = utils.TestDataContainer() TEST.subnetpools = utils.TestDataContainer() TEST.ports = utils.TestDataContainer() + TEST.trunks = utils.TestDataContainer() TEST.routers = utils.TestDataContainer() TEST.routers_with_rules = utils.TestDataContainer() TEST.routers_with_routes = utils.TestDataContainer() @@ -59,6 +60,7 @@ def data(TEST): TEST.api_subnets = utils.TestDataContainer() TEST.api_subnetpools = utils.TestDataContainer() TEST.api_ports = utils.TestDataContainer() + TEST.api_trunks = utils.TestDataContainer() TEST.api_routers = utils.TestDataContainer() TEST.api_routers_with_routes = utils.TestDataContainer() TEST.api_floating_ips = utils.TestDataContainer() @@ -327,6 +329,18 @@ def data(TEST): TEST.api_ports.add(port_dict) TEST.ports.add(neutron.Port(port_dict)) + trunk_dict = {'status': 'UP', + 'sub_ports': [], + 'name': 'trunk1', + 'admin_state_up': True, + 'tenant_id': '1', + 'project_id': '1', + 'port_id': '895d375c-1447-11e7-a52f-f7f280bbc809', + 'id': '94fcb9e8-1447-11e7-bed6-8b8c4ac74491'} + + TEST.api_trunks.add(trunk_dict) + TEST.trunks.add(neutron.Trunk(trunk_dict)) + router_dict = {'id': '279989f7-54bb-41d9-ba42-0d61f12fda61', 'name': 'router1', 'status': 'ACTIVE', @@ -574,11 +588,15 @@ def data(TEST): extension_5 = {"name": "HA Router extension", "alias": "l3-ha", "description": "Add HA capability to routers."} + extension_6 = {"name": "Trunks", + "alias": "trunk", + "description": "Provides support for trunk ports."} TEST.api_extensions.add(extension_1) TEST.api_extensions.add(extension_2) TEST.api_extensions.add(extension_3) TEST.api_extensions.add(extension_4) TEST.api_extensions.add(extension_5) + TEST.api_extensions.add(extension_6) # 1st agent. agent_dict = {"binary": "neutron-openvswitch-agent",