Add details to network_qos panel

This patch adds details drawer summary to network_qos panel to show
policy descriptions and details page with further information and
associated rules.

Implements: blueprint network-bandwidth-limiting-qos

Change-Id: I1a8032206b576cf8dad5f75077bac7a093971973
This commit is contained in:
Beth Elwell 2017-03-14 15:46:17 +00:00
parent a08d41de4e
commit b81c5901cd
13 changed files with 374 additions and 21 deletions

View File

@ -1616,3 +1616,11 @@ def policy_list(request, **kwargs):
policies = neutronclient(request).list_qos_policies(
**kwargs).get('policies')
return [QoSPolicy(p) for p in policies]
@profiler.trace
def policy_get(request, policy_id, **kwargs):
"""Get QoS policy for a given policy id."""
policy = neutronclient(request).show_qos_policy(
policy_id, **kwargs).get('policy')
return QoSPolicy(policy)

View File

@ -274,3 +274,15 @@ class QoSPolicies(generic.View):
result = api.neutron.policy_list(request,
project_id=request.user.project_id)
return {'items': [p.to_dict() for p in result]}
@urls.register
class QoSPolicy(generic.View):
"""API for a single QoS Policy."""
url_regex = r'neutron/qos_policy/(?P<policy_id>[^/]+)/$'
@rest_utils.ajax()
def get(self, request, policy_id):
"""Get a specific policy"""
policy = api.neutron.policy_get(request, policy_id)
return policy.to_dict()

View File

@ -15,8 +15,8 @@ from django.utils.translation import ugettext_lazy as _
from horizon.browsers import views
title = _("Network QoS Policies")
title = _("Network QoS Policies")
urlpatterns = [
url(r'^$', views.AngularIndexView.as_view(title=title), name='index'),
]

View File

@ -0,0 +1,54 @@
/**
* 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.network_qos.details
*
* @description
* Provides details features for policies.
*/
angular
.module('horizon.app.core.network_qos.details', [
'horizon.framework.conf',
'horizon.app.core'
])
.run(registerPolicyDetails);
registerPolicyDetails.$inject = [
'horizon.app.core.network_qos.basePath',
'horizon.app.core.network_qos.resourceType',
'horizon.app.core.network_qos.service',
'horizon.framework.conf.resource-type-registry.service'
];
function registerPolicyDetails(
basePath,
qosResourceType,
qosService,
registry
) {
registry.getResourceType(qosResourceType)
.setLoadFunction(qosService.getPolicyPromise)
.detailsViews.append({
id: 'policyDetailsOverview',
name: gettext('Overview'),
template: basePath + 'details/overview.html'
});
}
})();

View File

@ -0,0 +1,19 @@
<div>
<hz-resource-property-list
resource-type-name="OS::Neutron::QoSPolicy"
item="item"
property-groups="[
['name', 'id'],
['created_at','updated_at'],
['project_id']]">
</hz-resource-property-list>
<div class="row" ng-if="drawerCtrl.metadataDefs">
<div class="col-sm-12">
<metadata-display
available="::drawerCtrl.metadataDefs"
existing="item.properties || item">
</metadata-display>
</div>
</div>
</div>

View File

@ -0,0 +1,71 @@
/*
* 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.network_qos')
.controller('NetworkQoSOverviewController', NetworkQoSOverviewController);
NetworkQoSOverviewController.$inject = [
'horizon.app.core.network_qos.resourceType',
'horizon.framework.conf.resource-type-registry.service',
'horizon.app.core.openstack-service-api.userSession',
'$scope'
];
function NetworkQoSOverviewController(
qosResourceTypeCode,
registry,
userSession,
$scope
) {
var ctrl = this;
ctrl.resourceType = registry.getResourceType(qosResourceTypeCode);
ctrl.tableConfig = {
selectAll: false,
expand: false,
trackId: 'id',
/*
* getTableColumns here won't work as that will give back the
* columns for the policy, but here we need columns only for the
* policy rules, which is a (list of) dictionary(ies) in the
* policy dictionary.
*/
columns: [
{id: 'id', title: gettext('Rule ID'), priority: 1, sortDefault: true},
{id: 'type', title: gettext('Type'), priority: 1},
{id: 'direction', title: gettext('Direction'), priority: 1},
{id: 'max_kbps', title: gettext('Max Kbps'), priority: 1},
{id: 'max_burst_kbps', title: gettext('Max Burst Kbits'), priority: 1},
{id: 'min_kbps', title: gettext('Min Kbps'), priority: 1},
{id: 'dscp_mark', title: gettext('DSCP Mark'), priority: 1}
]
};
$scope.context.loadPromise.then(onGetPolicy);
function onGetPolicy(response) {
ctrl.policy = response.data;
userSession.get().then(setProject);
function setProject(session) {
ctrl.projectId = session.project_id;
}
}
}
})();

