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",