Add l7 support

Enable users to create l7 policies and rules on a listener.

Change-Id: I29130311346c8627473222491a76777a9dc95f52
This commit is contained in:
Jacky Hu 2018-03-12 18:14:23 +08:00 committed by Michael Johnson
parent e9b3988a9d
commit 5b7326405a
54 changed files with 4338 additions and 7 deletions

View File

@ -180,6 +180,47 @@ def create_listener(request, **kwargs):
return _get_sdk_object_dict(listener)
def create_l7_policy(request, **kwargs):
"""Create a new l7 policy.
"""
data = request.DATA
conn = _get_sdk_connection(request)
l7_policy = conn.load_balancer.create_l7_policy(
action=data['l7policy']['action'],
admin_state_up=data['l7policy'].get('admin_state_up'),
description=data['l7policy'].get('description'),
listener_id=kwargs['listener_id'],
name=data['l7policy'].get('name'),
position=data['l7policy'].get('position'),
redirect_pool_id=data['l7policy'].get('redirect_pool_id'),
redirect_url=data['l7policy'].get('redirect_url'),
)
return _get_sdk_object_dict(l7_policy)
def create_l7_rule(request, **kwargs):
"""Create a new l7 rule.
"""
data = request.DATA
conn = _get_sdk_connection(request)
l7_rule = conn.load_balancer.create_l7_rule(
admin_state_up=data['l7rule'].get('admin_state_up'),
compare_type=data['l7rule']['compare_type'],
invert=data['l7rule'].get('invert'),
key=data['l7rule'].get('key'),
l7_policy=kwargs['l7_policy_id'],
type=data['l7rule']['type'],
rule_value=data['l7rule']['rule_value'],
)
return _get_sdk_object_dict(l7_rule)
def create_pool(request, **kwargs):
"""Create a new pool.
@ -369,6 +410,50 @@ def update_listener(request, **kwargs):
return _get_sdk_object_dict(listener)
def update_l7_policy(request, **kwargs):
"""Update a l7 policy.
"""
data = request.DATA
l7_policy_id = data['l7policy'].get('id')
conn = _get_sdk_connection(request)
l7_policy = conn.load_balancer.update_l7_policy(
action=data['l7policy']['action'],
admin_state_up=data['l7policy'].get('admin_state_up'),
description=data['l7policy'].get('description'),
l7_policy=l7_policy_id,
name=data['l7policy'].get('name'),
position=data['l7policy'].get('position'),
redirect_pool_id=data['l7policy'].get('redirect_pool_id'),
redirect_url=data['l7policy'].get('redirect_url'),
)
return _get_sdk_object_dict(l7_policy)
def update_l7_rule(request, **kwargs):
"""Update a l7 rule.
"""
data = request.DATA
l7_rule_id = data['l7rule'].get('id')
conn = _get_sdk_connection(request)
l7_rule = conn.load_balancer.update_l7_rule(
admin_state_up=data['l7rule'].get('admin_state_up'),
compare_type=data['l7rule']['compare_type'],
invert=data['l7rule'].get('invert'),
key=data['l7rule'].get('key'),
l7_policy=kwargs['l7_policy_id'],
l7rule=l7_rule_id,
type=data['l7rule']['type'],
rule_value=data['l7rule']['rule_value'],
)
return _get_sdk_object_dict(l7_rule)
def update_pool(request, **kwargs):
"""Update a pool.
@ -674,6 +759,148 @@ class Listener(generic.View):
conn.load_balancer.delete_listener(listener_id, ignore_missing=True)
@urls.register
class L7Policies(generic.View):
"""API for load balancer l7 policies.
"""
url_regex = r'lbaas/l7policies/$'
@rest_utils.ajax()
def get(self, request):
"""List of l7 policies for the current project.
The listing result is an object with property "items".
"""
listener_id = request.GET.get('listenerId')
conn = _get_sdk_connection(request)
l7_policy_list = _sdk_object_to_list(conn.load_balancer.l7_policies(
listener_id=listener_id))
return {'items': l7_policy_list}
@rest_utils.ajax()
def post(self, request):
"""Create a new l7 policy.
Creates a new l7 policy as well as other optional resources such as
l7 rules.
"""
kwargs = {'listener_id': request.DATA.get('parentResourceId')}
return create_l7_policy(request, **kwargs)
@urls.register
class L7Policy(generic.View):
"""API for retrieving a single l7 policy.
"""
url_regex = r'lbaas/l7policies/(?P<l7_policy_id>[^/]+)/$'
@rest_utils.ajax()
def get(self, request, l7_policy_id):
"""Get a specific l7 policy.
If the param 'includeChildResources' is passed in as a truthy value,
the details of all resources that exist under the l7 policy will be
returned along with the l7 policy details.
http://localhost/api/lbaas/l7policies/cc758c90-3d98-4ea1-af44-aab405c9c915
"""
conn = _get_sdk_connection(request)
l7_policy = conn.load_balancer.find_l7_policy(l7_policy_id)
l7_policy = _get_sdk_object_dict(l7_policy)
if request.GET.get('includeChildResources'):
resources = {}
if l7_policy.get('rules'):
l7_rules_list = _sdk_object_to_list(
conn.load_balancer.l7_rules(l7_policy_id))
l7_policy['rules'] = l7_rules_list
resources['l7policy'] = l7_policy
return resources
else:
return l7_policy
@rest_utils.ajax()
def put(self, request, l7_policy_id):
"""Edit a l7 policy as well as any resources below it.
"""
kwargs = {'l7_policy_id': l7_policy_id}
update_l7_policy(request, **kwargs)
@rest_utils.ajax()
def delete(self, request, l7_policy_id):
"""Delete a specific l7 policy.
http://localhost/api/lbaas/l7policies/cc758c90-3d98-4ea1-af44-aab405c9c915
"""
conn = _get_sdk_connection(request)
conn.load_balancer.delete_l7_policy(l7_policy_id)
@urls.register
class L7Rules(generic.View):
"""API for load balancer l7 rules.
"""
url_regex = r'lbaas/l7policies/(?P<l7_policy_id>[^/]+)/l7rules/$'
@rest_utils.ajax()
def get(self, request, l7_policy_id):
"""List of l7 rules for the current project.
The listing result is an object with property "items".
"""
conn = _get_sdk_connection(request)
l7_rule_list = _sdk_object_to_list(conn.load_balancer.l7_rules(
l7_policy_id))
return {'items': l7_rule_list}
@rest_utils.ajax()
def post(self, request, l7_policy_id):
"""Create a new l7 rule.
Creates a new l7 rule as well as other optional resources such as
l7 rules.
"""
kwargs = {'l7_policy_id': l7_policy_id}
return create_l7_rule(request, **kwargs)
@urls.register
class L7Rule(generic.View):
"""API for retrieving a single l7 rule.
"""
url_regex = (
r'lbaas/l7policies/(?P<l7_policy_id>[^/]+)'
r'/l7rules/(?P<l7_rule_id>[^/]+)/$'
)
@rest_utils.ajax()
def get(self, request, l7_rule_id, l7_policy_id):
"""Get a specific l7 rule."""
conn = _get_sdk_connection(request)
l7_rule = conn.load_balancer.find_l7_rule(l7_rule_id, l7_policy_id)
return _get_sdk_object_dict(l7_rule)
@rest_utils.ajax()
def put(self, request, l7_rule_id, l7_policy_id):
"""Edit a specific l7 rule."""
kwargs = {'l7_rule_id': l7_rule_id, 'l7_policy_id': l7_policy_id}
update_l7_rule(request, **kwargs)
@rest_utils.ajax()
def delete(self, request, l7_rule_id, l7_policy_id):
"""Delete a specific l7 rule."""
conn = _get_sdk_connection(request)
conn.load_balancer.delete_l7_rule(l7_rule_id, l7_policy_id)
@urls.register
class Pools(generic.View):
"""API for load balancer pools.

View File

@ -46,6 +46,16 @@
createListener: createListener,
editListener: editListener,
deleteListener: deleteListener,
getL7Policies: getL7Policies,
getL7Policy: getL7Policy,
createL7Policy: createL7Policy,
editL7Policy: editL7Policy,
deleteL7Policy: deleteL7Policy,
getL7Rules: getL7Rules,
getL7Rule: getL7Rule,
createL7Rule: createL7Rule,
editL7Rule: editL7Rule,
deleteL7Rule: deleteL7Rule,
getPools: getPools,
getPool: getPool,
createPool: createPool,
@ -108,8 +118,8 @@
* @description
* Delete a single load balancer by ID
* @param {string} id
* @param {boolean} quiet
* Specifies the id of the load balancer to delete.
* @param {boolean} quiet
*/
function deleteLoadBalancer(id, quiet) {
@ -231,8 +241,8 @@
* @description
* Delete a single listener by ID
* @param {string} id
* @param {boolean} quiet
* Specifies the id of the listener to delete.
* @param {boolean} quiet
*/
function deleteListener(id, quiet) {
@ -333,8 +343,8 @@
* @description
* Delete a single pool by ID
* @param {string} id
* @param {boolean} quiet
* Specifies the id of the pool to delete.
* @param {boolean} quiet
*/
function deletePool(id, quiet) {
@ -344,6 +354,198 @@
});
}
// L7 Policies
/**
* @name horizon.app.core.openstack-service-api.lbaasv2.getL7Policies
* @description
* Get the list of l7 policies.
* If a listener ID is passed as a parameter, the returning list of
* l7 policies will be filtered to include only those l7 policies under the
* specified listener.
* @param {string} listenerId
* Specifies the id of the listener to request l7policies for.
*
* The listing result is an object with property "items". Each item is
* a l7 policy.
*/
function getL7Policies(listenerId) {
var params = $.extend({},
{
listenerId: listenerId
}
);
if (!$.isEmptyObject(params)) {
params = { params: params };
}
return apiService.get('/api/lbaas/l7policies/', params)
.error(function () {
toastService.add('error', gettext('Unable to retrieve l7 policies.'));
});
}
/**
* @name horizon.app.core.openstack-service-api.lbaasv2.getL7Policy
* @description
* Get a single L7Policy by ID.
* @param {string} id
* Specifies the id of the l7 policy to request.
* @param {boolean} includeChildResources
* If truthy, all child resources below the l7 policy will be included in the response.
*/
function getL7Policy(id, includeChildResources) {
var params = includeChildResources
? {params: {includeChildResources: includeChildResources}}
: {};
return apiService.get('/api/lbaas/l7policies/' + id + '/', params)
.error(function () {
toastService.add('error', gettext('Unable to retrieve l7 policy.'));
});
}
/**
* @name horizon.app.core.openstack-service-api.lbaasv2.createL7Policy
* @description
* Create a new l7 policy
* @param {object} spec
* Specifies the data used to create the new l7 policy.
*/
function createL7Policy(spec) {
return apiService.post('/api/lbaas/l7policies/', spec)
.error(function () {
toastService.add('error', gettext('Unable to create l7 policy.'));
});
}
/**
* @name horizon.app.core.openstack-service-api.lbaasv2.editL7Policy
* @description
* Edit a l7 policy
* @param {string} id
* Specifies the id of the l7 policy to update.
* @param {object} spec
* Specifies the data used to update the l7 policy.
*/
function editL7Policy(id, spec) {
return apiService.put('/api/lbaas/l7policies/' + id + '/', spec)
.error(function () {
toastService.add('error', gettext('Unable to update l7 policy.'));
});
}
/**
* @name horizon.app.core.openstack-service-api.lbaasv2.deleteL7Policy
* @description
* Delete a single l7 policy by ID
* @param {string} id
* Specifies the id of the l7 policy to delete.
* @param {boolean} quiet
*/
function deleteL7Policy(id, quiet) {
var promise = apiService.delete('/api/lbaas/l7policies/' + id + '/');
return quiet ? promise : promise.error(function () {
toastService.add('error', gettext('Unable to delete l7 policy.'));
});
}
// L7 Rules
/**
* @name horizon.app.core.openstack-service-api.lbaasv2.getL7Rules
* @description
* Get the list of l7 rules under the specified l7 policy.
* @param {string} l7policyId
* Specifies the id of the l7 policy to request l7rules for.
*
* The listing result is an object with property "items".
* Each item is a l7 rule.
*/
function getL7Rules(l7policyId) {
return apiService.get('/api/lbaas/l7policies/' + l7policyId + '/l7rules/')
.error(function () {
toastService.add('error', gettext('Unable to retrieve l7 rules.'));
});
}
/**
* @name horizon.app.core.openstack-service-api.lbaasv2.getL7Rule
* @description
* Get a single L7Rule by ID.
* @param {string} l7policyId
* Specifies the id of the l7 policy the l7 rule belongs to.
* @param {string} l7ruleId
* Specifies the id of the l7 rule to request.
*/
function getL7Rule(l7policyId, l7ruleId) {
return apiService.get('/api/lbaas/l7policies/' + l7policyId + '/l7rules/' + l7ruleId + '/')
.error(function () {
toastService.add('error', gettext('Unable to retrieve l7 rule.'));
});
}
/**
* @name horizon.app.core.openstack-service-api.lbaasv2.createL7Rule
* @description
* Create a new l7 rule
* @param {string} l7policyId
* Specifies the id of the l7 policy the l7 rule belongs to.
* @param {object} spec
* Specifies the data used to create the new l7 rule.
*/
function createL7Rule(l7policyId, spec) {
return apiService.post('/api/lbaas/l7policies/' + l7policyId + '/l7rules/', spec)
.error(function () {
toastService.add('error', gettext('Unable to create l7 rule.'));
});
}
/**
* @name horizon.app.core.openstack-service-api.lbaasv2.editL7Rule
* @description
* Edit a l7 rule
* @param {string} l7policyId
* Specifies the id of the l7 policy the l7 rule belongs to.
* @param {string} l7ruleId
* Specifies the id of the l7 rule to update.
* @param {object} spec
* Specifies the data used to update the l7 rule.
*/
function editL7Rule(l7policyId, l7ruleId, spec) {
return apiService.put('/api/lbaas/l7policies/' + l7policyId +
'/l7rules/' + l7ruleId + '/', spec)
.error(function () {
toastService.add('error', gettext('Unable to update l7 rule.'));
});
}
/**
* @name horizon.app.core.openstack-service-api.lbaasv2.deleteL7Rule
* @description
* Delete a single l7 rule by ID
* @param {string} l7policyId
* Specifies the id of the l7 policy the l7 rule belongs to.
* @param {string} l7ruleId
* Specifies the id of the l7 rule to delete.
* @param {boolean} quiet
*/
function deleteL7Rule(l7policyId, l7ruleId, quiet) {
var promise = apiService.delete('/api/lbaas/l7policies/' + l7policyId +
'/l7rules/' + l7ruleId + '/');
return quiet ? promise : promise.error(function () {
toastService.add('error', gettext('Unable to delete l7 rule.'));
});
}
// Members
/**
@ -493,8 +695,8 @@
* @description
* Delete a single health monitor by ID
* @param {string} id
* @param {boolean} quiet
* Specifies the id of the health monitor to delete.
* @param {boolean} quiet
*/
function deleteHealthMonitor(id, quiet) {

View File

@ -90,6 +90,65 @@
error: 'Unable to retrieve listener.',
testInput: [ '1234', false ]
},
{
func: 'getL7Policies',
method: 'get',
path: '/api/lbaas/l7policies/',
error: 'Unable to retrieve l7 policies.',
testInput: [ '1234' ],
data: { params: { listenerId: '1234' } }
},
{
func: 'getL7Policies',
method: 'get',
path: '/api/lbaas/l7policies/',
data: {},
error: 'Unable to retrieve l7 policies.'
},
{
func: 'getL7Policy',
method: 'get',
path: '/api/lbaas/l7policies/1234/',
data: { params: { includeChildResources: true } },
error: 'Unable to retrieve l7 policy.',
testInput: [ '1234', true ]
},
{
func: 'getL7Policy',
method: 'get',
path: '/api/lbaas/l7policies/1234/',
data: {},
error: 'Unable to retrieve l7 policy.',
testInput: [ '1234', false ]
},
{
func: 'deleteL7Policy',
method: 'delete',
path: '/api/lbaas/l7policies/1234/',
error: 'Unable to delete l7 policy.',
testInput: [ '1234' ]
},
{
func: 'getL7Rules',
method: 'get',
path: '/api/lbaas/l7policies/1234/l7rules/',
error: 'Unable to retrieve l7 rules.',
testInput: [ '1234' ]
},
{
func: 'getL7Rule',
method: 'get',
path: '/api/lbaas/l7policies/1234/l7rules/5678/',
error: 'Unable to retrieve l7 rule.',
testInput: [ '1234', '5678' ]
},
{
func: 'deleteL7Rule',
method: 'delete',
path: '/api/lbaas/l7policies/1234/l7rules/5678/',
error: 'Unable to delete l7 rule.',
testInput: [ '1234', '5678' ]
},
{
func: 'getPools',
method: 'get',
@ -249,6 +308,38 @@
error: 'Unable to delete listener.',
testInput: [ '1234' ]
},
{
func: 'createL7Policy',
method: 'post',
path: '/api/lbaas/l7policies/',
error: 'Unable to create l7 policy.',
data: { name: 'l7policy-1' },
testInput: [ { name: 'l7policy-1' } ]
},
{
func: 'editL7Policy',
method: 'put',
path: '/api/lbaas/l7policies/1234/',
error: 'Unable to update l7 policy.',
data: { name: 'l7policy-1' },
testInput: [ '1234', { name: 'l7policy-1' } ]
},
{
func: 'createL7Rule',
method: 'post',
path: '/api/lbaas/l7policies/1234/l7rules/',
error: 'Unable to create l7 rule.',
data: { name: 'l7rule-1' },
testInput: [ '1234', { name: 'l7rule-1' } ]
},
{
func: 'editL7Rule',
method: 'put',
path: '/api/lbaas/l7policies/1234/l7rules/5678/',
error: 'Unable to update l7 rule.',
data: { name: 'l7rule-1' },
testInput: [ '1234', '5678', { name: 'l7rule-1' } ]
},
{
func: 'createPool',
method: 'post',
@ -301,6 +392,16 @@
expect(service.deleteListener("whatever", true)).toBe("promise");
});
it('supresses the error if instructed for deleteL7Policy', function() {
spyOn(apiService, 'delete').and.returnValue("promise");
expect(service.deleteL7Policy("whatever", true)).toBe("promise");
});
it('supresses the error if instructed for deleteL7Rule', function() {
spyOn(apiService, 'delete').and.returnValue("promise");
expect(service.deleteL7Rule("whatever", "whatever", true)).toBe("promise");
});
it('supresses the error if instructed for deletePool', function() {
spyOn(apiService, 'delete').and.returnValue("promise");
expect(service.deletePool("whatever", true)).toBe("promise");

View File

@ -0,0 +1,74 @@
/*
* Copyright 2018 Walmart.
*
* 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.dashboard.project.lbaasv2.l7policies')
.factory('horizon.dashboard.project.lbaasv2.l7policies.actions.create', createService);
createService.$inject = [
'horizon.dashboard.project.lbaasv2.l7policies.resourceType',
'horizon.framework.util.actions.action-result.service',
'$q',
'horizon.dashboard.project.lbaasv2.workflow.modal',
'horizon.app.core.openstack-service-api.policy',
'horizon.framework.util.i18n.gettext'
];
/**
* @ngDoc factory
* @name horizon.dashboard.project.lbaasv2.l7policies.actions.createService
*
* @description
* Provides the service for creating a l7policy resource.
*
* @param resourceType The l7policy resource type.
* @param actionResultService The horizon action result service.
* @param $q The angular service for promises.
* @param workflowModal The LBaaS workflow modal service.
* @param policy The horizon policy service.
* @param gettext The horizon gettext function for translation.
*
* @returns The l7policy create service.
*/
function createService(
resourceType, actionResultService,
$q, workflowModal, policy, gettext
) {
return workflowModal.init({
controller: 'CreateL7PolicyWizardController',
message: gettext('A new l7 policy is being created.'),
handle: handle,
allowed: allowed
});
//////////////
function allowed() {
return $q.all([
policy.ifAllowed({ rules: [['neutron', 'create_l7policy']] })
]);
}
function handle(response) {
return actionResultService.getActionResult()
.created(resourceType, response.data.id)
.result;
}
}
})();

View File

@ -0,0 +1,65 @@
/*
* Copyright 2018 Walmart.
*
* 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('LBaaS v2 Create l7policy Action Service', function() {
var policy, service;
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) {
$provide.value('$modal', {
open: function() {
return {
result: {
then: function(func) {
func({ data: { id: 'listener1' } });
}
}
};
}
});
$provide.value('$routeParams', {});
}));
beforeEach(inject(function ($injector) {
policy = $injector.get('horizon.app.core.openstack-service-api.policy');
service = $injector.get('horizon.dashboard.project.lbaasv2.l7policies.actions.create');
}));
it('should not allow creating a l7policy if listenerId is not present', function() {
spyOn(policy, 'ifAllowed').and.returnValue(true);
var allowed = service.allowed();
permissionShouldFail(allowed);
});
it('should handle the action result properly', function() {
var result = service.handle({data: {id: 1}});
expect(result.created[0].id).toBe(1);
});
function permissionShouldFail(permissions) {
permissions.then(
function() {
expect(false).toBe(true);
},
function() {
expect(true).toBe(true);
});
}
});
})();

View File

@ -0,0 +1,45 @@
/*
* Copyright 2018 Walmart.
*
* 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.dashboard.project.lbaasv2.l7policies')
.controller('CreateL7PolicyWizardController', CreateL7PolicyWizardController);
CreateL7PolicyWizardController.$inject = [
'$scope',
'$routeParams',
'horizon.dashboard.project.lbaasv2.workflow.model',
'horizon.dashboard.project.lbaasv2.workflow.workflow',
'horizon.framework.util.i18n.gettext'
];
function CreateL7PolicyWizardController($scope, $routeParams, model, workflowService, gettext) {
var loadbalancerId = $routeParams.loadbalancerId;
var listenerId = $routeParams.listenerId;
var scope = $scope;
scope.model = model;
scope.submit = scope.model.submit;
scope.workflow = workflowService(
gettext('Create L7 Policy'),
'fa fa-cloud-download',
['l7policy']
);
scope.model.initialize('l7policy', false, loadbalancerId, listenerId);
}
})();

View File

@ -0,0 +1,63 @@
/*
* Copyright 2018 Walmart.
*
* 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('LBaaS v2 Create L7Policy Wizard Controller', function() {
var ctrl;
var model = {
submit: function() {
return 'created';
},
initialize: angular.noop
};
var workflow = function() {
return 'foo';
};
var scope = {
launchContext: {id: '1234'}
};
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function ($provide) {
$provide.value('horizon.dashboard.project.lbaasv2.workflow.model', model);
$provide.value('horizon.dashboard.project.lbaasv2.workflow.workflow', workflow);
}));
beforeEach(inject(function ($controller) {
spyOn(model, 'initialize');
ctrl = $controller('CreateL7PolicyWizardController', { $scope: scope });
}));
it('defines the controller', function() {
expect(ctrl).toBeDefined();
});
it('calls initialize on the given model', function() {
expect(model.initialize).toHaveBeenCalled();
});
it('sets scope.workflow to the given workflow', function() {
expect(scope.workflow).toBe('foo');
});
it('defines scope.submit', function() {
expect(scope.submit).toBeDefined();
expect(scope.submit()).toBe('created');
});
});
})();

View File

@ -0,0 +1,135 @@
/*
* Copyright 2018 Walmart.
*
* 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.dashboard.project.lbaasv2.l7policies')
.factory('horizon.dashboard.project.lbaasv2.l7policies.actions.delete', deleteService);
deleteService.$inject = [
'horizon.dashboard.project.lbaasv2.l7policies.resourceType',
'horizon.framework.util.actions.action-result.service',
'$location',
'horizon.framework.widgets.modal.deleteModalService',
'horizon.app.core.openstack-service-api.lbaasv2',
'horizon.framework.util.i18n.gettext',
'horizon.app.core.openstack-service-api.policy'
];
/**
* @ngDoc factory
* @name horizon.dashboard.project.lbaasv2.l7policies.actions.deleteService
*
* @description
* Brings up the delete l7policy confirmation modal dialog.
* On submit, deletes selected l7policy.
* On cancel, does nothing.
*
* @param resourceType The l7policy resource type.
* @param actionResultService The horizon action result service.
* @param $location The angular $location service.
* @param deleteModal The horizon delete modal service.
* @param api The LBaaS v2 API service.
* @param gettext The horizon gettext function for translation.
* @param policy The horizon policy service.
*
* @returns The l7policy delete service.
*/
function deleteService(
resourceType, actionResultService, $location,
deleteModal, api, gettext, policy
) {
var loadbalancerId, listenerId;
var service = {
perform: perform,
allowed: allowed,
deleteResult: deleteResult // exposed just for testing
};
return service;
//////////////
function allowed(/*item*/) {
// This rule is made up and should therefore always pass. I assume at some point there
// will be a valid rule similar to this that we will want to use.
return policy.ifAllowed({ rules: [['neutron', 'delete_l7policy']] });
}
function perform(items, scope) {
var context = { };
var l7policies = angular.isArray(items) ? items : [items];
context.labels = labelize(l7policies.length);
context.deleteEntity = deleteItem;
l7policies.map(function(item) {
loadbalancerId = item.loadbalancerId;
listenerId = item.listenerId;
});
return deleteModal.open(scope, l7policies, context).then(deleteResult);
}
function labelize(count) {
return {
title: ngettext(
'Confirm Delete L7 Policy',
'Confirm Delete L7 Policies', count),
message: ngettext(
'You have selected "%s". Deleted L7 Policy is not recoverable.',
'You have selected "%s". Deleted L7 Policies are not recoverable.', count),
submit: ngettext(
'Delete L7 Policy',
'Delete L7 Policies', count),
success: ngettext(
'Deleted L7 Policy: %s.',
'Deleted L7 Policies: %s.', count),
error: ngettext(
'Unable to delete L7 Policy: %s.',
'Unable to delete L7 Policies: %s.', count)
};
}
function deleteResult(deleteModalResult) {
// To make the result of this action generically useful, reformat the return
// from the deleteModal into a standard form
var actionResult = actionResultService.getActionResult();
deleteModalResult.pass.forEach(function markDeleted(item) {
actionResult.deleted(resourceType, item.context.id);
});
deleteModalResult.fail.forEach(function markFailed(item) {
actionResult.failed(resourceType, item.context.id);
});
if (actionResult.result.failed.length === 0 && actionResult.result.deleted.length > 0) {
var path = 'project/load_balancer/' + loadbalancerId +
'/listeners/' + listenerId;
$location.path(path);
}
return actionResult.result;
}
function deleteItem(id) {
return api.deleteL7Policy(id, true);
}
}
})();

View File

@ -0,0 +1,103 @@
/*
* Copyright 2018 Walmart.
*
* 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('LBaaS v2 L7Policy Delete Service', function() {
beforeEach(module('horizon.app.core'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module('horizon.framework'));
var deleteModalService, service, lbaasv2API, policyAPI, $location;
beforeEach(inject(function($injector) {
service = $injector.get('horizon.dashboard.project.lbaasv2.l7policies.actions.delete');
lbaasv2API = $injector.get('horizon.app.core.openstack-service-api.lbaasv2');
deleteModalService = $injector.get('horizon.framework.widgets.modal.deleteModalService');
policyAPI = $injector.get('horizon.app.core.openstack-service-api.policy');
$location = $injector.get('$location');
}));
describe('perform method', function() {
beforeEach(function () {
// just need for this to return something that looks like a promise but does nothing
spyOn(deleteModalService, 'open').and.returnValue({then: angular.noop});
});
it('should open the modal with correct label', function () {
service.perform({name: 'spam'});
var labels = deleteModalService.open.calls.argsFor(0)[2].labels;
expect(deleteModalService.open).toHaveBeenCalled();
angular.forEach(labels, function eachLabel(label) {
expect(label.toLowerCase()).toContain('l7 policy');
});
});
it('should open the delete modal with correct entities', function () {
service.perform([{name: 'one'}, {name: 'two'}]);
var entities = deleteModalService.open.calls.argsFor(0)[1];
expect(deleteModalService.open).toHaveBeenCalled();
expect(entities.length).toEqual(2);
});
it('should pass in a function that deletes a l7 policy', function () {
spyOn(lbaasv2API, 'deleteL7Policy').and.callFake(angular.noop);
service.perform({id: 1, name: 'one'});
var contextArg = deleteModalService.open.calls.argsFor(0)[2];
var deleteFunction = contextArg.deleteEntity;
deleteFunction(1);
expect(lbaasv2API.deleteL7Policy).toHaveBeenCalledWith(1, true);
});
});
it('should handle the action result properly', function() {
spyOn($location, 'path');
spyOn(deleteModalService, 'open').and.returnValue({then: angular.noop});
spyOn(lbaasv2API, 'deleteL7Policy').and.callFake(angular.noop);
service.perform({loadbalancerId: 1, listenerId: 2, id: 1, name: 'one'});
var result = service.deleteResult({
fail: [],
pass: [{
context: {
id: 1
}
}]
});
var path = 'project/load_balancer/1/listeners/2';
expect($location.path).toHaveBeenCalledWith(path);
expect(result.deleted[0].id).toBe(1);
result = service.deleteResult({
pass: [],
fail: [{
context: {
id: 1
}
}]
});
expect(result.failed[0].id).toBe(1);
});
describe('allow method', function() {
it('should use default policy if batch action', function () {
spyOn(policyAPI, 'ifAllowed');
service.allowed();
expect(policyAPI.ifAllowed).toHaveBeenCalled();
});
}); // end of allowed
}); // end of delete
})();

View File

@ -0,0 +1,69 @@
/*
* Copyright 2018 Walmart.
*
* 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.dashboard.project.lbaasv2.l7policies')
.factory('horizon.dashboard.project.lbaasv2.l7policies.actions.edit', editService);
editService.$inject = [
'horizon.dashboard.project.lbaasv2.l7policies.resourceType',
'horizon.framework.util.actions.action-result.service',
'horizon.dashboard.project.lbaasv2.workflow.modal',
'horizon.app.core.openstack-service-api.policy',
'horizon.framework.util.i18n.gettext'
];
/**
* @ngDoc factory
* @name horizon.dashboard.project.lbaasv2.l7policies.actions.editService
*
* @description
* Provides the service for editing a l7policy resource.
*
* @param resourceType The l7policy resource type.
* @param actionResultService The horizon action result service.
* @param workflowModal The LBaaS workflow modal service.
* @param policy The horizon policy service.
* @param gettext The horizon gettext function for translation.
*
* @returns The l7policy edit service.
*/
function editService(
resourceType, actionResultService, workflowModal, policy, gettext
) {
return workflowModal.init({
controller: 'EditL7PolicyWizardController',
message: gettext('The l7policy has been updated.'),
handle: handle,
allowed: allowed
});
function allowed(/*item*/) {
return policy.ifAllowed({ rules: [['neutron', 'update_l7policy']] });
}
function handle(response) {
return actionResultService.getActionResult()
.updated(resourceType, response.config.data.l7policy.id)
.result;
}
}
})();

View File

@ -0,0 +1,55 @@
/*
* Copyright 2018 Walmart.
*
* 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('LBaaS v2 Edit L7Policy Action Service', function() {
var policy, service;
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) {
$provide.value('$modal', {
open: function() {
return {
result: {
then: function(func) {
func({ data: { id: 'l7policy1' } });
}
}
};
}
});
}));
beforeEach(inject(function ($injector) {
policy = $injector.get('horizon.app.core.openstack-service-api.policy');
service = $injector.get('horizon.dashboard.project.lbaasv2.l7policies.actions.edit');
}));
it('should check policy to allow editing a l7policy', function() {
spyOn(policy, 'ifAllowed').and.returnValue(true);
expect(service.allowed()).toBe(true);
expect(policy.ifAllowed).toHaveBeenCalledWith({rules: [['neutron', 'update_l7policy']]});
});
it('should handle the action result properly', function() {
var result = service.handle({config: {data: {l7policy: {id: 1}}}});
expect(result.updated[0].id).toBe(1);
});
});
})();

View File

@ -0,0 +1,60 @@
/*
* Copyright 2018 Walmart.
*
* 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.dashboard.project.lbaasv2.loadbalancers')
.controller('EditL7PolicyWizardController', EditL7PolicyWizardController);
EditL7PolicyWizardController.$inject = [
'$scope',
'$routeParams',
'$q',
'horizon.dashboard.project.lbaasv2.workflow.model',
'horizon.dashboard.project.lbaasv2.workflow.workflow',
'horizon.framework.util.i18n.gettext'
];
/**
* @ngdoc controller
* @name EditL7PolicyWizardController
*
* @description
* Controller for the LBaaS v2 edit l7policy wizard.
*
* @param $scope The angular scope object.
* @param $routeParams The angular $routeParams service.
* @param $q The angular service for promises.
* @param model The LBaaS V2 workflow model service.
* @param workflowService The LBaaS V2 workflow service.
* @param gettext The horizon gettext function for translation.
* @returns undefined
*/
function EditL7PolicyWizardController($scope, $routeParams, $q, model, workflowService, gettext) {
var scope = $scope;
var loadbalancerId = $routeParams.loadbalancerId;
var listenerId = $routeParams.listenerId;
scope.model = model;
scope.submit = scope.model.submit;
scope.workflow = workflowService(
gettext('Update L7 Policy'),
'fa fa-pencil', ['l7policy']);
scope.model.initialize('l7policy', scope.launchContext.id, loadbalancerId, listenerId);
}
})();

View File

@ -0,0 +1,77 @@
/*
* Copyright 2018 Walmart.
*
* 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('LBaaS v2 Edit L7Policy Wizard Controller', function() {
var ctrl, workflowSpy, $q, scope;
var model = {
submit: function() {
return 'updated';
},
initialize: function() {
var defer = $q.defer();
defer.resolve();
return defer.promise;
}
};
var workflow = {
steps: [{id: 'l7policy'}],
append: angular.noop
};
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function ($provide) {
workflowSpy = jasmine.createSpy('workflow').and.returnValue(workflow);
$provide.value('horizon.dashboard.project.lbaasv2.workflow.model', model);
$provide.value('horizon.dashboard.project.lbaasv2.workflow.workflow', workflowSpy);
}));
beforeEach(inject(function ($controller, $injector) {
$q = $injector.get('$q');
scope = $injector.get('$rootScope').$new();
scope.launchContext = { id: 'l7policyId' };
spyOn(model, 'initialize').and.callThrough();
ctrl = $controller('EditL7PolicyWizardController', {
$scope: scope,
$routeParams: {loadbalancerId: 'loadbalancerId', listenerId: 'listenerId'}});
}));
it('defines the controller', function() {
expect(ctrl).toBeDefined();
});
it('calls initialize on the given model', function() {
expect(model.initialize).toHaveBeenCalledWith(
'l7policy', 'l7policyId', 'loadbalancerId', 'listenerId');
});
it('sets scope.workflow to the given workflow', function() {
expect(scope.workflow).toBe(workflow);
});
it('initializes workflow with correct properties', function() {
expect(workflowSpy).toHaveBeenCalledWith('Update L7 Policy',
'fa fa-pencil', ['l7policy']);
});
it('defines scope.submit', function() {
expect(scope.submit).toBe(model.submit);
expect(scope.submit()).toBe('updated');
});
});
})();

View File

@ -0,0 +1,109 @@
/*
* Copyright 2018 Walmart.
*
* 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.dashboard.project.lbaasv2.l7policies')
.controller('L7PolicyDetailController', L7PolicyDetailController);
L7PolicyDetailController.$inject = [
'loadbalancer',
'listener',
'l7policy',
'horizon.dashboard.project.lbaasv2.loadbalancers.service',
'horizon.dashboard.project.lbaasv2.l7policies.resourceType',
'horizon.framework.conf.resource-type-registry.service',
'horizon.framework.widgets.modal-wait-spinner.service',
'$q'
];
/**
* @ngdoc controller
* @name L7PolicyDetailController
*
* @description
* Controller for the LBaaS v2 l7policy detail page.
*
* @param loadbalancer The loadbalancer object.
* @param listener The listener object.
* @param l7policy The l7policy object.
* @param loadBalancersService The LBaaS v2 load balancers service.
* @param resourceType The load balancer resource type.
* @param typeRegistry The horizon type registry service.
* @param spinnerService The horizon modal wait spinner service.
* @param $q The angular service for promises.
*
* @returns undefined
*/
function L7PolicyDetailController(
loadbalancer, listener, l7policy, loadBalancersService,
resourceType, typeRegistry, spinnerService, $q
) {
var ctrl = this;
ctrl.operatingStatus = loadBalancersService.operatingStatus;
ctrl.provisioningStatus = loadBalancersService.provisioningStatus;
ctrl.l7policyAction = loadBalancersService.l7policyAction;
ctrl.loadbalancer = loadbalancer;
ctrl.listener = listener;
ctrl.l7policy = l7policy;
ctrl.listFunctionExtraParams = {
loadbalancerId: ctrl.loadbalancer.id,
listenerId: ctrl.listener.id,
l7policyId: ctrl.l7policy.id
};
ctrl.resourceType = typeRegistry.getResourceType(resourceType);
ctrl.context = {};
ctrl.context.identifier = l7policy.id;
ctrl.resultHandler = actionResultHandler;
function actionResultHandler(returnValue) {
return $q.when(returnValue, actionSuccessHandler);
}
function loadData(response) {
spinnerService.hideModalSpinner();
ctrl.showDetails = true;
ctrl.resourceType.initActions();
ctrl.l7policy = response.data;
ctrl.l7policy.loadbalancerId = ctrl.loadbalancer.id;
ctrl.l7policy.listenerId = ctrl.listener.id;
}
function actionSuccessHandler(result) {
// The action has completed (for whatever "complete" means to that
// action. Notice the view doesn't really need to know the semantics of the
// particular action because the actions return data in a standard form.
// That return includes the id and type of each created, updated, deleted
// and failed item.
// Currently just refreshes the display each time.
if (result) {
if (result.failed.length === 0 && result.deleted.length > 0) {
// handle a race condition where the resource is already deleted
return;
}
spinnerService.showModalSpinner(gettext('Please Wait'));
ctrl.showDetails = false;
ctrl.context.loadPromise = ctrl.resourceType.load(ctrl.context.identifier);
ctrl.context.loadPromise.then(loadData);
}
}
}
})();

View File

@ -0,0 +1,112 @@
/*
* Copyright 2018 Walmart.
*
* 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('LBaaS v2 L7Policy Detail Controller', function() {
var deferred, service, ctrl, scope, $timeout, $q, actionResultService;
///////////////////////
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) {
$provide.value('$uibModal', {});
}));
beforeEach(inject(function($controller, $rootScope, _$q_, _$timeout_) {
$q = _$q_;
deferred = $q.defer();
service = {
getResourceType: function() {
return {
load: function() { return deferred.promise; },
parsePath: function() { return 'my-context'; },
itemName: function() { return 'A name'; },
initActions: angular.noop
};
},
getDefaultDetailsTemplateUrl: angular.noop
};
actionResultService = {
getIdsOfType: function() { return []; }
};
$timeout = _$timeout_;
scope = $rootScope.$new();
ctrl = $controller('L7PolicyDetailController', {
$scope: scope,
loadbalancer: { id: '123' },
listener: { id: '123' },
l7policy: { id: '123' },
'horizon.framework.conf.resource-type-registry.service': service,
'horizon.framework.util.actions.action-result.service': actionResultService,
'horizon.framework.widgets.modal-wait-spinner.service': {
showModalSpinner: angular.noop,
hideModalSpinner: angular.noop
}
});
}));
it('should create a controller', function() {
expect(ctrl).toBeDefined();
expect(ctrl.loadbalancer).toBeDefined();
expect(ctrl.listener).toBeDefined();
expect(ctrl.l7policy).toBeDefined();
});
describe('resultHandler', function() {
it('handles empty results', function() {
var result = $q.defer();
result.resolve({failed: [], deleted: []});
ctrl.resultHandler(result.promise);
$timeout.flush();
expect(ctrl.showDetails).not.toBe(true);
});
it('handles falsy results', function() {
var result = $q.defer();
result.resolve(false);
ctrl.resultHandler(result.promise);
$timeout.flush();
expect(ctrl.showDetails).not.toBe(true);
});
it('handles matched results', function() {
spyOn(actionResultService, 'getIdsOfType').and.returnValue([1, 2, 3]);
var result = $q.defer();
result.resolve({some: 'thing', failed: [], deleted: []});
ctrl.resultHandler(result.promise);
deferred.resolve({data: {some: 'data'}});
$timeout.flush();
expect(ctrl.showDetails).toBe(true);
});
it('handles delete race condition', function() {
spyOn(actionResultService, 'getIdsOfType').and.returnValue([1, 2, 3]);
var result = $q.defer();
result.resolve({some: 'thing', failed: [], deleted: [{id: 1}]});
ctrl.resultHandler(result.promise);
deferred.resolve({data: {some: 'data'}});
$timeout.flush();
expect(ctrl.showDetails).toBe(undefined);
});
});
});
})();

View File

@ -0,0 +1,61 @@
<div class="page-header">
<ol class="breadcrumb">
<li class="breadcrumb-item-truncate"><translate>Project</translate></li>
<li class="breadcrumb-item-truncate"><translate>Network</translate></li>
<li class="breadcrumb-item-truncate"><a href="project/load_balancer/"><translate>Load Balancers</translate></a></li>
<li class="breadcrumb-item-truncate"><a href="project/load_balancer/{$ ::ctrl.loadbalancer.id $}">{$ ::(ctrl.loadbalancer.name || ctrl.loadbalancer.id) $}</a></li>
<li class="breadcrumb-item-truncate"><a href="project/load_balancer/{$ ::ctrl.loadbalancer.id $}/listeners/{$ ::ctrl.listener.id $}">{$ ::(ctrl.listener.name || ctrl.listener.id) $}</a></li>
<li class="breadcrumb-item-truncate active">{$ ::(ctrl.l7policy.name || ctrl.l7policy.id) $}</li>
</ol>
<div class="row">
<div class="col-xs-12 col-sm-9 text-left">
<ul class="list-inline">
<li>
<strong translate>Action</strong>
{$ ::ctrl.l7policy.action | decode:ctrl.l7policyAction $}
</li>
<li>
<strong translate>Operating Status</strong>
{$ ctrl.l7policy.operating_status | decode:ctrl.operatingStatus $}
</li>
<li>
<strong translate>Provisioning Status</strong>
{$ ctrl.l7policy.provisioning_status | decode:ctrl.provisioningStatus $}
</li>
<li>
<strong translate>Admin State Up</strong>
{$ ctrl.l7policy.admin_state_up | yesno $}
</li>
</ul>
</div>
<div class="col-xs-12 col-sm-3 text-right details-item-actions">
<actions allowed="ctrl.resourceType.itemActions"
type="row"
item="ctrl.l7policy"
ng-if="ctrl.l7policy"
class="actions_column pull-right"
result-handler="ctrl.resultHandler"></actions>
</div>
</div>
</div>
<uib-tabset class="octavia-tabset">
<uib-tab heading="{$ 'Overview' | translate $}">
<div class="col-md-6 detail">
<hz-resource-property-list
resource-type-name="OS::Octavia::L7Policy"
cls="dl-horizontal"
item="ctrl.l7policy"
property-groups="[[
'id', 'name', 'description', 'project_id', 'created_at', 'updated_at',
'redirect_pool_id', 'redirect_url', 'position', 'listener_id']]">
</hz-resource-property-list>
</div>
</uib-tab>
<uib-tab heading="{$ 'L7 Rules' | translate $}">
<hz-resource-table resource-type-name="OS::Octavia::L7Rule"
track-by="trackBy"
list-function-extra-params="ctrl.listFunctionExtraParams">
</hz-resource-table>
</uib-tab>
</uib-tabset>

View File

@ -0,0 +1,9 @@
<hz-resource-property-list
resource-type-name="OS::Octavia::L7Policy"
item="item"
property-groups="[
['name', 'id', 'project_id'],
['created_at', 'updated_at', 'description'],
['action', 'redirect_pool_id', 'redirect_url'],
['position', 'listener_id']]">
</hz-resource-property-list>

View File

@ -0,0 +1,177 @@
/*
* Copyright 2018 Walmart.
*
* 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.dashboard.project.lbaasv2.l7policies
*
* @description
* Provides the services and widgets required to support and display the project l7 policies
* for the load balancers v2 panel.
*/
angular
.module('horizon.dashboard.project.lbaasv2.l7policies', [])
.constant('horizon.dashboard.project.lbaasv2.l7policies.resourceType',
'OS::Octavia::L7Policy')
.run(run);
run.$inject = [
'horizon.framework.conf.resource-type-registry.service',
'horizon.dashboard.project.lbaasv2.basePath',
'horizon.dashboard.project.lbaasv2.loadbalancers.service',
'horizon.dashboard.project.lbaasv2.l7policies.actions.create',
'horizon.dashboard.project.lbaasv2.l7policies.actions.edit',
'horizon.dashboard.project.lbaasv2.l7policies.actions.delete',
'horizon.dashboard.project.lbaasv2.l7policies.resourceType'
];
function run(
registry,
basePath,
loadBalancerService,
createService,
editService,
deleteService,
resourceType
) {
var l7policyResourceType = registry.getResourceType(resourceType);
l7policyResourceType
.setNames(gettext('L7 Policy'), gettext('L7 Policies'))
.setSummaryTemplateUrl(basePath + 'l7policies/details/drawer.html')
.setProperties(l7policyProperties(loadBalancerService))
.setListFunction(loadBalancerService.getL7PoliciesPromise)
.setLoadFunction(loadBalancerService.getL7PolicyPromise)
.tableColumns
.append({
id: 'name',
priority: 1,
urlFunction: loadBalancerService.getL7PolicyDetailsPath
})
.append({
id: 'position',
sortDefault: true,
priority: 1
})
.append({
id: 'action',
priority: 1
})
.append({
id: 'operating_status',
priority: 1
})
.append({
id: 'provisioning_status',
priority: 1
})
.append({
id: 'admin_state_up',
priority: 1
});
l7policyResourceType.itemActions
.append({
id: 'l7policyEdit',
service: editService,
template: {
text: gettext('Edit L7 Policy')
}
})
.append({
id: 'l7policyDelete',
service: deleteService,
template: {
text: gettext('Delete L7 Policy'),
type: 'delete'
}
});
l7policyResourceType.globalActions
.append({
id: 'l7policyCreate',
service: createService,
template: {
type: 'create',
text: gettext('Create L7 Policy')
}
});
l7policyResourceType.batchActions
.append({
id: 'l7policyBatchDelete',
service: deleteService,
template: {
text: gettext('Delete L7 Policies'),
type: 'delete-selected'
}
});
}
function l7policyProperties(loadBalancerService) {
return {
id: gettext('ID'),
name: {
label: gettext('Name'),
filters: ['noName']
},
description: {
label: gettext('Description'),
filters: ['noValue']
},
provisioning_status: {
label: gettext('Provisioning Status'),
values: loadBalancerService.provisioningStatus
},
operating_status: {
label: gettext('Operating Status'),
values: loadBalancerService.operatingStatus
},
admin_state_up: {
label: gettext('Admin State Up'),
filters: ['yesno']
},
action: {
label: gettext('Action'),
values: loadBalancerService.l7policyAction
},
redirect_url: {
label: gettext('Redirect URL'),
filters: ['noName']
},
redirect_pool_id: {
label: gettext('Redirect Pool ID'),
filters: ['noName']
},
project_id: gettext('Project ID'),
created_at: {
label: gettext('Created At'),
filters: ['noValue']
},
updated_at: {
label: gettext('Updated At'),
filters: ['noValue']
},
position: gettext('Position'),
listener_id: gettext('Listener ID'),
rules: gettext('Rules')
};
}
})();

View File

@ -0,0 +1,67 @@
/*
* Copyright 2018 Walmart.
*
* 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('LBaaS v2 L7Policies Module', function() {
it('should exist', function() {
expect(angular.module('horizon.dashboard.project.lbaasv2.l7policies')).toBeDefined();
});
});
describe('LBaaS v2 L7Policies Registry', function () {
var registry, resourceType;
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(inject(function($injector) {
resourceType = $injector.get('horizon.dashboard.project.lbaasv2.l7policies.resourceType');
registry = $injector.get('horizon.framework.conf.resource-type-registry.service');
}));
it('should define resourceType', function () {
expect(resourceType).toBeDefined();
});
it('should register item actions', function () {
var actions = registry.getResourceType(resourceType).itemActions;
expect(actionHasId(actions, 'l7policyEdit')).toBe(true);
expect(actionHasId(actions, 'l7policyDelete')).toBe(true);
});
it('should register global actions', function () {
var actions = registry.getResourceType(resourceType).globalActions;
expect(actionHasId(actions, 'l7policyCreate')).toBe(true);
});
it('should register batch actions', function () {
var actions = registry.getResourceType(resourceType).batchActions;
expect(actionHasId(actions, 'l7policyBatchDelete')).toBe(true);
});
function actionHasId(list, value) {
return list.filter(matchesId).length === 1;
function matchesId(action) {
if (action.id === value) {
return true;
}
}
}
});
})();

View File

@ -0,0 +1,74 @@
/*
* Copyright 2018 Walmart.
*
* 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.dashboard.project.lbaasv2.l7rules')
.factory('horizon.dashboard.project.lbaasv2.l7rules.actions.create', createService);
createService.$inject = [
'horizon.dashboard.project.lbaasv2.l7rules.resourceType',
'horizon.framework.util.actions.action-result.service',
'$q',
'horizon.dashboard.project.lbaasv2.workflow.modal',
'horizon.app.core.openstack-service-api.policy',
'horizon.framework.util.i18n.gettext'
];
/**
* @ngDoc factory
* @name horizon.dashboard.project.lbaasv2.l7rules.actions.createService
*
* @description
* Provides the service for creating a l7rule resource.
*
* @param resourceType The l7rule resource type.
* @param actionResultService The horizon action result service.
* @param $q The angular service for promises.
* @param workflowModal The LBaaS workflow modal service.
* @param policy The horizon policy service.
* @param gettext The horizon gettext function for translation.
*
* @returns The l7rule create service.
*/
function createService(
resourceType, actionResultService,
$q, workflowModal, policy, gettext
) {
return workflowModal.init({
controller: 'CreateL7RuleWizardController',
message: gettext('A new l7 policy is being created.'),
handle: handle,
allowed: allowed
});
//////////////
function allowed() {
return $q.all([
policy.ifAllowed({ rules: [['neutron', 'create_l7rule']] })
]);
}
function handle(response) {
return actionResultService.getActionResult()
.created(resourceType, response.data.id)
.result;
}
}
})();

View File

@ -0,0 +1,65 @@
/*
* Copyright 2018 Walmart.
*
* 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('LBaaS v2 Create l7rule Action Service', function() {
var policy, service;
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) {
$provide.value('$modal', {
open: function() {
return {
result: {
then: function(func) {
func({ data: { id: 'listener1' } });
}
}
};
}
});
$provide.value('$routeParams', {});
}));
beforeEach(inject(function ($injector) {
policy = $injector.get('horizon.app.core.openstack-service-api.policy');
service = $injector.get('horizon.dashboard.project.lbaasv2.l7rules.actions.create');
}));
it('should not allow creating a l7rule if listenerId is not present', function() {
spyOn(policy, 'ifAllowed').and.returnValue(true);
var allowed = service.allowed();
permissionShouldFail(allowed);
});
it('should handle the action result properly', function() {
var result = service.handle({data: {id: 1}});
expect(result.created[0].id).toBe(1);
});
function permissionShouldFail(permissions) {
permissions.then(
function() {
expect(false).toBe(true);
},
function() {
expect(true).toBe(true);
});
}
});
})();

View File

@ -0,0 +1,45 @@
/*
* Copyright 2018 Walmart.
*
* 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.dashboard.project.lbaasv2.l7rules')
.controller('CreateL7RuleWizardController', CreateL7RuleWizardController);
CreateL7RuleWizardController.$inject = [
'$scope',
'$routeParams',
'horizon.dashboard.project.lbaasv2.workflow.model',
'horizon.dashboard.project.lbaasv2.workflow.workflow',
'horizon.framework.util.i18n.gettext'
];
function CreateL7RuleWizardController($scope, $routeParams, model, workflowService, gettext) {
var loadbalancerId = $routeParams.loadbalancerId;
var l7policyId = $routeParams.l7policyId;
var scope = $scope;
scope.model = model;
scope.submit = scope.model.submit;
scope.workflow = workflowService(
gettext('Create L7 Rule'),
'fa fa-cloud-download',
['l7rule']
);
scope.model.initialize('l7rule', false, loadbalancerId, l7policyId);
}
})();

View File

@ -0,0 +1,63 @@
/*
* Copyright 2018 Walmart.
*
* 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('LBaaS v2 Create L7Rule Wizard Controller', function() {
var ctrl;
var model = {
submit: function() {
return 'created';
},
initialize: angular.noop
};
var workflow = function() {
return 'foo';
};
var scope = {
launchContext: {id: '1234'}
};
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function ($provide) {
$provide.value('horizon.dashboard.project.lbaasv2.workflow.model', model);
$provide.value('horizon.dashboard.project.lbaasv2.workflow.workflow', workflow);
}));
beforeEach(inject(function ($controller) {
spyOn(model, 'initialize');
ctrl = $controller('CreateL7RuleWizardController', { $scope: scope });
}));
it('defines the controller', function() {
expect(ctrl).toBeDefined();
});
it('calls initialize on the given model', function() {
expect(model.initialize).toHaveBeenCalled();
});
it('sets scope.workflow to the given workflow', function() {
expect(scope.workflow).toBe('foo');
});
it('defines scope.submit', function() {
expect(scope.submit).toBeDefined();
expect(scope.submit()).toBe('created');
});
});
})();

View File

@ -0,0 +1,137 @@
/*
* Copyright 2018 Walmart.
*
* 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.dashboard.project.lbaasv2.l7rules')
.factory('horizon.dashboard.project.lbaasv2.l7rules.actions.delete', deleteService);
deleteService.$inject = [
'horizon.dashboard.project.lbaasv2.l7rules.resourceType',
'horizon.framework.util.actions.action-result.service',
'$location',
'horizon.framework.widgets.modal.deleteModalService',
'horizon.app.core.openstack-service-api.lbaasv2',
'horizon.framework.util.i18n.gettext',
'horizon.app.core.openstack-service-api.policy'
];
/**
* @ngDoc factory
* @name horizon.dashboard.project.lbaasv2.l7rules.actions.deleteService
*
* @description
* Brings up the delete l7rule confirmation modal dialog.
* On submit, deletes selected l7rule.
* On cancel, does nothing.
*
* @param resourceType The l7rule resource type.
* @param actionResultService The horizon action result service.
* @param $location The angular $location service.
* @param deleteModal The horizon delete modal service.
* @param api The LBaaS v2 API service.
* @param gettext The horizon gettext function for translation.
* @param policy The horizon policy service.
*
* @returns The l7rule delete service.
*/
function deleteService(
resourceType, actionResultService, $location,
deleteModal, api, gettext, policy
) {
var loadbalancerId, listenerId, l7policyId;
var service = {
perform: perform,
allowed: allowed,
deleteResult: deleteResult // exposed just for testing
};
return service;
//////////////
function allowed(/*item*/) {
// This rule is made up and should therefore always pass. I assume at some point there
// will be a valid rule similar to this that we will want to use.
return policy.ifAllowed({ rules: [['neutron', 'delete_l7rule']] });
}
function perform(items, scope) {
var context = { };
var l7rules = angular.isArray(items) ? items : [items];
context.labels = labelize(l7rules.length);
context.deleteEntity = deleteItem;
l7rules.map(function(item) {
loadbalancerId = item.loadbalancerId;
listenerId = item.listenerId;
l7policyId = item.l7policyId;
});
return deleteModal.open(scope, l7rules, context).then(deleteResult);
}
function labelize(count) {
return {
title: ngettext(
'Confirm Delete L7 Rule',
'Confirm Delete L7 Rules', count),
message: ngettext(
'You have selected "%s". Deleted L7 Rule is not recoverable.',
'You have selected "%s". Deleted L7 Rules are not recoverable.', count),
submit: ngettext(
'Delete L7 Rule',
'Delete L7 Rules', count),
success: ngettext(
'Deleted L7 Rule: %s.',
'Deleted L7 Rules: %s.', count),
error: ngettext(
'Unable to delete L7 Rule: %s.',
'Unable to delete L7 Rules: %s.', count)
};
}
function deleteResult(deleteModalResult) {
// To make the result of this action generically useful, reformat the return
// from the deleteModal into a standard form
var actionResult = actionResultService.getActionResult();
deleteModalResult.pass.forEach(function markDeleted(item) {
actionResult.deleted(resourceType, item.context.id);
});
deleteModalResult.fail.forEach(function markFailed(item) {
actionResult.failed(resourceType, item.context.id);
});
if (actionResult.result.failed.length === 0 && actionResult.result.deleted.length > 0) {
var path = 'project/load_balancer/' + loadbalancerId +
'/listeners/' + listenerId +
'/l7policies/' + l7policyId;
$location.path(path);
}
return actionResult.result;
}
function deleteItem(id) {
return api.deleteL7Rule(l7policyId, id, true);
}
}
})();

View File

@ -0,0 +1,103 @@
/*
* Copyright 2018 Walmart.
*
* 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('LBaaS v2 L7Rule Delete Service', function() {
beforeEach(module('horizon.app.core'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module('horizon.framework'));
var deleteModalService, service, lbaasv2API, policyAPI, $location;
beforeEach(inject(function($injector) {
service = $injector.get('horizon.dashboard.project.lbaasv2.l7rules.actions.delete');
lbaasv2API = $injector.get('horizon.app.core.openstack-service-api.lbaasv2');
deleteModalService = $injector.get('horizon.framework.widgets.modal.deleteModalService');
policyAPI = $injector.get('horizon.app.core.openstack-service-api.policy');
$location = $injector.get('$location');
}));
describe('perform method', function() {
beforeEach(function () {
// just need for this to return something that looks like a promise but does nothing
spyOn(deleteModalService, 'open').and.returnValue({then: angular.noop});
});
it('should open the modal with correct label', function () {
service.perform({name: 'spam'});
var labels = deleteModalService.open.calls.argsFor(0)[2].labels;
expect(deleteModalService.open).toHaveBeenCalled();
angular.forEach(labels, function eachLabel(label) {
expect(label.toLowerCase()).toContain('l7 rule');
});
});
it('should open the delete modal with correct entities', function () {
service.perform([{name: 'one'}, {name: 'two'}]);
var entities = deleteModalService.open.calls.argsFor(0)[1];
expect(deleteModalService.open).toHaveBeenCalled();
expect(entities.length).toEqual(2);
});
it('should pass in a function that deletes a l7 rule', function () {
spyOn(lbaasv2API, 'deleteL7Rule').and.callFake(angular.noop);
service.perform({l7policyId: 2, id: 1, name: 'one'});
var contextArg = deleteModalService.open.calls.argsFor(0)[2];
var deleteFunction = contextArg.deleteEntity;
deleteFunction(1);
expect(lbaasv2API.deleteL7Rule).toHaveBeenCalledWith(2, 1, true);
});
});
it('should handle the action result properly', function() {
spyOn($location, 'path');
spyOn(deleteModalService, 'open').and.returnValue({then: angular.noop});
spyOn(lbaasv2API, 'deleteL7Rule').and.callFake(angular.noop);
service.perform({loadbalancerId: 1, listenerId: 2, l7policyId: 3, id: 1, name: 'one'});
var result = service.deleteResult({
fail: [],
pass: [{
context: {
id: 1
}
}]
});
var path = 'project/load_balancer/1/listeners/2/l7policies/3';
expect($location.path).toHaveBeenCalledWith(path);
expect(result.deleted[0].id).toBe(1);
result = service.deleteResult({
pass: [],
fail: [{
context: {
id: 1
}
}]
});
expect(result.failed[0].id).toBe(1);
});
describe('allow method', function() {
it('should use default policy if batch action', function () {
spyOn(policyAPI, 'ifAllowed');
service.allowed();
expect(policyAPI.ifAllowed).toHaveBeenCalled();
});
}); // end of allowed
}); // end of delete
})();

View File

@ -0,0 +1,69 @@
/*
* Copyright 2018 Walmart.
*
* 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.dashboard.project.lbaasv2.l7rules')
.factory('horizon.dashboard.project.lbaasv2.l7rules.actions.edit', editService);
editService.$inject = [
'horizon.dashboard.project.lbaasv2.l7rules.resourceType',
'horizon.framework.util.actions.action-result.service',
'horizon.dashboard.project.lbaasv2.workflow.modal',
'horizon.app.core.openstack-service-api.policy',
'horizon.framework.util.i18n.gettext'
];
/**
* @ngDoc factory
* @name horizon.dashboard.project.lbaasv2.l7rules.actions.editService
*
* @description
* Provides the service for editing a l7rule resource.
*
* @param resourceType The l7rule resource type.
* @param actionResultService The horizon action result service.
* @param workflowModal The LBaaS workflow modal service.
* @param policy The horizon policy service.
* @param gettext The horizon gettext function for translation.
*
* @returns The l7rule edit service.
*/
function editService(
resourceType, actionResultService, workflowModal, policy, gettext
) {
return workflowModal.init({
controller: 'EditL7RuleWizardController',
message: gettext('The l7rule has been updated.'),
handle: handle,
allowed: allowed
});
function allowed(/*item*/) {
return policy.ifAllowed({ rules: [['neutron', 'update_l7rule']] });
}
function handle(response) {
return actionResultService.getActionResult()
.updated(resourceType, response.config.data.l7rule.id)
.result;
}
}
})();

View File

@ -0,0 +1,55 @@
/*
* Copyright 2018 Walmart.
*
* 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('LBaaS v2 Edit L7Rule Action Service', function() {
var policy, service;
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) {
$provide.value('$modal', {
open: function() {
return {
result: {
then: function(func) {
func({ data: { id: 'l7rule1' } });
}
}
};
}
});
}));
beforeEach(inject(function ($injector) {
policy = $injector.get('horizon.app.core.openstack-service-api.policy');
service = $injector.get('horizon.dashboard.project.lbaasv2.l7rules.actions.edit');
}));
it('should check policy to allow editing a l7rule', function() {
spyOn(policy, 'ifAllowed').and.returnValue(true);
expect(service.allowed()).toBe(true);
expect(policy.ifAllowed).toHaveBeenCalledWith({rules: [['neutron', 'update_l7rule']]});
});
it('should handle the action result properly', function() {
var result = service.handle({config: {data: {l7rule: {id: 1}}}});
expect(result.updated[0].id).toBe(1);
});
});
})();

View File

@ -0,0 +1,60 @@
/*
* Copyright 2018 Walmart.
*
* 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.dashboard.project.lbaasv2.loadbalancers')
.controller('EditL7RuleWizardController', EditL7RuleWizardController);
EditL7RuleWizardController.$inject = [
'$scope',
'$routeParams',
'$q',
'horizon.dashboard.project.lbaasv2.workflow.model',
'horizon.dashboard.project.lbaasv2.workflow.workflow',
'horizon.framework.util.i18n.gettext'
];
/**
* @ngdoc controller
* @name EditL7RuleWizardController
*
* @description
* Controller for the LBaaS v2 edit l7rule wizard.
*
* @param $scope The angular scope object.
* @param $routeParams The angular $routeParams service.
* @param $q The angular service for promises.
* @param model The LBaaS V2 workflow model service.
* @param workflowService The LBaaS V2 workflow service.
* @param gettext The horizon gettext function for translation.
* @returns undefined
*/
function EditL7RuleWizardController($scope, $routeParams, $q, model, workflowService, gettext) {
var scope = $scope;
var loadbalancerId = $routeParams.loadbalancerId;
var l7policyId = $routeParams.l7policyId;
scope.model = model;
scope.submit = scope.model.submit;
scope.workflow = workflowService(
gettext('Update L7 Rule'),
'fa fa-pencil', ['l7rule']);
scope.model.initialize('l7rule', scope.launchContext.id, loadbalancerId, l7policyId);
}
})();

View File

@ -0,0 +1,77 @@
/*
* Copyright 2018 Walmart.
*
* 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('LBaaS v2 Edit L7Rule Wizard Controller', function() {
var ctrl, workflowSpy, $q, scope;
var model = {
submit: function() {
return 'updated';
},
initialize: function() {
var defer = $q.defer();
defer.resolve();
return defer.promise;
}
};
var workflow = {
steps: [{id: 'l7rule'}],
append: angular.noop
};
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function ($provide) {
workflowSpy = jasmine.createSpy('workflow').and.returnValue(workflow);
$provide.value('horizon.dashboard.project.lbaasv2.workflow.model', model);
$provide.value('horizon.dashboard.project.lbaasv2.workflow.workflow', workflowSpy);
}));
beforeEach(inject(function ($controller, $injector) {
$q = $injector.get('$q');
scope = $injector.get('$rootScope').$new();
scope.launchContext = { id: 'l7ruleId' };
spyOn(model, 'initialize').and.callThrough();
ctrl = $controller('EditL7RuleWizardController', {
$scope: scope,
$routeParams: {loadbalancerId: 'loadbalancerId', l7policyId: 'l7policyId'}});
}));
it('defines the controller', function() {
expect(ctrl).toBeDefined();
});
it('calls initialize on the given model', function() {
expect(model.initialize).toHaveBeenCalledWith(
'l7rule', 'l7ruleId', 'loadbalancerId', 'l7policyId');
});
it('sets scope.workflow to the given workflow', function() {
expect(scope.workflow).toBe(workflow);
});
it('initializes workflow with correct properties', function() {
expect(workflowSpy).toHaveBeenCalledWith('Update L7 Rule',
'fa fa-pencil', ['l7rule']);
});
it('defines scope.submit', function() {
expect(scope.submit).toBe(model.submit);
expect(scope.submit()).toBe('updated');
});
});
})();

View File

@ -0,0 +1,119 @@
/*
* Copyright 2018 Walmart.
*
* 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.dashboard.project.lbaasv2.l7rules')
.controller('L7RuleDetailController', L7RuleDetailController);
L7RuleDetailController.$inject = [
'loadbalancer',
'listener',
'l7policy',
'l7rule',
'horizon.dashboard.project.lbaasv2.loadbalancers.service',
'horizon.dashboard.project.lbaasv2.l7rules.resourceType',
'horizon.framework.conf.resource-type-registry.service',
'horizon.framework.widgets.modal-wait-spinner.service',
'$q'
];
/**
* @ngdoc controller
* @name L7RuleDetailController
*
* @description
* Controller for the LBaaS v2 l7rule detail page.
*
* @param loadbalancer The loadbalancer object.
* @param listener The listener object.
* @param l7policy The l7policy object.
* @param l7rule The l7rule object.
* @param loadBalancersService The LBaaS v2 load balancers service.
* @param resourceType The load balancer resource type.
* @param typeRegistry The horizon type registry service.
* @param spinnerService The horizon modal wait spinner service.
* @param $q The angular service for promises.
*
* @returns undefined
*/
function L7RuleDetailController(
loadbalancer, listener, l7policy, l7rule, loadBalancersService,
resourceType, typeRegistry, spinnerService, $q
) {
var ctrl = this;
ctrl.operatingStatus = loadBalancersService.operatingStatus;
ctrl.provisioningStatus = loadBalancersService.provisioningStatus;
ctrl.l7ruleType = loadBalancersService.l7ruleType;
ctrl.l7ruleCompareType = loadBalancersService.l7ruleCompareType;
ctrl.loadbalancer = loadbalancer;
ctrl.listener = listener;
ctrl.l7policy = l7policy;
ctrl.l7rule = l7rule;
ctrl.listFunctionExtraParams = {
loadbalancerId: ctrl.loadbalancer.id,
listenerId: ctrl.listener.id,
l7policyId: ctrl.l7policy.id,
l7ruleId: ctrl.l7rule.id
};
ctrl.resourceType = typeRegistry.getResourceType(resourceType);
ctrl.context = {};
ctrl.context.l7policyId = l7policy.id;
ctrl.context.l7ruleId = l7rule.id;
ctrl.resultHandler = actionResultHandler;
function actionResultHandler(returnValue) {
return $q.when(returnValue, actionSuccessHandler);
}
function loadData(response) {
spinnerService.hideModalSpinner();
ctrl.showDetails = true;
ctrl.resourceType.initActions();
ctrl.l7rule = response.data;
ctrl.l7rule.loadbalancerId = ctrl.loadbalancer.id;
ctrl.l7rule.listenerId = ctrl.listener.id;
ctrl.l7rule.l7policyId = ctrl.l7policy.id;
}
function actionSuccessHandler(result) {
// The action has completed (for whatever "complete" means to that
// action. Notice the view doesn't really need to know the semantics of the
// particular action because the actions return data in a standard form.
// That return includes the id and type of each created, updated, deleted
// and failed item.
// Currently just refreshes the display each time.
if (result) {
if (result.failed.length === 0 && result.deleted.length > 0) {
// handle a race condition where the resource is already deleted
return;
}
spinnerService.showModalSpinner(gettext('Please Wait'));
ctrl.showDetails = false;
ctrl.context.loadPromise = ctrl.resourceType.load(
ctrl.context.l7policyId,
ctrl.context.l7ruleId
);
ctrl.context.loadPromise.then(loadData);
}
}
}
})();

View File

@ -0,0 +1,114 @@
/*
* Copyright 2018 Walmart.
*
* 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('LBaaS v2 L7Rule Detail Controller', function() {
var deferred, service, ctrl, scope, $timeout, $q, actionResultService;
///////////////////////
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) {
$provide.value('$uibModal', {});
}));
beforeEach(inject(function($controller, $rootScope, _$q_, _$timeout_) {
$q = _$q_;
deferred = $q.defer();
service = {
getResourceType: function() {
return {
load: function() { return deferred.promise; },
parsePath: function() { return 'my-context'; },
itemName: function() { return 'A name'; },
initActions: angular.noop
};
},
getDefaultDetailsTemplateUrl: angular.noop
};
actionResultService = {
getIdsOfType: function() { return []; }
};
$timeout = _$timeout_;
scope = $rootScope.$new();
ctrl = $controller('L7RuleDetailController', {
$scope: scope,
loadbalancer: { id: '123' },
listener: { id: '123' },
l7policy: { id: '123' },
l7rule: { id: '123' },
'horizon.framework.conf.resource-type-registry.service': service,
'horizon.framework.util.actions.action-result.service': actionResultService,
'horizon.framework.widgets.modal-wait-spinner.service': {
showModalSpinner: angular.noop,
hideModalSpinner: angular.noop
}
});
}));
it('should create a controller', function() {
expect(ctrl).toBeDefined();
expect(ctrl.loadbalancer).toBeDefined();
expect(ctrl.listener).toBeDefined();
expect(ctrl.l7policy).toBeDefined();
expect(ctrl.l7rule).toBeDefined();
});
describe('resultHandler', function() {
it('handles empty results', function() {
var result = $q.defer();
result.resolve({failed: [], deleted: []});
ctrl.resultHandler(result.promise);
$timeout.flush();
expect(ctrl.showDetails).not.toBe(true);
});
it('handles falsy results', function() {
var result = $q.defer();
result.resolve(false);
ctrl.resultHandler(result.promise);
$timeout.flush();
expect(ctrl.showDetails).not.toBe(true);
});
it('handles matched results', function() {
spyOn(actionResultService, 'getIdsOfType').and.returnValue([1, 2, 3]);
var result = $q.defer();
result.resolve({some: 'thing', failed: [], deleted: []});
ctrl.resultHandler(result.promise);
deferred.resolve({data: {some: 'data'}});
$timeout.flush();
expect(ctrl.showDetails).toBe(true);
});
it('handles delete race condition', function() {
spyOn(actionResultService, 'getIdsOfType').and.returnValue([1, 2, 3]);
var result = $q.defer();
result.resolve({some: 'thing', failed: [], deleted: [{id: 1}]});
ctrl.resultHandler(result.promise);
deferred.resolve({data: {some: 'data'}});
$timeout.flush();
expect(ctrl.showDetails).toBe(undefined);
});
});
});
})();

View File

@ -0,0 +1,54 @@
<div class="page-header">
<ol class="breadcrumb">
<li class="breadcrumb-item-truncate"><translate>Project</translate></li>
<li class="breadcrumb-item-truncate"><translate>Network</translate></li>
<li class="breadcrumb-item-truncate"><a href="project/load_balancer/"><translate>Load Balancers</translate></a></li>
<li class="breadcrumb-item-truncate"><a href="project/load_balancer/{$ ::ctrl.loadbalancer.id $}">{$ ::(ctrl.loadbalancer.name || ctrl.loadbalancer.id) $}</a></li>
<li class="breadcrumb-item-truncate"><a href="project/load_balancer/{$ ::ctrl.loadbalancer.id $}/listeners/{$ ::ctrl.listener.id $}">{$ ::(ctrl.listener.name || ctrl.listener.id) $}</a></li>
<li class="breadcrumb-item-truncate"><a href="project/load_balancer/{$ ::ctrl.loadbalancer.id $}/listeners/{$ ::ctrl.listener.id $}/l7policies/{$ ::ctrl.l7policy.id $}">{$ ::(ctrl.l7policy.name || ctrl.l7policy.id) $}</a></li>
<li class="breadcrumb-item-truncate active">{$ :: ctrl.l7rule.id $}</li>
</ol>
<div class="row">
<div class="col-xs-12 col-sm-9 text-left">
<ul class="list-inline">
<li>
<strong translate>Type</strong>
{$ ::ctrl.l7rule.type | decode:ctrl.l7ruleType $}
</li>
<li>
<strong translate>Operating Status</strong>
{$ ctrl.l7rule.operating_status | decode:ctrl.operatingStatus $}
</li>
<li>
<strong translate>Provisioning Status</strong>
{$ ctrl.l7rule.provisioning_status | decode:ctrl.provisioningStatus $}
</li>
<li>
<strong translate>Admin State Up</strong>
{$ ctrl.l7rule.admin_state_up | yesno $}
</li>
</ul>
</div>
<div class="col-xs-12 col-sm-3 text-right details-item-actions">
<actions allowed="ctrl.resourceType.itemActions"
type="row"
item="ctrl.l7rule"
ng-if="ctrl.l7rule"
class="actions_column pull-right"
result-handler="ctrl.resultHandler"></actions>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 detail">
<hz-resource-property-list
resource-type-name="OS::Octavia::L7Rule"
cls="dl-horizontal"
item="ctrl.l7rule"
property-groups="[[
'id', 'compare_type', 'project_id', 'created_at', 'updated_at',
'key', 'rule_value', 'invert']]">
</hz-resource-property-list>
</div>
</div?

View File

@ -0,0 +1,8 @@
<hz-resource-property-list
resource-type-name="OS::Octavia::L7Rule"
item="item"
property-groups="[
['type', 'id', 'project_id'],
['created_at', 'updated_at', 'compare_type'],
['key', 'rule_value', 'invert']]">
</hz-resource-property-list>

View File

@ -0,0 +1,181 @@
/*
* Copyright 2018 Walmart.
*
* 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.dashboard.project.lbaasv2.l7rules
*
* @description
* Provides the services and widgets required to support and display the project l7 rules
* for the load balancers v2 panel.
*/
angular
.module('horizon.dashboard.project.lbaasv2.l7rules', [])
.constant('horizon.dashboard.project.lbaasv2.l7rules.resourceType',
'OS::Octavia::L7Rule')
.run(run);
run.$inject = [
'horizon.framework.conf.resource-type-registry.service',
'horizon.dashboard.project.lbaasv2.basePath',
'horizon.dashboard.project.lbaasv2.loadbalancers.service',
'horizon.dashboard.project.lbaasv2.l7rules.actions.create',
'horizon.dashboard.project.lbaasv2.l7rules.actions.edit',
'horizon.dashboard.project.lbaasv2.l7rules.actions.delete',
'horizon.dashboard.project.lbaasv2.l7rules.resourceType'
];
function run(
registry,
basePath,
loadBalancerService,
createService,
editService,
deleteService,
resourceType
) {
var l7ruleResourceType = registry.getResourceType(resourceType);
l7ruleResourceType
.setNames(gettext('L7 Rule'), gettext('L7 Rules'))
.setSummaryTemplateUrl(basePath + 'l7rules/details/drawer.html')
.setProperties(l7ruleProperties(loadBalancerService))
.setListFunction(loadBalancerService.getL7RulesPromise)
.setLoadFunction(loadBalancerService.getL7RulePromise)
.tableColumns
.append({
id: 'type',
priority: 1,
urlFunction: loadBalancerService.getL7RuleDetailsPath
})
.append({
id: 'compare_type',
priority: 1
})
.append({
id: 'key',
priority: 1
})
.append({
id: 'rule_value',
priority: 1
})
.append({
id: 'invert',
priority: 1
})
.append({
id: 'operating_status',
priority: 1
})
.append({
id: 'provisioning_status',
priority: 1
})
.append({
id: 'admin_state_up',
priority: 1
});
l7ruleResourceType.itemActions
.append({
id: 'l7ruleEdit',
service: editService,
template: {
text: gettext('Edit L7 Rule')
}
})
.append({
id: 'l7ruleDelete',
service: deleteService,
template: {
text: gettext('Delete L7 Rule'),
type: 'delete'
}
});
l7ruleResourceType.globalActions
.append({
id: 'l7ruleCreate',
service: createService,
template: {
type: 'create',
text: gettext('Create L7 Rule')
}
});
l7ruleResourceType.batchActions
.append({
id: 'l7ruleBatchDelete',
service: deleteService,
template: {
text: gettext('Delete L7 Rules'),
type: 'delete-selected'
}
});
}
function l7ruleProperties(loadBalancerService) {
return {
id: gettext('ID'),
type: {
label: gettext('Type'),
values: loadBalancerService.l7ruleType
},
compare_type: {
label: gettext('Compare Type'),
values: loadBalancerService.l7ruleCompareType
},
provisioning_status: {
label: gettext('Provisioning Status'),
values: loadBalancerService.provisioningStatus
},
operating_status: {
label: gettext('Operating Status'),
values: loadBalancerService.operatingStatus
},
admin_state_up: {
label: gettext('Admin State Up'),
filters: ['yesno']
},
key: {
label: gettext('Key'),
filters: ['noName']
},
rule_value: {
label: gettext('Value'),
filters: ['noName']
},
invert: {
label: gettext('Invert'),
filters: ['yesno']
},
project_id: gettext('Project ID'),
created_at: {
label: gettext('Created At'),
filters: ['noValue']
},
updated_at: {
label: gettext('Updated At'),
filters: ['noValue']
}
};
}
})();

View File

@ -0,0 +1,67 @@
/*
* Copyright 2018 Walmart.
*
* 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('LBaaS v2 L7Rules Module', function() {
it('should exist', function() {
expect(angular.module('horizon.dashboard.project.lbaasv2.l7rules')).toBeDefined();
});
});
describe('LBaaS v2 L7Rules Registry', function () {
var registry, resourceType;
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(inject(function($injector) {
resourceType = $injector.get('horizon.dashboard.project.lbaasv2.l7rules.resourceType');
registry = $injector.get('horizon.framework.conf.resource-type-registry.service');
}));
it('should define resourceType', function () {
expect(resourceType).toBeDefined();
});
it('should register item actions', function () {
var actions = registry.getResourceType(resourceType).itemActions;
expect(actionHasId(actions, 'l7ruleEdit')).toBe(true);
expect(actionHasId(actions, 'l7ruleDelete')).toBe(true);
});
it('should register global actions', function () {
var actions = registry.getResourceType(resourceType).globalActions;
expect(actionHasId(actions, 'l7ruleCreate')).toBe(true);
});
it('should register batch actions', function () {
var actions = registry.getResourceType(resourceType).batchActions;
expect(actionHasId(actions, 'l7ruleBatchDelete')).toBe(true);
});
function actionHasId(list, value) {
return list.filter(matchesId).length === 1;
function matchesId(action) {
if (action.id === value) {
return true;
}
}
}
});
})();

View File

@ -28,6 +28,8 @@
.module('horizon.dashboard.project.lbaasv2', [
'horizon.dashboard.project.lbaasv2.loadbalancers',
'horizon.dashboard.project.lbaasv2.listeners',
'horizon.dashboard.project.lbaasv2.l7policies',
'horizon.dashboard.project.lbaasv2.l7rules',
'horizon.dashboard.project.lbaasv2.pools',
'horizon.dashboard.project.lbaasv2.members',
'horizon.dashboard.project.lbaasv2.healthmonitors',
@ -68,6 +70,8 @@
var loadbalancers = '/project/load_balancer';
var listener = loadbalancers + '/:loadbalancerId/listeners/:listenerId';
var listenerL7Policy = listener + '/l7policies/:l7policyId';
var listenerL7Rule = listenerL7Policy + '/l7rules/:l7ruleId';
var listenerPool = listener + '/pools/:poolId';
var listenerPoolMember = listenerPool + '/members/:memberId';
var listenerPoolHealthmonitor = listenerPool + '/healthmonitors/:healthmonitorId';
@ -129,6 +133,105 @@
controller: 'ListenerDetailController',
controllerAs: 'ctrl'
})
.when(listenerL7Policy, {
templateUrl: basePath + 'l7policies/details/detail.html',
resolve: {
loadbalancer: [
'$route',
'horizon.app.core.openstack-service-api.lbaasv2',
function($route, api) {
return api.getLoadBalancer($route.current.params.loadbalancerId, true).then(
function success(response) {
response.data.floating_ip_address = response.data.floating_ip.ip;
return response.data;
}
);
}
],
listener: [
'$route',
'horizon.app.core.openstack-service-api.lbaasv2',
function($route, api) {
return api.getListener($route.current.params.listenerId).then(
function success(response) {
return response.data;
}
);
}
],
l7policy: [
'$route',
'horizon.app.core.openstack-service-api.lbaasv2',
function($route, api) {
return api.getL7Policy($route.current.params.l7policyId).then(
function success(response) {
response.data.loadbalancerId = $route.current.params.loadbalancerId;
response.data.listenerId = $route.current.params.listenerId;
return response.data;
}
);
}
]
},
controller: 'L7PolicyDetailController',
controllerAs: 'ctrl'
})
.when(listenerL7Rule, {
templateUrl: basePath + 'l7rules/details/detail.html',
resolve: {
loadbalancer: [
'$route',
'horizon.app.core.openstack-service-api.lbaasv2',
function($route, api) {
return api.getLoadBalancer($route.current.params.loadbalancerId, true).then(
function success(response) {
response.data.floating_ip_address = response.data.floating_ip.ip;
return response.data;
}
);
}
],
listener: [
'$route',
'horizon.app.core.openstack-service-api.lbaasv2',
function($route, api) {
return api.getListener($route.current.params.listenerId).then(
function success(response) {
return response.data;
}
);
}
],
l7policy: [
'$route',
'horizon.app.core.openstack-service-api.lbaasv2',
function($route, api) {
return api.getL7Policy($route.current.params.l7policyId).then(
function success(response) {
return response.data;
}
);
}
],
l7rule: [
'$route',
'horizon.app.core.openstack-service-api.lbaasv2',
function($route, api) {
return api.getL7Rule($route.current.params.l7policyId,
$route.current.params.l7ruleId).then(
function success(response) {
response.data.loadbalancerId = $route.current.params.loadbalancerId;
response.data.listenerId = $route.current.params.listenerId;
response.data.l7policyId = $route.current.params.l7policyId;
return response.data;
}
);
}
]
},
controller: 'L7RuleDetailController',
controllerAs: 'ctrl'
})
.when(listenerPool, {
templateUrl: basePath + 'pools/details/detail.html',
resolve: {

View File

@ -233,6 +233,121 @@
});
}));
it('should route resolved listener l7 policy detail', inject(function($injector) {
function loadbalancerAPI() {
var loadbalancer = { provisioning_status: 'ACTIVE' };
return {
success: function(callback) {
callback(loadbalancer);
},
then: function(callback) {
callback({ data: { id: 1, floating_ip: {}}});
}
};
}
function listenerAPI() {
var listener = { provisioning_status: 'ACTIVE' };
return {
success: function(callback) {
callback(listener);
},
then: function(callback) {
callback({ data: { id: 1}});
}
};
}
function l7policyAPI() {
var l7policy = { provisioning_status: 'ACTIVE' };
return {
success: function(callback) {
callback(l7policy);
},
then: function(callback) {
callback({ data: { id: 1}});
}
};
}
var lbaasv2API = $injector.get('horizon.app.core.openstack-service-api.lbaasv2');
spyOn(lbaasv2API, 'getLoadBalancer').and.callFake(loadbalancerAPI);
spyOn(lbaasv2API, 'getListener').and.callFake(listenerAPI);
spyOn(lbaasv2API, 'getL7Policy').and.callFake(l7policyAPI);
inject(function($route, $location, $rootScope, $httpBackend) {
$httpBackend.expectGET(
'/static/dashboard/project/lbaasv2/l7policies/details/detail.html'
).respond({});
$location.path('/project/load_balancer/1/listeners/2/l7policies/3');
$rootScope.$digest();
expect($route.current).toBeDefined();
});
}));
it('should route resolved listener l7 rule detail', inject(function($injector) {
function loadbalancerAPI() {
var loadbalancer = { provisioning_status: 'ACTIVE' };
return {
success: function(callback) {
callback(loadbalancer);
},
then: function(callback) {
callback({ data: { id: 1, floating_ip: {}}});
}
};
}
function listenerAPI() {
var listener = { provisioning_status: 'ACTIVE' };
return {
success: function(callback) {
callback(listener);
},
then: function(callback) {
callback({ data: { id: 1}});
}
};
}
function l7policyAPI() {
var l7policy = { provisioning_status: 'ACTIVE' };
return {
success: function(callback) {
callback(l7policy);
},
then: function(callback) {
callback({ data: { id: 1}});
}
};
}
function l7ruleAPI() {
var l7rule = { provisioning_status: 'ACTIVE' };
return {
success: function(callback) {
callback(l7rule);
},
then: function(callback) {
callback({ data: { id: 1}});
}
};
}
var lbaasv2API = $injector.get('horizon.app.core.openstack-service-api.lbaasv2');
spyOn(lbaasv2API, 'getLoadBalancer').and.callFake(loadbalancerAPI);
spyOn(lbaasv2API, 'getListener').and.callFake(listenerAPI);
spyOn(lbaasv2API, 'getL7Policy').and.callFake(l7policyAPI);
spyOn(lbaasv2API, 'getL7Rule').and.callFake(l7ruleAPI);
inject(function($route, $location, $rootScope, $httpBackend) {
$httpBackend.expectGET(
'/static/dashboard/project/lbaasv2/l7rules/details/detail.html'
).respond({});
$location.path('/project/load_balancer/1/listeners/2/l7policies/3/l7rules/4');
$rootScope.$digest();
expect($route.current).toBeDefined();
});
}));
it('should route resolved listener pool member detail', inject(function($injector) {
function loadbalancerAPI() {
var loadbalancer = { provisioning_status: 'ACTIVE' };

View File

@ -61,4 +61,10 @@
list-function-extra-params="ctrl.listFunctionExtraParams">
</hz-resource-table>
</uib-tab>
<uib-tab heading="{$ 'L7 Policies' | translate $}">
<hz-resource-table resource-type-name="OS::Octavia::L7Policy"
track-by="trackBy"
list-function-extra-params="ctrl.listFunctionExtraParams">
</hz-resource-table>
</uib-tab>
</uib-tabset>

View File

@ -33,7 +33,11 @@
// the wizard continues to work. Using local var to appease eslint angular/ng_controller_as.
scope.model = model;
scope.submit = scope.model.submit;
scope.workflow = workflowService(gettext('Create Load Balancer'), 'fa fa-cloud-download');
scope.workflow = workflowService(
gettext('Create Load Balancer'),
'fa fa-cloud-download',
['loadbalancer', 'listener', 'certificates', 'pool', 'members', 'monitor']
);
scope.model.initialize('loadbalancer');
}

View File

@ -64,6 +64,28 @@
SOURCE_IP: gettext('Source IP')
};
var l7policyAction = {
REJECT: gettext('Reject'),
REDIRECT_TO_URL: gettext('Redirect to URL'),
REDIRECT_TO_POOL: gettext('Redirect to Pool')
};
var l7ruleType = {
HOST_NAME: gettext('Host Name'),
PATH: gettext('Path'),
FILE_TYPE: gettext('File Type'),
HEADER: gettext('Header'),
COOKIE: gettext('Cookie')
};
var l7ruleCompareType = {
REGEX: gettext('Regex'),
STARTS_WITH: gettext('Starts With'),
ENDS_WITH: gettext('Ends With'),
CONTAINS: gettext('Contains'),
EQUAL_TO: gettext('Equal To')
};
var none = {
null: gettext('None')
};
@ -72,6 +94,9 @@
operatingStatus: operatingStatus,
provisioningStatus: provisioningStatus,
loadBalancerAlgorithm: loadBalancerAlgorithm,
l7policyAction: l7policyAction,
l7ruleType: l7ruleType,
l7ruleCompareType: l7ruleCompareType,
none: none,
nullFilter: nullFilter,
getLoadBalancersPromise: getLoadBalancersPromise,
@ -80,6 +105,12 @@
getListenersPromise: getListenersPromise,
getListenerPromise: getListenerPromise,
getListenerDetailsPath: getListenerDetailsPath,
getL7PoliciesPromise: getL7PoliciesPromise,
getL7PolicyPromise: getL7PolicyPromise,
getL7PolicyDetailsPath: getL7PolicyDetailsPath,
getL7RulesPromise: getL7RulesPromise,
getL7RulePromise: getL7RulePromise,
getL7RuleDetailsPath: getL7RuleDetailsPath,
getPoolsPromise: getPoolsPromise,
getPoolPromise: getPoolPromise,
getPoolDetailsPath: getPoolDetailsPath,
@ -200,6 +231,58 @@
}
}
function getL7RulesPromise(params) {
return api.getL7Rules(params.l7policyId).then(modifyResponse);
function modifyResponse(response) {
return {data: {items: response.data.items.map(modifyItem)}};
function modifyItem(item) {
item.trackBy = item.id + item.updated_at;
item.loadbalancerId = params.loadbalancerId;
item.listenerId = params.listenerId;
item.l7policyId = params.l7policyId;
return item;
}
}
}
function getL7RulePromise(l7policyId, l7ruleId) {
return api.getL7Rule(l7policyId, l7ruleId);
}
function getL7RuleDetailsPath(item) {
return 'project/load_balancer/' +
item.loadbalancerId + '/listeners/' +
item.listenerId + '/l7policies/' +
item.l7policyId + '/l7rules/' + item.id;
}
function getL7PoliciesPromise(params) {
return api.getL7Policies(params.listenerId).then(modifyResponse);
function modifyResponse(response) {
return {data: {items: response.data.items.map(modifyItem)}};
function modifyItem(item) {
item.trackBy = item.id + item.updated_at;
item.loadbalancerId = params.loadbalancerId;
item.listenerId = params.listenerId;
return item;
}
}
}
function getL7PolicyPromise(identifier) {
return api.getL7Policy(identifier);
}
function getL7PolicyDetailsPath(item) {
return 'project/load_balancer/' +
item.loadbalancerId + '/listeners/' +
item.listenerId + '/l7policies/' + item.id;
}
function getListenersPromise(params) {
return api.getListeners(params.loadbalancerId).then(modifyResponse);

View File

@ -91,6 +91,61 @@
expect(result.$$state.value.data.updated_at).toBe('feb8');
}));
it('getL7PolicyDetailsPath creates urls using the item\'s ID', function() {
var myItem = {loadbalancerId: '123', id: '789', listenerId: '456'};
expect(service.getL7PolicyDetailsPath(myItem))
.toBe('project/load_balancer/123/listeners/456/l7policies/789');
});
it("getL7PoliciesPromise provides a promise", inject(function($timeout) {
var deferred = $q.defer();
spyOn(api, 'getL7Policies').and.returnValue(deferred.promise);
var result = service.getL7PoliciesPromise({listenerId: 3});
deferred.resolve({data: {items: [{id: 1, updated_at: 'feb8'}]}});
$timeout.flush();
expect(result.$$state.value.data.items[0].id).toBe(1);
expect(result.$$state.value.data.items[0].updated_at).toBe('feb8');
expect(result.$$state.value.data.items[0].trackBy).toBe('1feb8');
expect(result.$$state.value.data.items[0].listenerId).toBe(3);
}));
it("getL7PolicyPromise provides a promise", inject(function() {
var deferred = $q.defer();
spyOn(api, 'getL7Policy').and.returnValue(deferred.promise);
var result = service.getL7PolicyPromise(1);
deferred.resolve({data: {id: 1, updated_at: 'feb8'}});
expect(result.$$state.value.data.id).toBe(1);
expect(result.$$state.value.data.updated_at).toBe('feb8');
}));
it('getL7RuleDetailsPath creates urls using the item\'s ID', function() {
var myItem = {loadbalancerId: '1', id: '5', listenerId: '2', l7policyId: '3'};
expect(service.getL7RuleDetailsPath(myItem))
.toBe('project/load_balancer/1/listeners/2/l7policies/3/l7rules/5');
});
it("getL7RulesPromise provides a promise", inject(function($timeout) {
var deferred = $q.defer();
spyOn(api, 'getL7Rules').and.returnValue(deferred.promise);
var result = service.getL7RulesPromise({listenerId: 3, l7policyId: 5});
deferred.resolve({data: {items: [{id: 1, updated_at: 'feb8'}]}});
$timeout.flush();
expect(result.$$state.value.data.items[0].id).toBe(1);
expect(result.$$state.value.data.items[0].updated_at).toBe('feb8');
expect(result.$$state.value.data.items[0].trackBy).toBe('1feb8');
expect(result.$$state.value.data.items[0].listenerId).toBe(3);
expect(result.$$state.value.data.items[0].l7policyId).toBe(5);
}));
it("getL7RulePromise provides a promise", inject(function() {
var deferred = $q.defer();
spyOn(api, 'getL7Rule').and.returnValue(deferred.promise);
var result = service.getL7RulePromise(2, 1);
deferred.resolve({data: {id: 1, updated_at: 'feb8'}});
expect(result.$$state.value.data.id).toBe(1);
expect(result.$$state.value.data.updated_at).toBe('feb8');
}));
it('getPoolDetailsPath creates urls using the item\'s ID', function() {
var myItem = {loadbalancerId: '123', id: '789', listeners: [{id: '456'}]};
expect(service.getPoolDetailsPath(myItem))

View File

@ -0,0 +1,52 @@
/*
* Copyright 2018 Walmart.
*
* 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.dashboard.project.lbaasv2')
.controller('L7PolicyDetailsController', L7PolicyDetailsController);
L7PolicyDetailsController.$inject = [
'horizon.dashboard.project.lbaasv2.patterns',
'horizon.framework.util.i18n.gettext'
];
/**
* @ngdoc controller
* @name L7PolicyDetailsController
* @description
* The `L7PolicyDetailsController` controller provides functions for
* configuring the l7policy step of the LBaaS wizard.
* @param patterns The LBaaS v2 patterns constant.
* @param gettext The horizon gettext function for translation.
* @returns undefined
*/
function L7PolicyDetailsController(patterns, gettext) {
var ctrl = this;
ctrl.adminStateUpOptions = [
{ label: gettext('Yes'), value: true },
{ label: gettext('No'), value: false }
];
ctrl.redirectUrlError = gettext('The redirect url must be a valid http or https url.');
ctrl.positionError = gettext('The position must be a number between 1 and 2147483647.');
}
})();

View File

@ -0,0 +1,37 @@
/*
* Copyright 2018 Walmart.
*
* 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('L7Policy Details Step', function() {
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
describe('L7PolicyDetailsController', function() {
var ctrl;
beforeEach(inject(function($controller) {
ctrl = $controller('L7PolicyDetailsController');
}));
it('should define adminStateUpOptions', function() {
expect(ctrl.adminStateUpOptions).toBeDefined();
});
});
});
})();

View File

@ -0,0 +1,38 @@
<p translate>
An L7 Policy is a collection of L7 rules associated with a Listener, and which may also have an association to a back-end pool
</p>
<p>
<strong translate>Action:</strong>
<translate>
The L7 policy action. One of REJECT, REDIRECT_TO_URL, or REDIRECT_TO_POOL.
</translate>
<ul>
<li translate>
REJECT: The request is denied with an appropriate response code, and not forwarded on to any back-end pool.
</li>
<li translate>
REDIRECT_TO_URL: The request is sent an HTTP redirect to the URL defined in the redirect_url parameter.
</li>
<li translate>
REDIRECT_TO_POOL: The request is forwarded to the back-end pool associated with the L7 policy.
</li>
</ul>
</p>
<p>
<strong translate>Position:</strong>
<translate>
The position of this policy on the listener. Positions start at 1.
</translate>
</p>
<p>
<strong translate>Redirect Pool ID:</strong>
<translate>
Requests matching this policy will be redirected to the pool with this ID. Only valid if action is REDIRECT_TO_POOL.
</translate>
</p>
<p>
<strong translate>Redirect URL:</strong>
<translate>
Requests matching this policy will be redirected to this URL. Only valid if action is REDIRECT_TO_URL.
</translate>
</p>

View File

@ -0,0 +1,104 @@
<div ng-controller="L7PolicyDetailsController as ctrl">
<p translate>Provide the details for the l7 policy.</p>
<div class="row">
<div class="col-xs-12 col-sm-8 col-md-6">
<div class="form-group">
<label translate class="control-label" for="name">Name</label>
<input name="name" id="name" type="text" class="form-control"
ng-model="model.spec.l7policy.name">
</div>
</div>
<div class="col-xs-12 col-sm-8 col-md-6">
<div class="form-group">
<label translate class="control-label" for="description">Description</label>
<input name="description" id="description" type="text" class="form-control"
ng-model="model.spec.l7policy.description">
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-sm-8 col-md-6">
<div class="form-group required">
<label class="control-label" for="action">
<translate>Action</translate>
<span class="hz-icon-required fa fa-asterisk"></span>
</label>
<select class="form-control" name="action" id="action"
ng-options="action for action in model.l7policyActions"
ng-model="model.spec.l7policy.action"
ng-required="true">
</select>
</div>
</div>
<div class="col-xs-12 col-sm-8 col-md-6" ng-if="model.spec.l7policy.action === 'REDIRECT_TO_URL'">
<div class="form-group required">
<label class="control-label" for="redirect_url">
<translate>Redirect URL</translate>
<span class="hz-icon-required fa fa-asterisk"></span>
</label>
<input name="redirect_url" id="redirect_url" placeholder="https://www.example.com" type="url" class="form-control"
ng-model="model.spec.l7policy.redirect_url" ng-patter="/^http[s]?:\/\//" ng-required="true">
<span class="help-block" ng-show="!l7policyDetailsForm.redirect_url.$valid && l7policyDetailsForm.redirect_url.$dirty">
{$ ::ctrl.redirectUrlError $}
</span>
</div>
</div>
<div class="col-xs-12 col-sm-8 col-md-6" ng-if="model.spec.l7policy.action === 'REDIRECT_TO_POOL'">
<div class="form-group required">
<label translate class="control-label" for="redirect_pool_id">
<translate>Redirect Pool ID</translate>
<span class="hz-icon-required fa fa-asterisk"></span>
</label>
<select class="form-control" name="redirect_pool_id" id="redirect_pool_id"
ng-model="model.spec.l7policy.redirect_pool_id" ng-required="true">
<option ng-repeat="pool_id in model.spec.availablePools" value="{$ pool_id $}">
{$ pool_id $}
</option>
</select>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-sm-8 col-md-6">
<div class="form-group">
<label translate class="control-label" for="position">Position</label>
<input name="position" id="position" type="number" class="form-control"
ng-model="model.spec.l7policy.position" ng-pattern="/^\d+$/" min="1" max="2147483647">
<span class="help-block" ng-show="!l7policyDetailsForm.position.$valid && l7policyDetailsForm.position.$dirty">
{$ ::ctrl.positionError $}
</span>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-sm-8 col-md-6">
<div class="form-group">
<label class="control-label required" translate>Admin State Up</label>
<div class="form-field">
<div class="btn-group">
<label class="btn btn-default"
ng-repeat="option in ctrl.adminStateUpOptions"
ng-model="model.spec.l7policy.admin_state_up"
uib-btn-radio="option.value">{$ ::option.label $}</label>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,49 @@
/*
* Copyright 2018 Walmart.
*
* 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.dashboard.project.lbaasv2')
.controller('L7RuleDetailsController', L7RuleDetailsController);
L7RuleDetailsController.$inject = [
'horizon.dashboard.project.lbaasv2.patterns',
'horizon.framework.util.i18n.gettext'
];
/**
* @ngdoc controller
* @name L7RuleDetailsController
* @description
* The `L7RuleDetailsController` controller provides functions for
* configuring the l7rule step of the LBaaS wizard.
* @param patterns The LBaaS v2 patterns constant.
* @param gettext The horizon gettext function for translation.
* @returns undefined
*/
function L7RuleDetailsController(patterns, gettext) {
var ctrl = this;
ctrl.adminStateUpOptions = [
{ label: gettext('Yes'), value: true },
{ label: gettext('No'), value: false }
];
}
})();

View File

@ -0,0 +1,37 @@
/*
* Copyright 2018 Walmart.
*
* 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('L7Rule Details Step', function() {
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
describe('L7RuleDetailsController', function() {
var ctrl;
beforeEach(inject(function($controller) {
ctrl = $controller('L7RuleDetailsController');
}));
it('should define adminStateUpOptions', function() {
expect(ctrl.adminStateUpOptions).toBeDefined();
});
});
});
})();

View File

@ -0,0 +1,76 @@
<p translate>
An L7 Rule is a single, simple logical test which returns either true or
false.
</p>
<p>
<strong translate>Type:</strong>
<translate>
The L7 rule type. One of COOKIE, FILE_TYPE, HEADER, HOST_NAME, or PATH.
</translate>
<ul>
<li translate>
COOKIE: The rule looks for a cookie named by the key parameter and
compares it against the value parameter in the rule.
</li>
<li translate>
HEADER: The rule looks for a header defined in the key parameter
and compares it against the value parameter in the rule.
</li>
<li translate>
FILE_TYPE: The rule compares the last portion of the URI against
the value parameter in the rule. (eg. “txt”, “jpg”, etc.)
</li>
<li translate>
PATH: The rule compares the path portion of the HTTP URI against
the value parameter in the rule.
</li>
<li translate>
HOST_NAME: The rule does a comparison between the HTTP/1.1
hostname in the request against the value parameter in the rule.
</li>
</ul>
</p>
<p>
<strong translate>Compare Type:</strong>
<translate>
The comparison type for the L7 rule. One of CONTAINS, ENDS_WITH,
EQUAL_TO, REGEX, or STARTS_WITH.
</translate>
<ul>
<li translate>
REGEX: Perl type regular expression matching.
</li>
<li translate>
STARTS_WITH: String starts with.
</li>
<li translate>
ENDS_WITH: String ends with.
</li>
<li translate>
CONTAINS: String contains.
</li>
<li translate>
EQUAL_TO: String is equal to.
</li>
</ul>
</p>
<p>
<strong translate>Key:</strong>
<translate>
The key to use for the comparison. For example, the name of the cookie
to evaluate.
</translate>
</p>
<p>
<strong translate>Value:</strong>
<translate>
The value to use for the comparison. For example, the file type to compare.
</translate>
</p>
<p>
<strong translate>Invert:</strong>
<translate>
When true the logic of the rule is inverted. For example, with invert
true, equal to would become not equal to.
</translate>
</p>

View File

@ -0,0 +1,92 @@
<div ng-controller="L7RuleDetailsController as ctrl">
<p translate>Provide the details for the l7 rule.</p>
<div class="row">
<div class="col-xs-12 col-sm-8 col-md-6">
<div class="form-group">
<label class="control-label required" translate>Invert</label>
<div class="form-field">
<div class="btn-group">
<label class="btn btn-default"
ng-repeat="option in ctrl.adminStateUpOptions"
ng-model="model.spec.l7rule.invert"
uib-btn-radio="option.value">{$ ::option.label $}</label>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-sm-8 col-md-6">
<div class="form-group required">
<label class="control-label" for="type">
<translate>Type</translate>
<span class="hz-icon-required fa fa-asterisk"></span>
</label>
<select class="form-control" name="type" id="type"
ng-options="type for type in model.l7ruleTypes"
ng-model="model.spec.l7rule.type"
ng-required="true">
</select>
</div>
</div>
<div class="col-xs-12 col-sm-8 col-md-6" ng-if="(model.spec.l7rule.type === 'COOKIE') || (model.spec.l7rule.type === 'HEADER')">
<div class="form-group required">
<label translate class="control-label" for="key">Key</label>
<input name="key" id="key" type="text" class="form-control"
ng-model="model.spec.l7rule.key" ng-required="true">
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-sm-8 col-md-6">
<div class="form-group required">
<label class="control-label" for="compare_type">
<translate>Compare Type</translate>
<span class="hz-icon-required fa fa-asterisk"></span>
</label>
<select class="form-control" name="compare_type" id="compare_type"
ng-options="compare_type for compare_type in model.l7ruleCompareTypes"
ng-model="model.spec.l7rule.compare_type"
ng-required="true">
</select>
</div>
</div>
<div class="col-xs-12 col-sm-8 col-md-6">
<div class="form-group required">
<label translate class="control-label" for="rule_value">Value</label>
<input name="rule_value" id="rule_value" type="text" class="form-control"
ng-model="model.spec.l7rule.rule_value" ng-required="true">
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-sm-8 col-md-6">
<div class="form-group">
<label class="control-label required" translate>Admin State Up</label>
<div class="form-field">
<div class="btn-group">
<label class="btn btn-default"
ng-repeat="option in ctrl.adminStateUpOptions"
ng-model="model.spec.l7rule.admin_state_up"
uib-btn-radio="option.value">{$ ::option.label $}</label>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -86,6 +86,9 @@
subnets: [],
members: [],
listenerProtocols: ['HTTP', 'TCP', 'TERMINATED_HTTPS', 'HTTPS'],
l7policyActions: ['REJECT', 'REDIRECT_TO_URL', 'REDIRECT_TO_POOL'],
l7ruleTypes: ['HOST_NAME', 'PATH', 'FILE_TYPE', 'HEADER', 'COOKIE'],
l7ruleCompareTypes: ['REGEX', 'STARTS_WITH', 'ENDS_WITH', 'CONTAINS', 'EQUAL_TO'],
poolProtocols: ['HTTP', 'HTTPS', 'PROXY', 'TCP'],
methods: ['LEAST_CONNECTIONS', 'ROUND_ROBIN', 'SOURCE_IP'],
types: ['SOURCE_IP', 'HTTP_COOKIE', 'APP_COOKIE'],
@ -153,6 +156,25 @@
admin_state_up: true,
default_pool_id: null
},
l7policy: {
id: null,
name: gettext('L7 Policy 1'),
description: null,
action: null,
position: null,
redirect_pool_id: null,
redirect_url: null,
admin_state_up: true
},
l7rule: {
id: null,
type: null,
compare_type: null,
key: null,
rule_value: null,
invert: false,
admin_state_up: true
},
pool: {
id: null,
name: gettext('Pool 1'),
@ -198,11 +220,15 @@
var promise = {
createloadbalancer: initCreateLoadBalancer,
createlistener: initCreateListener,
createl7policy: initCreateL7Policy,
createl7rule: initCreateL7Rule,
createpool: initCreatePool,
createmonitor: initCreateMonitor,
createmembers: initUpdateMemberList,
editloadbalancer: initEditLoadBalancer,
editlistener: initEditListener,
editl7policy: initEditL7Policy,
editl7rule: initEditL7Rule,
editpool: initEditPool,
editmonitor: initEditMonitor
}[type](keymanagerPromise);
@ -242,6 +268,17 @@
]).then(initMemberAddresses);
}
function initCreateL7Policy() {
model.context.submit = createL7Policy;
return lbaasv2API.getListener(model.spec.parentResourceId)
.then(onGetListener).then(getPools).then(onGetPools);
}
function initCreateL7Rule() {
model.context.submit = createL7Rule;
return $q.when();
}
function initCreatePool() {
model.context.submit = createPool;
// We get the listener details here because we need to know the listener protocol
@ -295,6 +332,19 @@
]).then(initMemberAddresses);
}
function initEditL7Policy() {
model.context.submit = editL7Policy;
return lbaasv2API
.getListener(model.spec.parentResourceId).then(onGetListener)
.then(getPools).then(onGetPools)
.then(getL7Policy).then(onGetL7Policy);
}
function initEditL7Rule() {
model.context.submit = editL7Rule;
return getL7Rule().then(onGetL7Rule);
}
function initEditPool() {
model.context.submit = editPool;
return $q.all([
@ -339,6 +389,14 @@
return lbaasv2API.createListener(spec);
}
function createL7Policy(spec) {
return lbaasv2API.createL7Policy(spec);
}
function createL7Rule(spec) {
return lbaasv2API.createL7Rule(model.spec.parentResourceId, spec);
}
function createPool(spec) {
return lbaasv2API.createPool(spec);
}
@ -355,6 +413,14 @@
return lbaasv2API.editListener(model.context.id, spec);
}
function editL7Policy(spec) {
return lbaasv2API.editL7Policy(model.context.id, spec);
}
function editL7Rule(spec) {
return lbaasv2API.editL7Rule(model.spec.parentResourceId, model.context.id, spec);
}
function editPool(spec) {
return lbaasv2API.editPool(model.context.id, spec);
}
@ -576,6 +642,15 @@
return lbaasv2API.getListener(model.context.id, true);
}
function getL7Policy() {
return lbaasv2API.getL7Policy(model.context.id, true);
}
function getL7Rule() {
return lbaasv2API.getL7Rule(model.spec.parentResourceId,
model.context.id);
}
function getPool() {
return lbaasv2API.getPool(model.context.id, true);
}
@ -633,6 +708,18 @@
}
}
function onGetL7Policy(response) {
var result = response.data;
setL7PolicySpec(result.l7policy || result);
}
function onGetL7Rule(response) {
var result = response.data;
setL7RuleSpec(result.l7rule || result);
}
function onGetPool(response) {
var result = response.data;
@ -674,6 +761,29 @@
spec.default_pool_id = listener.default_pool_id;
}
function setL7PolicySpec(l7policy) {
var spec = model.spec.l7policy;
spec.id = l7policy.id;
spec.name = l7policy.name;
spec.description = l7policy.description;
spec.action = l7policy.action;
spec.position = l7policy.position;
spec.redirect_pool_id = l7policy.redirect_pool_id;
spec.redirect_url = l7policy.redirect_url;
spec.admin_state_up = l7policy.admin_state_up;
}
function setL7RuleSpec(l7rule) {
var spec = model.spec.l7rule;
spec.id = l7rule.id;
spec.type = l7rule.type;
spec.compare_type = l7rule.compare_type;
spec.key = l7rule.key;
spec.rule_value = l7rule.rule_value;
spec.invert = l7rule.invert;
spec.admin_state_up = l7rule.admin_state_up;
}
function setPoolSpec(pool) {
var spec = model.spec.pool;
spec.id = pool.id;

View File

@ -141,6 +141,37 @@
deferred.resolve({ data: listenerData });
return deferred.promise;
},
getL7Policy: function() {
var l7policy = {
admin_state_up: true,
id: '1234',
name: 'L7 Policy 1',
description: 'l7 policy description',
action: 'REDIRECT_TO_URL',
position: 1,
redirect_url: 'http://example.com'
};
var deferred = $q.defer();
deferred.resolve({ data: l7policy });
return deferred.promise;
},
getL7Rule: function() {
var l7rule = {
admin_state_up: true,
id: '1234',
type: 'HOST_NAME',
compare_type: 'EQUAL_TO',
value: 'api.example.com',
invert: false
};
var deferred = $q.defer();
deferred.resolve({ data: l7rule });
return deferred.promise;
},
getPool: function() {
var poolResources = angular.copy(listenerResources);
delete poolResources.listener;
@ -202,6 +233,18 @@
editListener: function(id, spec) {
return spec;
},
createL7Policy: function(spec) {
return spec;
},
editL7Policy: function(id, spec) {
return spec;
},
createL7Rule: function(l7policyId, spec) {
return spec;
},
editL7Rule: function(l7policyId, l7ruleId, spec) {
return spec;
},
createPool: function(spec) {
return spec;
},
@ -463,6 +506,82 @@
});
});
describe('Post initialize model (create l7 policy)', function() {
beforeEach(function() {
includeChildResources = false;
model.initialize('l7policy', false, '1234', '5678');
scope.$apply();
});
it('should initialize model properties', function() {
expect(model.initializing).toBe(false);
expect(model.initialized).toBe(true);
expect(model.subnets.length).toBe(0);
expect(model.members.length).toBe(0);
expect(model.certificates.length).toBe(0);
expect(model.listenerPorts.length).toBe(0);
expect(model.spec).toBeDefined();
expect(model.spec.loadbalancer_id).toBe('1234');
expect(model.spec.parentResourceId).toBe('5678');
expect(model.spec.loadbalancer).toBeDefined();
expect(model.spec.listener).toBeDefined();
expect(model.spec.pool).toBeDefined();
expect(model.spec.members.length).toBe(0);
expect(model.spec.certificates).toEqual([]);
expect(model.spec.monitor).toBeDefined();
expect(model.certificatesError).toBe(false);
});
it('should initialize names', function() {
expect(model.spec.l7policy.name).toBe('L7 Policy 1');
});
it('should initialize context properties', function() {
expect(model.context.resource).toBe('l7policy');
expect(model.context.id).toBeFalsy();
expect(model.context.submit).toBeDefined();
});
});
describe('Post initialize model (create l7 rule)', function() {
beforeEach(function() {
includeChildResources = false;
model.initialize('l7rule', false, '1234', '5678');
scope.$apply();
});
it('should initialize model properties', function() {
expect(model.initializing).toBe(false);
expect(model.initialized).toBe(true);
expect(model.subnets.length).toBe(0);
expect(model.members.length).toBe(0);
expect(model.certificates.length).toBe(0);
expect(model.listenerPorts.length).toBe(0);
expect(model.spec).toBeDefined();
expect(model.spec.loadbalancer_id).toBe('1234');
expect(model.spec.parentResourceId).toBe('5678');
expect(model.spec.loadbalancer).toBeDefined();
expect(model.spec.listener).toBeDefined();
expect(model.spec.pool).toBeDefined();
expect(model.spec.members.length).toBe(0);
expect(model.spec.certificates).toEqual([]);
expect(model.spec.monitor).toBeDefined();
expect(model.certificatesError).toBe(false);
});
it('should initialize invert', function() {
expect(model.spec.l7rule.invert).toBe(false);
});
it('should initialize context properties', function() {
expect(model.context.resource).toBe('l7rule');
expect(model.context.id).toBeFalsy();
expect(model.context.submit).toBeDefined();
});
});
describe('Post initialize model (create pool with listener)', function() {
beforeEach(function() {
@ -794,6 +913,53 @@
});
});
describe('Post initialize model (edit l7 policy)', function() {
beforeEach(function() {
model.initialize('l7policy', '1234', 'loadbalancerId', 'listenerId');
scope.$apply();
});
it('should initialize model properties', function() {
expect(model.initializing).toBe(false);
expect(model.initialized).toBe(true);
expect(model.spec).toBeDefined();
expect(model.spec.loadbalancer_id).toBeDefined();
expect(model.spec.l7policy.id).toBe('1234');
expect(model.spec.l7policy.name).toBe('L7 Policy 1');
expect(model.spec.l7policy.description).toBe('l7 policy description');
});
it('should initialize context', function() {
expect(model.context.resource).toBe('l7policy');
expect(model.context.id).toBeDefined();
expect(model.context.submit).toBeDefined();
});
});
describe('Post initialize model (edit l7 rule)', function() {
beforeEach(function() {
model.initialize('l7rule', '1234', 'loadbalancerId', 'l7policyId');
scope.$apply();
});
it('should initialize model properties', function() {
expect(model.initializing).toBe(false);
expect(model.initialized).toBe(true);
expect(model.spec).toBeDefined();
expect(model.spec.loadbalancer_id).toBeDefined();
expect(model.spec.l7rule.id).toBe('1234');
expect(model.spec.l7rule.type).toBe('HOST_NAME');
});
it('should initialize context', function() {
expect(model.context.resource).toBe('l7rule');
expect(model.context.id).toBeDefined();
expect(model.context.submit).toBeDefined();
});
});
describe('Post initialize model (edit pool)', function() {
beforeEach(function() {
@ -1005,9 +1171,11 @@
// This is here to ensure that as people add/change spec properties, they don't forget
// to implement tests for them.
it('has the right number of properties', function() {
expect(Object.keys(model.spec).length).toBe(9);
expect(Object.keys(model.spec).length).toBe(11);
expect(Object.keys(model.spec.loadbalancer).length).toBe(5);
expect(Object.keys(model.spec.listener).length).toBe(8);
expect(Object.keys(model.spec.l7policy).length).toBe(8);
expect(Object.keys(model.spec.l7rule).length).toBe(7);
expect(Object.keys(model.spec.pool).length).toBe(8);
expect(Object.keys(model.spec.monitor).length).toBe(10);
expect(model.spec.members).toEqual([]);
@ -1716,6 +1884,47 @@
});
});
describe('Model submit function (create l7 policy)', function() {
beforeEach(function() {
model.initialize('l7policy', null, '1234', 'listener1');
scope.$apply();
});
it('should set final spec properties', function() {
model.spec.l7policy.action = 'REJECT';
var finalSpec = model.submit();
expect(finalSpec.loadbalancer_id).toBe('1234');
expect(finalSpec.parentResourceId).toBe('listener1');
expect(finalSpec.loadbalancer).toBeUndefined();
expect(finalSpec.listener).toBeDefined();
expect(finalSpec.l7policy.action).toBe('REJECT');
});
});
describe('Model submit function (create l7 rule)', function() {
beforeEach(function() {
model.initialize('l7rule', null, '1234', 'l7policy1');
scope.$apply();
});
it('should set final spec properties', function() {
model.spec.l7rule.type = 'PATH';
var finalSpec = model.submit();
expect(finalSpec.loadbalancer_id).toBe('1234');
expect(finalSpec.parentResourceId).toBe('l7policy1');
expect(finalSpec.loadbalancer).toBeUndefined();
expect(finalSpec.l7rule.type).toBe('PATH');
});
});
describe('Model submit function (create pool)', function() {
beforeEach(function() {
@ -1977,6 +2186,45 @@
});
});
describe('Model submit function (edit l7 policy)', function() {
beforeEach(function() {
model.initialize('l7policy', 'l7policy1', '1234', '1234');
scope.$apply();
});
it('should set final spec properties', function() {
var finalSpec = model.submit();
expect(finalSpec.loadbalancer_id).toBe('1234');
expect(finalSpec.parentResourceId).toBe('1234');
expect(finalSpec.loadbalancer).toBeUndefined();
expect(finalSpec.listener).toBeDefined();
expect(finalSpec.l7policy.action).toBe('REDIRECT_TO_URL');
});
});
describe('Model submit function (edit l7 rule)', function() {
beforeEach(function() {
model.initialize('l7rule', '1234', '1234', '1234');
scope.$apply();
});
it('should set final spec properties', function() {
var finalSpec = model.submit();
expect(finalSpec.loadbalancer_id).toBe('1234');
expect(finalSpec.parentResourceId).toBe('1234');
expect(finalSpec.loadbalancer).toBeUndefined();
expect(finalSpec.l7rule.type).toBe('HOST_NAME');
});
});
describe('Model submit function (edit pool)', function() {
beforeEach(function() {

View File

@ -42,6 +42,20 @@
helpUrl: basePath + 'workflow/listener/listener.help.html',
formName: 'listenerDetailsForm'
},
{
id: 'l7policy',
title: gettext('L7 Policy Details'),
templateUrl: basePath + 'workflow/l7policy/l7policy.html',
helpUrl: basePath + 'workflow/l7policy/l7policy.help.html',
formName: 'l7policyDetailsForm'
},
{
id: 'l7rule',
title: gettext('L7 Rule Details'),
templateUrl: basePath + 'workflow/l7rule/l7rule.html',
helpUrl: basePath + 'workflow/l7rule/l7rule.help.html',
formName: 'l7ruleDetailsForm'
},
{
id: 'pool',
title: gettext('Pool Details'),

View File

@ -44,11 +44,13 @@
it('should have default steps defined', function () {
var workflow = workflowService('My Workflow');
expect(workflow.steps).toBeDefined();
expect(workflow.steps.length).toBe(6);
expect(workflow.steps.length).toBe(8);
var forms = [
'loadBalancerDetailsForm',
'listenerDetailsForm',
'l7policyDetailsForm',
'l7ruleDetailsForm',
'poolDetailsForm',
'memberDetailsForm',
'monitorDetailsForm',

View File

@ -0,0 +1,4 @@
---
features:
- |
Adds L7 policy support to the dashboard.