View File

@ -0,0 +1,74 @@
/**
* 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('network qos overview controller', function() {
var ctrl;
var sessionObj = {project_id: '12'};
var neutron = {
getNamespaces: angular.noop
};
beforeEach(module('horizon.app.core.network_qos'));
beforeEach(module('horizon.framework.conf'));
beforeEach(inject(function($controller, $q, $injector) {
var session = $injector.get('horizon.app.core.openstack-service-api.userSession');
var deferred = $q.defer();
var sessionDeferred = $q.defer();
deferred.resolve({data: {rules: [{'a': 'apple'}, [], {}]}});
sessionDeferred.resolve(sessionObj);
spyOn(neutron, 'getNamespaces').and.returnValue(deferred.promise);
spyOn(session, 'get').and.returnValue(sessionDeferred.promise);
ctrl = $controller('NetworkQoSOverviewController',
{
'$scope': {context: {loadPromise: deferred.promise}}
}
);
}));
it('sets ctrl.resourceType', function() {
expect(ctrl.resourceType).toBeDefined();
});
it('sets ctrl.policy.rules (metadata)', inject(function($timeout) {
$timeout.flush();
expect(ctrl.policy).toBeDefined();
expect(ctrl.policy.rules).toBeDefined();
expect(ctrl.policy.rules[0]).toEqual({'a': 'apple'});
}));
it('sets ctrl.policy.rules propValue if empty array', inject(function($timeout) {
$timeout.flush();
expect(ctrl.policy).toBeDefined();
expect(ctrl.policy.rules).toBeDefined();
expect(ctrl.policy.rules[1]).toEqual([]);
}));
it('sets ctrl.policy.rules propValue if empty object', inject(function($timeout) {
$timeout.flush();
expect(ctrl.policy).toBeDefined();
expect(ctrl.policy.rules).toBeDefined();
expect(ctrl.policy.rules[2]).toEqual({});
}));
it('sets ctrl.projectId', inject(function($timeout) {
$timeout.flush();
expect(ctrl.projectId).toBe(sessionObj.project_id);
}));
});
})();

View File

@ -0,0 +1,37 @@
<div ng-controller="NetworkQoSOverviewController as ctrl">
<div class="row">
<div class="col-md-6 detail">
<h3 translate>Policy Details</h3>
<hr>
<hz-resource-property-list
resource-type-name="OS::Neutron::QoSPolicy"
cls="dl-horizontal"
item="ctrl.policy"
property-groups="[['name', 'description', 'id', 'project_id']]">
</hz-resource-property-list>
</div>
<div class="col-md-6 detail">
<h3 translate>Ownership</h3>
<hr>
<hz-resource-property-list
resource-type-name="OS::Neutron::QoSPolicy"
cls="dl-horizontal"
item="ctrl.policy"
property-groups="[['created_at', 'updated_at', 'shared', 'revision_number']]">
</hz-resource-property-list>
</div>
</div>
<div class="row">
<div class="col-md-12 detail">
<h3 translate>Rules</h3>
<hr>
<dl class="dl-horizontal">
<hz-dynamic-table
config="ctrl.tableConfig"
items="ctrl.policy.rules"
table="ctrl">
</hz-dynamic-table>
</dl>
</div>
</div>
</div>

View File

@ -25,7 +25,8 @@
*/
angular
.module('horizon.app.core.network_qos', [
'ngRoute'
'ngRoute',
'horizon.app.core.network_qos.details'
])
.constant('horizon.app.core.network_qos.resourceType', 'OS::Neutron::QoSPolicy')
.run(run)
@ -33,26 +34,26 @@
run.$inject = [
'horizon.framework.conf.resource-type-registry.service',
'horizon.app.core.network_qos.basePath',
'horizon.app.core.network_qos.service',
'horizon.app.core.network_qos.resourceType'
];
function run(registry,
basePath,
qosService,
qosResourceType) {
registry.getResourceType(qosResourceType)
.setNames(gettext('QoS Policy'), gettext('QoS Policies'))
.setSummaryTemplateUrl(basePath + 'details/drawer.html')
.setProperties(qosProperties(qosService))
.setListFunction(qosService.getPoliciesPromise)
.tableColumns
.append({
id: 'name',
priority: 1,
sortDefault: true
})
.append({
id: 'id',
priority: 1
sortDefault: true,
urlFunction: qosService.getDetailsPath
})
.append({
id: 'description',
@ -70,11 +71,6 @@
singleton: true,
persistent: true
})
.append({
label: gettext('Policy ID'),
name: 'id',
singleton: true
})
.append({
label: gettext('Description'),
name: 'description',
@ -100,27 +96,46 @@
name: gettext('Policy Name'),
id: gettext('Policy ID'),
description: gettext('Description'),
shared: { label: gettext('Shared'), filters: ['yesno'] }
shared: { label: gettext('Shared'), filters: ['yesno'] },
tenant_id: gettext('Tenant ID'),
project_id: gettext('Project ID'),
created_at: gettext('Created At'),
updated_at: gettext('Updated At'),
rules: gettext('Rules'),
revision_number: gettext('Revision Number')
};
}
config.$inject = [
'$provide',
'$windowProvider',
'$routeProvider'
'$routeProvider',
'horizon.app.core.detailRoute'
];
/**
* @name horizon.dashboard.project.network_qos.basePath
* @param {Object} $provide
* @param {Object} $windowProvider
* @param {Object} $routeProvider
* @param {Object} detailRoute
* @description Base path for the QoS code
*/
function config($provide, $windowProvider, $routeProvider) {
function config($provide, $windowProvider, $routeProvider, detailRoute) {
var path = $windowProvider.$get().STATIC_URL + 'app/core/network_qos/';
$provide.constant('horizon.app.core.network_qos.basePath', path);
$routeProvider.when('/project/network_qos', {
$routeProvider
.when('/project/network_qos', {
templateUrl: path + 'panel.html'
})
.when('/project/network_qos/:policy_id', {
redirectTo: goToAngularDetails
});
function goToAngularDetails(params) {
return detailRoute + 'OS::Neutron::QoSPolicy/' + params.id;
}
}
})();

View File

@ -21,7 +21,8 @@
qosService.$inject = [
'$filter',
'horizon.app.core.openstack-service-api.neutron',
'horizon.app.core.openstack-service-api.userSession'
'horizon.app.core.openstack-service-api.userSession',
'horizon.app.core.detailRoute'
];
/*
@ -34,13 +35,30 @@
* but do not need to be restricted to such use. Each exposed function
* is documented below.
*/
function qosService($filter, neutron, userSession) {
function qosService($filter,
neutron,
userSession,
detailRoute) {
var version;
return {
getPoliciesPromise: getPoliciesPromise
getDetailsPath: getDetailsPath,
getPoliciesPromise: getPoliciesPromise,
getPolicyPromise: getPolicyPromise
};
/*
* @ngdoc function
* @name getDetailsPath
* @param item {Object} - The QoS Policy object
* @description
* Given an QoS Policy object, returns the relative path to the details
* view.
*/
function getDetailsPath(item) {
return detailRoute + 'OS::Neutron::QoSPolicy/' + item.id;
}
/*
* @ngdoc function
* @name getPoliciesPromise
@ -69,6 +87,16 @@
}
}
/*
* @ngdoc function
* @name getPolicyPromise
* @description
* Given an id, returns a promise for the policy data.
*/
function getPolicyPromise(identifier) {
return neutron.getQosPolicy(identifier);
}
}
})();

View File

@ -51,6 +51,18 @@
}));
});
describe('getPolicyPromise', function() {
it("provides a promise", inject(function($q, $injector) {
var neutron = $injector.get('horizon.app.core.openstack-service-api.neutron');
var deferred = $q.defer();
spyOn(neutron, 'getQosPolicy').and.returnValue(deferred.promise);
var result = service.getPolicyPromise({});
deferred.resolve({data: {id: 1, name: 'policy1'}});
expect(neutron.getQosPolicy).toHaveBeenCalled();
expect(result.$$state.value.data.name).toBe('policy1');
}));
});
});
})();

