Add monitor tab to create load balancer workflow
This adds the monitor tab to the create load balancer workflow and allows adding a monitor to the pool. Partially-Implements: blueprint horizon-lbaas-v2-ui Change-Id: I23ee916f283b2b3f782327f16ca1624a98568cb0
This commit is contained in:
parent
9c4ca8d5f9
commit
729c223f77
|
@ -105,6 +105,10 @@ def create_pool(request, **kwargs):
|
|||
kwargs = {'callback_kwargs': {'pool_id': pool['id'],
|
||||
'index': 0}}
|
||||
thread.start_new_thread(poll_loadbalancer_status, args, kwargs)
|
||||
elif data.get('monitor'):
|
||||
args = (request, kwargs['loadbalancer_id'], add_monitor)
|
||||
kwargs = {'callback_kwargs': {'pool_id': pool['id']}}
|
||||
thread.start_new_thread(poll_loadbalancer_status, args, kwargs)
|
||||
|
||||
return pool
|
||||
|
||||
|
@ -133,10 +137,36 @@ def add_member(request, **kwargs):
|
|||
kwargs = {'callback_kwargs': {'pool_id': kwargs['pool_id'],
|
||||
'index': index}}
|
||||
thread.start_new_thread(poll_loadbalancer_status, args, kwargs)
|
||||
elif data.get('monitor'):
|
||||
args = (request, kwargs['loadbalancer_id'], add_monitor)
|
||||
kwargs = {'callback_kwargs': {'pool_id': kwargs['pool_id']}}
|
||||
thread.start_new_thread(poll_loadbalancer_status, args, kwargs)
|
||||
|
||||
return member
|
||||
|
||||
|
||||
def add_monitor(request, **kwargs):
|
||||
"""Create a new health monitor for a pool.
|
||||
|
||||
"""
|
||||
data = request.DATA
|
||||
monitorSpec = {
|
||||
'type': data['monitor']['type'],
|
||||
'delay': data['monitor']['interval'],
|
||||
'timeout': data['monitor']['timeout'],
|
||||
'max_retries': data['monitor']['retry'],
|
||||
'pool_id': kwargs['pool_id']
|
||||
}
|
||||
if data['monitor'].get('method'):
|
||||
monitorSpec['http_method'] = data['monitor']['method']
|
||||
if data['monitor'].get('path'):
|
||||
monitorSpec['url_path'] = data['monitor']['path']
|
||||
if data['monitor'].get('status'):
|
||||
monitorSpec['expected_codes'] = data['monitor']['status']
|
||||
return neutronclient(request).create_lbaas_healthmonitor(
|
||||
{'healthmonitor': monitorSpec}).get('healthmonitor')
|
||||
|
||||
|
||||
@urls.register
|
||||
class LoadBalancers(generic.View):
|
||||
"""API for load balancers.
|
||||
|
|
|
@ -51,7 +51,9 @@ ADD_JS_FILES = [
|
|||
('dashboard/project/lbaasv2/loadbalancers/actions/create/pool/'
|
||||
'pool.controller.js'),
|
||||
('dashboard/project/lbaasv2/loadbalancers/actions/create/members/'
|
||||
'members.controller.js')
|
||||
'members.controller.js'),
|
||||
('dashboard/project/lbaasv2/loadbalancers/actions/create/monitor/'
|
||||
'monitor.controller.js')
|
||||
]
|
||||
|
||||
ADD_JS_SPEC_FILES = [
|
||||
|
@ -78,7 +80,9 @@ ADD_JS_SPEC_FILES = [
|
|||
('dashboard/project/lbaasv2/loadbalancers/actions/create/pool/'
|
||||
'pool.controller.spec.js'),
|
||||
('dashboard/project/lbaasv2/loadbalancers/actions/create/members/'
|
||||
'members.controller.spec.js')
|
||||
'members.controller.spec.js'),
|
||||
('dashboard/project/lbaasv2/loadbalancers/actions/create/monitor/'
|
||||
'monitor.controller.spec.js')
|
||||
]
|
||||
|
||||
ADD_SCSS_FILES = [
|
||||
|
|
|
@ -29,12 +29,16 @@
|
|||
'horizon.dashboard.project.lbaasv2.loadbalancers'
|
||||
])
|
||||
.config(config)
|
||||
/* eslint-disable max-len */
|
||||
.constant('horizon.dashboard.project.lbaasv2.patterns', {
|
||||
/* eslint-disable max-len */
|
||||
ipv4: '^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))$',
|
||||
ipv6: '^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?$'
|
||||
ipv6: '^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?$',
|
||||
/* eslint-enable max-len */
|
||||
// HTTP status codes - comma separated numbers or ranges
|
||||
httpStatusCodes: /^\d+(\s*-\s*\d+)?(,\s*\d+(\s*-\s*\d+)?)*$/,
|
||||
// URL path - must start with "/" and can include anything after that
|
||||
urlPath: /^((\/)|(\/[^/]+)+)$/
|
||||
})
|
||||
/* eslint-enable max-len */
|
||||
.constant('horizon.dashboard.project.lbaasv2.popovers', {
|
||||
ipAddresses: '<ul><li ng-repeat="addr in member.addresses">{$ addr.ip $}</li></ul>'
|
||||
});
|
||||
|
@ -42,13 +46,10 @@
|
|||
config.$inject = [
|
||||
'$provide',
|
||||
'$windowProvider',
|
||||
'$routeProvider',
|
||||
'$locationProvider'
|
||||
'$routeProvider'
|
||||
];
|
||||
|
||||
function config($provide, $windowProvider, $routeProvider, $locationProvider) {
|
||||
$locationProvider.html5Mode(true).hashPrefix('!');
|
||||
|
||||
function config($provide, $windowProvider, $routeProvider) {
|
||||
var href = '/project/ngloadbalancersv2/';
|
||||
var basePath = $windowProvider.$get().STATIC_URL + 'dashboard/project/lbaasv2/';
|
||||
$provide.constant('horizon.dashboard.project.lbaasv2.basePath', basePath);
|
||||
|
|
|
@ -41,19 +41,52 @@
|
|||
});
|
||||
});
|
||||
|
||||
describe('LBaaS v2 Module Constants', function () {
|
||||
var patterns, popovers;
|
||||
|
||||
beforeEach(module('horizon.dashboard.project.lbaasv2'));
|
||||
|
||||
beforeEach(inject(function ($injector) {
|
||||
patterns = $injector.get('horizon.dashboard.project.lbaasv2.patterns');
|
||||
popovers = $injector.get('horizon.dashboard.project.lbaasv2.popovers');
|
||||
}));
|
||||
|
||||
it('should define patterns', function () {
|
||||
expect(patterns).toBeDefined();
|
||||
});
|
||||
|
||||
it('should define expected patterns', function () {
|
||||
expect(Object.keys(patterns).length).toBe(4);
|
||||
var keys = ['ipv4', 'ipv6', 'httpStatusCodes', 'urlPath'];
|
||||
angular.forEach(keys, function(key) {
|
||||
expect(patterns[key]).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('should define popovers', function () {
|
||||
expect(popovers).toBeDefined();
|
||||
});
|
||||
|
||||
it('should define expected popover templates', function () {
|
||||
expect(Object.keys(popovers).length).toBe(1);
|
||||
var keys = ['ipAddresses'];
|
||||
angular.forEach(keys, function(key) {
|
||||
expect(popovers[key]).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('LBaaS v2 Module Config', function () {
|
||||
var $routeProvider, $locationProvider, basePath;
|
||||
var $routeProvider, basePath;
|
||||
|
||||
beforeEach(function() {
|
||||
// Create a dummy module so that we can test $routeProvider and $locationProvider calls
|
||||
// in our actual config block.
|
||||
// Create a dummy module so that we can test $routeProvider calls in our actual
|
||||
// config block.
|
||||
angular.module('configTest', [])
|
||||
.config(function(_$routeProvider_, _$locationProvider_, $windowProvider) {
|
||||
.config(function(_$routeProvider_, $windowProvider) {
|
||||
$routeProvider = _$routeProvider_;
|
||||
$locationProvider = _$locationProvider_;
|
||||
basePath = $windowProvider.$get().STATIC_URL + 'dashboard/project/lbaasv2/';
|
||||
spyOn($routeProvider, 'when').and.callThrough();
|
||||
spyOn($locationProvider, 'html5Mode').and.callThrough();
|
||||
});
|
||||
module('ngRoute');
|
||||
module('configTest');
|
||||
|
@ -61,10 +94,6 @@
|
|||
inject();
|
||||
});
|
||||
|
||||
it('should use html5 mode', function () {
|
||||
expect($locationProvider.html5Mode).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it('should route URLs', function () {
|
||||
var href = '/project/ngloadbalancersv2/';
|
||||
var routes = [
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
</thead>
|
||||
<tbody>
|
||||
<tr ng-if="ctrl.tableData.allocated.length === 0">
|
||||
<td colspan="0">
|
||||
<td colspan="100">
|
||||
<div class="no-rows-help">
|
||||
{$ ::trCtrl.helpText.noneAllocText $}
|
||||
</div>
|
||||
|
@ -93,7 +93,7 @@
|
|||
</td>
|
||||
</tr>
|
||||
<tr ng-repeat-end class="detail-row">
|
||||
<td class="detail" colspan="0">
|
||||
<td class="detail" colspan="100">
|
||||
<div class="row">
|
||||
<dl class="rsp-alt-p2 col-sm-2">
|
||||
<dt translate>Name</dt>
|
||||
|
@ -117,7 +117,7 @@
|
|||
hz-table class="table-striped table-rsp table-detail modern">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="search-header" colspan="0">
|
||||
<th class="search-header" colspan="100">
|
||||
<hz-search-bar group-classes="input-group-sm" icon-classes="fa-search">
|
||||
</hz-search-bar>
|
||||
</th>
|
||||
|
@ -132,7 +132,7 @@
|
|||
</thead>
|
||||
<tbody>
|
||||
<tr ng-if="trCtrl.numAvailable() === 0">
|
||||
<td colspan="0">
|
||||
<td colspan="100">
|
||||
<div class="no-rows-help">
|
||||
{$ ::trCtrl.helpText.noneAvailText $}
|
||||
</div>
|
||||
|
@ -168,7 +168,7 @@
|
|||
</td>
|
||||
</tr>
|
||||
<tr ng-repeat-end class="detail-row">
|
||||
<td class="detail" colspan="0">
|
||||
<td class="detail" colspan="100">
|
||||
<div class="row">
|
||||
<dl class="rsp-alt-p2 col-sm-2">
|
||||
<dt translate>Name</dt>
|
||||
|
|
|
@ -79,6 +79,8 @@
|
|||
listenerProtocols: ['TCP', 'HTTP', 'HTTPS'],
|
||||
poolProtocols: ['TCP', 'HTTP', 'HTTPS'],
|
||||
methods: ['ROUND_ROBIN', 'LEAST_CONNECTIONS', 'SOURCE_IP'],
|
||||
monitorTypes: ['HTTP', 'HTTPS', 'PING', 'TCP'],
|
||||
monitorMethods: ['GET', 'HEAD'],
|
||||
|
||||
/**
|
||||
* api methods for UI controllers
|
||||
|
@ -119,6 +121,15 @@
|
|||
protocol: null,
|
||||
method: null
|
||||
},
|
||||
monitor: {
|
||||
type: null,
|
||||
interval: null,
|
||||
retry: null,
|
||||
timeout: null,
|
||||
method: 'GET',
|
||||
status: '200',
|
||||
path: '/'
|
||||
},
|
||||
members: []
|
||||
};
|
||||
|
||||
|
@ -175,6 +186,17 @@
|
|||
delete finalSpec.pool;
|
||||
}
|
||||
|
||||
cleanFinalSpecMembers(finalSpec);
|
||||
cleanFinalSpecMonitor(finalSpec);
|
||||
removeNulls(finalSpec);
|
||||
|
||||
finalSpec.loadbalancer.subnet = finalSpec.loadbalancer.subnet.id;
|
||||
|
||||
return lbaasv2API.createLoadBalancer(finalSpec);
|
||||
}
|
||||
|
||||
function cleanFinalSpecMembers(finalSpec) {
|
||||
|
||||
// Members require a pool, address, subnet, and port but the wizard requires the address,
|
||||
// subnet, and port so we can assume those exist here.
|
||||
if (!finalSpec.pool || finalSpec.members.length === 0) {
|
||||
|
@ -189,8 +211,27 @@
|
|||
member.subnet = member.address.subnet;
|
||||
member.address = member.address.ip;
|
||||
});
|
||||
}
|
||||
|
||||
// Delete null properties
|
||||
function cleanFinalSpecMonitor(finalSpec) {
|
||||
|
||||
// Monitor requires a pool, interval, retry count, and timeout
|
||||
if (!finalSpec.pool ||
|
||||
!angular.isNumber(finalSpec.monitor.interval) ||
|
||||
!angular.isNumber(finalSpec.monitor.retry) ||
|
||||
!angular.isNumber(finalSpec.monitor.timeout)) {
|
||||
delete finalSpec.monitor;
|
||||
}
|
||||
|
||||
// Only include necessary monitor properties
|
||||
if (finalSpec.monitor && finalSpec.monitor.type !== 'HTTP') {
|
||||
delete finalSpec.monitor.method;
|
||||
delete finalSpec.monitor.status;
|
||||
delete finalSpec.monitor.path;
|
||||
}
|
||||
}
|
||||
|
||||
function removeNulls(finalSpec) {
|
||||
angular.forEach(finalSpec, function deleteNullsForGroup(group, groupName) {
|
||||
angular.forEach(group, function deleteNullValue(value, key) {
|
||||
if (value === null) {
|
||||
|
@ -198,10 +239,6 @@
|
|||
}
|
||||
});
|
||||
});
|
||||
|
||||
finalSpec.loadbalancer.subnet = finalSpec.loadbalancer.subnet.id;
|
||||
|
||||
return lbaasv2API.createLoadBalancer(finalSpec);
|
||||
}
|
||||
|
||||
function onGetLoadBalancers(response) {
|
||||
|
|
|
@ -114,10 +114,18 @@
|
|||
expect(model.listenerProtocols).toEqual(['TCP', 'HTTP', 'HTTPS']);
|
||||
});
|
||||
|
||||
it('has array of methods', function() {
|
||||
it('has array of pool methods', function() {
|
||||
expect(model.methods).toEqual(['ROUND_ROBIN', 'LEAST_CONNECTIONS', 'SOURCE_IP']);
|
||||
});
|
||||
|
||||
it('has array of monitor types', function() {
|
||||
expect(model.monitorTypes).toEqual(['HTTP', 'HTTPS', 'PING', 'TCP']);
|
||||
});
|
||||
|
||||
it('has array of monitor methods', function() {
|
||||
expect(model.monitorMethods).toEqual(['GET', 'HEAD']);
|
||||
});
|
||||
|
||||
it('has an "initialize" function', function() {
|
||||
expect(model.initialize).toBeDefined();
|
||||
});
|
||||
|
@ -144,6 +152,7 @@
|
|||
expect(model.spec.listener).toBeDefined();
|
||||
expect(model.spec.pool).toBeDefined();
|
||||
expect(model.spec.members).toEqual([]);
|
||||
expect(model.spec.monitor).toBeDefined();
|
||||
});
|
||||
|
||||
it('should initialize names', function() {
|
||||
|
@ -151,6 +160,12 @@
|
|||
expect(model.spec.listener.name).toBe('Listener 1');
|
||||
expect(model.spec.pool.name).toBe('Pool 1');
|
||||
});
|
||||
|
||||
it('should initialize monitor fields', function() {
|
||||
expect(model.spec.monitor.method).toBe('GET');
|
||||
expect(model.spec.monitor.status).toBe('200');
|
||||
expect(model.spec.monitor.path).toBe('/');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Initialization failure', function() {
|
||||
|
@ -188,10 +203,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(4);
|
||||
expect(Object.keys(model.spec).length).toBe(5);
|
||||
expect(Object.keys(model.spec.loadbalancer).length).toBe(4);
|
||||
expect(Object.keys(model.spec.listener).length).toBe(4);
|
||||
expect(Object.keys(model.spec.pool).length).toBe(4);
|
||||
expect(Object.keys(model.spec.monitor).length).toBe(7);
|
||||
});
|
||||
|
||||
it('sets load balancer name to null', function() {
|
||||
|
@ -241,6 +257,34 @@
|
|||
it('sets pool method to null', function() {
|
||||
expect(model.spec.pool.method).toBeNull();
|
||||
});
|
||||
|
||||
it('sets monitor type to null', function() {
|
||||
expect(model.spec.monitor.type).toBeNull();
|
||||
});
|
||||
|
||||
it('sets monitor interval to null', function() {
|
||||
expect(model.spec.monitor.interval).toBeNull();
|
||||
});
|
||||
|
||||
it('sets monitor retry count to null', function() {
|
||||
expect(model.spec.monitor.retry).toBeNull();
|
||||
});
|
||||
|
||||
it('sets monitor timeout to null', function() {
|
||||
expect(model.spec.monitor.timeout).toBeNull();
|
||||
});
|
||||
|
||||
it('sets monitor method to default', function() {
|
||||
expect(model.spec.monitor.method).toBe('GET');
|
||||
});
|
||||
|
||||
it('sets monitor status code to default', function() {
|
||||
expect(model.spec.monitor.status).toBe('200');
|
||||
});
|
||||
|
||||
it('sets monitor URL path to default', function() {
|
||||
expect(model.spec.monitor.path).toBe('/');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Create Load Balancer', function() {
|
||||
|
@ -269,6 +313,10 @@
|
|||
port: 80,
|
||||
weight: 1
|
||||
}];
|
||||
model.spec.monitor.type = 'PING';
|
||||
model.spec.monitor.interval = 1;
|
||||
model.spec.monitor.retry = 1;
|
||||
model.spec.monitor.timeout = 1;
|
||||
|
||||
var finalSpec = model.createLoadBalancer();
|
||||
|
||||
|
@ -293,6 +341,10 @@
|
|||
expect(finalSpec.members[0].id).toBeUndefined();
|
||||
expect(finalSpec.members[0].name).toBeUndefined();
|
||||
expect(finalSpec.members[0].description).toBeUndefined();
|
||||
expect(finalSpec.monitor.type).toBe('PING');
|
||||
expect(finalSpec.monitor.interval).toBe(1);
|
||||
expect(finalSpec.monitor.retry).toBe(1);
|
||||
expect(finalSpec.monitor.timeout).toBe(1);
|
||||
});
|
||||
|
||||
it('should delete listener if any required property is not set', function() {
|
||||
|
@ -335,6 +387,26 @@
|
|||
expect(finalSpec.pool).toBeDefined();
|
||||
expect(finalSpec.members).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should delete monitor if any required property not set', function() {
|
||||
model.spec.loadbalancer.ip = '1.2.3.4';
|
||||
model.spec.loadbalancer.subnet = model.subnets[0];
|
||||
model.spec.listener.protocol = 'HTTPS';
|
||||
model.spec.listener.port = 80;
|
||||
model.spec.pool.protocol = 'HTTP';
|
||||
model.spec.pool.method = 'LEAST_CONNECTIONS';
|
||||
model.spec.monitor.type = 'PING';
|
||||
model.spec.monitor.interval = 1;
|
||||
model.spec.monitor.retry = 1;
|
||||
|
||||
var finalSpec = model.createLoadBalancer();
|
||||
|
||||
expect(finalSpec.loadbalancer).toBeDefined();
|
||||
expect(finalSpec.listener).toBeDefined();
|
||||
expect(finalSpec.pool).toBeDefined();
|
||||
expect(finalSpec.members).toBeUndefined();
|
||||
expect(finalSpec.monitor).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright 2016 IBM Corp.
|
||||
*
|
||||
* 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('CreateMonitorController', CreateMonitorController);
|
||||
|
||||
CreateMonitorController.$inject = [
|
||||
'horizon.dashboard.project.lbaasv2.patterns',
|
||||
'horizon.framework.util.i18n.gettext'
|
||||
];
|
||||
|
||||
/**
|
||||
* @ngdoc controller
|
||||
* @name CreateMonitorController
|
||||
* @description
|
||||
* The `CreateMonitorController` controller provides functions for
|
||||
* configuring the health monitor step when creating a new health monitor.
|
||||
* @param patterns The LBaaS v2 patterns constant.
|
||||
* @param gettext The horizon gettext function for translation.
|
||||
* @returns undefined
|
||||
*/
|
||||
|
||||
function CreateMonitorController(patterns, gettext) {
|
||||
|
||||
var ctrl = this;
|
||||
|
||||
// Error text for invalid fields
|
||||
/* eslint-disable max-len */
|
||||
ctrl.intervalError = gettext('The health check interval must be greater than or equal to the timeout.');
|
||||
/* eslint-enable max-len */
|
||||
ctrl.retryError = gettext('The max retry count must be a number between 1 and 10.');
|
||||
ctrl.timeoutError = gettext('The timeout must be a number greater than or equal to 0.');
|
||||
ctrl.statusError = gettext('The expected status code is not valid.');
|
||||
ctrl.pathError = gettext('The URL path is not valid.');
|
||||
|
||||
// Field level help text
|
||||
ctrl.statusHelp = gettext('Enter comma separated values or ranges.');
|
||||
ctrl.intervalHelp = gettext('The delay between health check calls.');
|
||||
ctrl.retryHelp = interpolate(
|
||||
/* eslint-disable max-len */
|
||||
gettext('The number of allowed connection failures before changing the status of the member to %(state)s.'),
|
||||
/* eslint-enable max-len */
|
||||
{ state: gettext('Inactive') },
|
||||
true);
|
||||
|
||||
// HTTP status codes validation pattern
|
||||
ctrl.statusPattern = patterns.httpStatusCodes;
|
||||
ctrl.urlPathPattern = patterns.urlPath;
|
||||
}
|
||||
})();
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright 2016 IBM Corp.
|
||||
*
|
||||
* 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('Create Monitor Step', function() {
|
||||
|
||||
beforeEach(module('horizon.framework.util'));
|
||||
beforeEach(module('horizon.dashboard.project.lbaasv2'));
|
||||
|
||||
describe('CreateMonitorController', function() {
|
||||
var ctrl;
|
||||
|
||||
beforeEach(inject(function($controller) {
|
||||
ctrl = $controller('CreateMonitorController');
|
||||
}));
|
||||
|
||||
it('should define error messages for invalid fields', function() {
|
||||
expect(ctrl.intervalError).toBeDefined();
|
||||
expect(ctrl.retryError).toBeDefined();
|
||||
expect(ctrl.timeoutError).toBeDefined();
|
||||
expect(ctrl.statusError).toBeDefined();
|
||||
expect(ctrl.pathError).toBeDefined();
|
||||
});
|
||||
|
||||
it('should define field level help messages', function() {
|
||||
expect(ctrl.statusHelp).toBeDefined();
|
||||
expect(ctrl.intervalHelp).toBeDefined();
|
||||
expect(ctrl.retryHelp).toBeDefined();
|
||||
});
|
||||
|
||||
it('should define patterns for field validation', function() {
|
||||
expect(ctrl.statusPattern).toBeDefined();
|
||||
expect(ctrl.urlPathPattern).toBeDefined();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
})();
|
|
@ -0,0 +1,3 @@
|
|||
<h1 translate>Monitor Help</h1>
|
||||
|
||||
<p translate>When adding a load balancer, you can also specify a health check monitor to use to determine the health of your instances. Health checks routinely run against each instance within a target load balancer and the result of the health check is used to determine if the instance receives new connections.</p>
|
|
@ -0,0 +1,131 @@
|
|||
<div ng-controller="CreateMonitorController as ctrl">
|
||||
<h1 translate>Monitor</h1>
|
||||
|
||||
<!--content-->
|
||||
<div class="content">
|
||||
<div translate class="subtitle">Provide the details for the new health monitor. The monitor will only be created if values are provided for all fields marked as required. A listener and pool are also required.</div>
|
||||
|
||||
<div class="row form-group">
|
||||
|
||||
<div class="col-sm-6 col-md-3">
|
||||
<div class="form-field required monitor-type">
|
||||
<label translate class="on-top" for="monitor-type">Monitor type</label>
|
||||
<select class="form-control input-sm" name="monitor-type"
|
||||
id="monitor-type"
|
||||
ng-options="type for type in model.monitorTypes"
|
||||
ng-model="model.spec.monitor.type">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row form-group">
|
||||
|
||||
<div class="col-sm-6 col-md-3">
|
||||
<div class="form-field required monitor-interval"
|
||||
ng-class="{ 'has-error': createLoadBalancerMonitorForm['monitor-interval'].$invalid && createLoadBalancerMonitorForm['monitor-interval'].$dirty }">
|
||||
<label translate class="on-top" for="monitor-interval">Health check interval (sec)</label>
|
||||
<span class="fa fa-exclamation-triangle invalid"
|
||||
ng-show="createLoadBalancerMonitorForm['monitor-interval'].$invalid && createLoadBalancerMonitorForm.$dirty"
|
||||
popover="{$ ::ctrl.intervalError $}"
|
||||
popover-placement="top" popover-append-to-body="true"
|
||||
popover-trigger="hover"></span>
|
||||
<span class="fa fa-question-circle pull-right"
|
||||
popover="{$ ::ctrl.intervalHelp $}"
|
||||
popover-placement="top" popover-append-to-body="true"
|
||||
popover-trigger="hover"></span>
|
||||
<input name="monitor-interval" id="monitor-interval"
|
||||
type="number" class="form-control input-sm"
|
||||
ng-model="model.spec.monitor.interval" ng-pattern="/^\d+$/" ng-min="model.spec.monitor.timeout">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 col-md-3">
|
||||
<div class="form-field required monitor-retry"
|
||||
ng-class="{ 'has-error': createLoadBalancerMonitorForm['monitor-retry'].$invalid && createLoadBalancerMonitorForm['monitor-retry'].$dirty }">
|
||||
<label translate class="on-top" for="monitor-retry">Retry count before markdown</label>
|
||||
<span class="fa fa-exclamation-triangle invalid"
|
||||
ng-show="createLoadBalancerMonitorForm['monitor-retry'].$invalid && createLoadBalancerMonitorForm.$dirty"
|
||||
popover="{$ ::ctrl.retryError $}"
|
||||
popover-placement="top" popover-append-to-body="true"
|
||||
popover-trigger="hover"></span>
|
||||
<span class="fa fa-question-circle pull-right"
|
||||
popover="{$ ::ctrl.retryHelp $}"
|
||||
popover-placement="top" popover-append-to-body="true"
|
||||
popover-trigger="hover"></span>
|
||||
<input name="monitor-retry" id="monitor-retry"
|
||||
type="number" class="form-control input-sm"
|
||||
ng-model="model.spec.monitor.retry" ng-pattern="/^\d+$/" min="1" max="10">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 col-md-3">
|
||||
<div class="form-field required monitor-timeout"
|
||||
ng-class="{ 'has-error': createLoadBalancerMonitorForm['monitor-timeout'].$invalid && createLoadBalancerMonitorForm['monitor-timeout'].$dirty }">
|
||||
<label translate class="on-top" for="monitor-timeout">Timeout (sec)</label>
|
||||
<span class="fa fa-exclamation-triangle invalid"
|
||||
ng-show="createLoadBalancerMonitorForm['monitor-timeout'].$invalid && createLoadBalancerMonitorForm.$dirty"
|
||||
popover="{$ ::ctrl.timeoutError $}"
|
||||
popover-placement="top" popover-append-to-body="true"
|
||||
popover-trigger="hover"></span>
|
||||
<input name="monitor-timeout" id="monitor-timeout"
|
||||
type="number" class="form-control input-sm"
|
||||
ng-model="model.spec.monitor.timeout" ng-pattern="/^\d+$/" min="0">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row form-group" ng-if="model.spec.monitor.type === 'HTTP'">
|
||||
|
||||
<div class="col-sm-6 col-md-3">
|
||||
<div class="form-field monitor-method">
|
||||
<label translate class="on-top" for="monitor-method">HTTP method</label>
|
||||
<select class="form-control input-sm" name="monitor-method"
|
||||
id="monitor-method"
|
||||
ng-options="method for method in model.monitorMethods"
|
||||
ng-model="model.spec.monitor.method">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 col-md-3">
|
||||
<div class="form-field monitor-status"
|
||||
ng-class="{ 'has-error': createLoadBalancerMonitorForm['monitor-status'].$invalid && createLoadBalancerMonitorForm['monitor-status'].$dirty }">
|
||||
<label translate class="on-top" for="monitor-status">Expected HTTP status code</label>
|
||||
<span class="fa fa-exclamation-triangle invalid"
|
||||
ng-show="createLoadBalancerMonitorForm['monitor-status'].$invalid && createLoadBalancerMonitorForm.$dirty"
|
||||
popover="{$ ::ctrl.statusError $}"
|
||||
popover-placement="top" popover-append-to-body="true"
|
||||
popover-trigger="hover"></span>
|
||||
<span class="fa fa-question-circle pull-right"
|
||||
popover="{$ ::ctrl.statusHelp $}"
|
||||
popover-placement="top" popover-append-to-body="true"
|
||||
popover-trigger="hover"></span>
|
||||
<input name="monitor-status" id="monitor-status"
|
||||
type="text" class="form-control input-sm"
|
||||
ng-model="model.spec.monitor.status" ng-pattern="::ctrl.statusPattern">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 col-md-3">
|
||||
<div class="form-field monitor-path"
|
||||
ng-class="{ 'has-error': createLoadBalancerMonitorForm['monitor-path'].$invalid && createLoadBalancerMonitorForm['monitor-path'].$dirty }">
|
||||
<label translate class="on-top" for="monitor-path">URL path</label>
|
||||
<span class="fa fa-exclamation-triangle invalid"
|
||||
ng-show="createLoadBalancerMonitorForm['monitor-path'].$invalid && createLoadBalancerMonitorForm.$dirty"
|
||||
popover="{$ ::ctrl.pathError $}"
|
||||
popover-placement="top" popover-append-to-body="true"
|
||||
popover-trigger="hover"></span>
|
||||
<input name="monitor-path" id="monitor-path"
|
||||
type="text" class="form-control input-sm"
|
||||
ng-model="model.spec.monitor.path" ng-pattern="::ctrl.urlPathPattern">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- end content -->
|
||||
</div>
|
|
@ -59,6 +59,13 @@
|
|||
templateUrl: basePath + 'loadbalancers/actions/create/members/members.html',
|
||||
helpUrl: basePath + 'loadbalancers/actions/create/members/members.help.html',
|
||||
formName: 'createLoadBalancerMembersForm'
|
||||
},
|
||||
{
|
||||
id: 'monitor',
|
||||
title: gettext('Monitor'),
|
||||
templateUrl: basePath + 'loadbalancers/actions/create/monitor/monitor.html',
|
||||
helpUrl: basePath + 'loadbalancers/actions/create/monitor/monitor.help.html',
|
||||
formName: 'createLoadBalancerMonitorForm'
|
||||
}
|
||||
],
|
||||
|
||||
|
|
|
@ -41,13 +41,14 @@
|
|||
|
||||
it('should have steps defined', function () {
|
||||
expect(createLoadBalancerWorkflow.steps).toBeDefined();
|
||||
expect(createLoadBalancerWorkflow.steps.length).toBe(4);
|
||||
expect(createLoadBalancerWorkflow.steps.length).toBe(5);
|
||||
|
||||
var forms = [
|
||||
'createLoadBalancerDetailsForm',
|
||||
'createLoadBalancerListenerForm',
|
||||
'createLoadBalancerPoolForm',
|
||||
'createLoadBalancerMembersForm'
|
||||
'createLoadBalancerMembersForm',
|
||||
'createLoadBalancerMonitorForm'
|
||||
];
|
||||
|
||||
forms.forEach(function(expectedForm, idx) {
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
Table-batch-actions:
|
||||
This is where batch actions like searching, creating, and deleting.
|
||||
-->
|
||||
<th colspan="0" class="search-header">
|
||||
<th colspan="100" class="search-header">
|
||||
<hz-search-bar group-classes="input-group-sm" icon-classes="fa-search">
|
||||
<actions allowed="table.batchActions.actions" type="batch"></actions>
|
||||
</hz-search-bar>
|
||||
|
@ -86,7 +86,7 @@
|
|||
Can be toggled using the chevron button.
|
||||
Ensure colspan is greater or equal to number of column-headers.
|
||||
-->
|
||||
<td class="detail" colspan="0">
|
||||
<td class="detail" colspan="100">
|
||||
<!--
|
||||
The responsive columns that disappear typically should reappear here
|
||||
with the same responsive priority that they disappear.
|
||||
|
|
Loading…
Reference in New Issue