View File

@ -43,6 +43,7 @@
getExtensions: getExtensions,
getNetworks: getNetworks,
getPorts: getPorts,
getQosPolicy: getQosPolicy,
getQoSPolicies: getQoSPolicies,
getSubnets: getSubnets,
getTrunks: getTrunks,
@ -338,10 +339,23 @@
// QoS policies
/**
* @name horizon.app.core.openstack-service-api.neutron.getQosPolicy
* @description get a single qos policy by ID.
* @param {string} id
* Specifies the id of the policy to request.
* @returns {Object} The result of the API call
*/
function getQosPolicy(id) {
return apiService.get('/api/neutron/qos_policy/' + id + '/')
.error(function () {
toastService.add('error', gettext('Unable to retrieve the qos policy.'));
});
}
/**
* @name horizon.app.core.openstack-service-api.neutron.getQoSPolicies
* @description
* Get a list of qos policies.
* @description get a list of qos policies.
*
* The listing result is an object with property "items". Each item is
* a QoS policy.

View File

@ -162,6 +162,15 @@
42
]
},
{
"func": "getQosPolicy",
"method": "get",
"path": "/api/neutron/qos_policy/1/",
"error": "Unable to retrieve the qos policy.",
"testInput": [
1
]
},
{
"func": "getQoSPolicies",
"method": "get",