octavia-dashboard/octavia_dashboard/static/dashboard/project/lbaasv2/workflow/model.service.js
Michael Johnson 1f0c90f3a8 Updating for octavia-dashboard
This patch updates the imported code to reflect octavia-dashboard.

More work is required to make this work with the Octavia v2 API.
This will be done in followup patches.

Change-Id: I7363105a53642d0003036cada21c5b0cc25efc1a
2017-06-22 16:20:27 +08:00

744 lines
23 KiB
JavaScript

/*
* Copyright 2015 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';
var push = Array.prototype.push;
angular
.module('horizon.dashboard.project.lbaasv2')
.factory('horizon.dashboard.project.lbaasv2.workflow.model', workflowModel);
workflowModel.$inject = [
'$q',
'horizon.app.core.openstack-service-api.neutron',
'horizon.app.core.openstack-service-api.nova',
'horizon.app.core.openstack-service-api.lbaasv2',
'horizon.app.core.openstack-service-api.barbican',
'horizon.app.core.openstack-service-api.serviceCatalog',
'horizon.framework.util.i18n.gettext'
];
/**
* @ngdoc service
* @name horizon.dashboard.project.lbaasv2.workflow.model
*
* @description
* This is the M part of the MVC design pattern for the LBaaS wizard workflow. It is responsible
* for providing data to the view of each step in the workflow and collecting the user's input
* from the view. It is also the center point of communication between the UI and services API.
*
* @param $q The angular service for promises.
* @param neutronAPI The neutron service API.
* @param novaAPI The nova service API.
* @param lbaasv2API The LBaaS V2 service API.
* @param barbicanAPI The barbican service API.
* @param serviceCatalog The keystone service catalog API.
* @param gettext The horizon gettext function for translation.
* @returns The model service for the create load balancer workflow.
*/
function workflowModel(
$q,
neutronAPI,
novaAPI,
lbaasv2API,
barbicanAPI,
serviceCatalog,
gettext
) {
var ports, keymanagerPromise;
/**
* @ngdoc model api object
*/
var model = {
initializing: false,
initialized: false,
context: null,
/**
* @name spec
*
* @description
* A dictionary like object containing specification collected from user
* input.
*/
spec: null,
visibleResources: [],
subnets: [],
members: [],
listenerProtocols: ['HTTP', 'TCP', 'TERMINATED_HTTPS'],
methods: ['LEAST_CONNECTIONS', 'ROUND_ROBIN', 'SOURCE_IP'],
monitorTypes: ['HTTP', 'PING', 'TCP'],
monitorMethods: ['GET', 'HEAD'],
certificates: [],
listenerPorts: [],
/**
* api methods for UI controllers
*/
initialize: initialize,
submit: submit
};
return model;
///////////////
/**
* @ngdoc method
* @name workflowModel.initialize
* @returns {promise}
*
* @description
* Send request to get all data to initialize the model.
*
* @param resource Resource type being created / edited, one of 'loadbalancer', 'listener',
* 'pool', 'monitor', or 'members'.
* @param id ID of the resource being edited.
*/
function initialize(resource, id, loadBalancerId, parentResourceId) {
var promise;
model.certificatesError = false;
model.context = {
resource: resource,
id: id,
submit: null
};
model.visibleResources = [];
model.certificates = [];
model.listenerPorts = [];
model.spec = {
loadbalancer_id: loadBalancerId,
parentResourceId: parentResourceId,
loadbalancer: {
name: null,
description: null,
ip: null,
subnet: null
},
listener: {
id: null,
name: gettext('Listener 1'),
description: null,
protocol: null,
port: null
},
pool: {
id: null,
name: gettext('Pool 1'),
description: null,
protocol: null,
method: null
},
monitor: {
id: null,
type: null,
interval: 5,
retry: 3,
timeout: 5,
method: 'GET',
status: '200',
path: '/'
},
members: [],
certificates: []
};
if (!model.initializing) {
model.initializing = true;
promise = initializeResources();
}
return promise;
}
function initializeResources() {
var type = (model.context.id ? 'edit' : 'create') + model.context.resource;
keymanagerPromise = serviceCatalog.ifTypeEnabled('key-manager');
if (type === 'createloadbalancer' || model.context.resource === 'listener') {
keymanagerPromise.then(angular.noop, certificatesNotSupported);
}
var promise = {
createloadbalancer: initCreateLoadBalancer,
createlistener: initCreateListener,
createpool: initCreatePool,
createmonitor: initCreateMonitor,
createmembers: initUpdateMemberList,
editloadbalancer: initEditLoadBalancer,
editlistener: initEditListener,
editpool: initEditPool,
editmonitor: initEditMonitor
}[type](keymanagerPromise);
return promise.then(onInitSuccess, onInitFail);
}
function onInitSuccess() {
model.initializing = false;
model.initialized = true;
}
function onInitFail() {
model.initializing = false;
model.initialized = false;
}
function initCreateLoadBalancer(keymanagerPromise) {
model.context.submit = createLoadBalancer;
return $q.all([
lbaasv2API.getLoadBalancers().then(onGetLoadBalancers),
neutronAPI.getSubnets().then(onGetSubnets),
neutronAPI.getPorts().then(onGetPorts),
novaAPI.getServers().then(onGetServers),
keymanagerPromise.then(prepareCertificates, angular.noop)
]).then(initMemberAddresses);
}
function initCreateListener(keymanagerPromise) {
model.context.submit = createListener;
return $q.all([
lbaasv2API.getListeners(model.spec.loadbalancer_id).then(onGetListeners),
neutronAPI.getSubnets().then(onGetSubnets),
neutronAPI.getPorts().then(onGetPorts),
novaAPI.getServers().then(onGetServers),
keymanagerPromise.then(prepareCertificates, angular.noop)
]).then(initMemberAddresses);
}
function initCreatePool() {
model.context.submit = createPool;
// We get the listener details here because we need to know the listener protocol
// in order to default the new pool's protocol to match.
return $q.all([
lbaasv2API.getListener(model.spec.parentResourceId).then(onGetListener),
neutronAPI.getSubnets().then(onGetSubnets),
neutronAPI.getPorts().then(onGetPorts),
novaAPI.getServers().then(onGetServers)
]).then(initMemberAddresses);
}
function initCreateMonitor() {
model.context.submit = createHealthMonitor;
return $q.when();
}
function initUpdateMemberList() {
model.context.submit = updatePoolMemberList;
return $q.all([
lbaasv2API.getPool(model.spec.parentResourceId).then(onGetPool),
neutronAPI.getSubnets().then(onGetSubnets).then(getMembers).then(onGetMembers),
neutronAPI.getPorts().then(onGetPorts),
novaAPI.getServers().then(onGetServers)
]).then(initMemberAddresses);
}
function initEditLoadBalancer() {
model.context.submit = editLoadBalancer;
return $q.all([
lbaasv2API.getLoadBalancer(model.context.id).then(onGetLoadBalancer),
neutronAPI.getSubnets().then(onGetSubnets)
]).then(initSubnet);
}
function initEditListener() {
model.context.submit = editListener;
return $q.all([
neutronAPI.getSubnets().then(onGetSubnets).then(getListener).then(onGetListener),
neutronAPI.getPorts().then(onGetPorts),
novaAPI.getServers().then(onGetServers)
]).then(initMemberAddresses);
}
function initEditPool() {
model.context.submit = editPool;
return $q.all([
neutronAPI.getSubnets().then(onGetSubnets).then(getPool).then(onGetPool),
neutronAPI.getPorts().then(onGetPorts),
novaAPI.getServers().then(onGetServers)
]).then(initMemberAddresses);
}
function initEditMonitor() {
model.context.submit = editHealthMonitor;
return lbaasv2API.getHealthMonitor(model.context.id).then(onGetHealthMonitor);
}
/**
* @ngdoc method
* @name workflowModel.submit
* @returns {promise}
*
* @description
* Send request for completing the wizard.
*
* @returns Response from the LBaaS V2 API.
*/
function submit() {
var finalSpec = angular.copy(model.spec);
cleanFinalSpecLoadBalancer(finalSpec);
cleanFinalSpecListener(finalSpec);
cleanFinalSpecPool(finalSpec);
cleanFinalSpecMembers(finalSpec);
cleanFinalSpecMonitor(finalSpec);
removeNulls(finalSpec);
return model.context.submit(finalSpec);
}
function createLoadBalancer(spec) {
return lbaasv2API.createLoadBalancer(spec);
}
function createListener(spec) {
return lbaasv2API.createListener(spec);
}
function createPool(spec) {
return lbaasv2API.createPool(spec);
}
function createHealthMonitor(spec) {
return lbaasv2API.createHealthMonitor(spec);
}
function editLoadBalancer(spec) {
return lbaasv2API.editLoadBalancer(model.context.id, spec);
}
function editListener(spec) {
return lbaasv2API.editListener(model.context.id, spec);
}
function editPool(spec) {
return lbaasv2API.editPool(model.context.id, spec);
}
function editHealthMonitor(spec) {
return lbaasv2API.editHealthMonitor(model.context.id, spec);
}
function updatePoolMemberList(spec) {
return lbaasv2API.updateMemberList(model.spec.parentResourceId, spec);
}
function cleanFinalSpecLoadBalancer(finalSpec) {
var context = model.context;
// Load balancer requires subnet
if (!finalSpec.loadbalancer.subnet) {
delete finalSpec.loadbalancer;
} else {
finalSpec.loadbalancer.subnet = finalSpec.loadbalancer.subnet.id;
}
// Cannot edit the IP or subnet
if (context.resource === 'loadbalancer' && context.id) {
delete finalSpec.subnet;
delete finalSpec.ip;
}
}
function cleanFinalSpecListener(finalSpec) {
if (!finalSpec.listener.protocol || !finalSpec.listener.port) {
// Listener requires protocol and port
delete finalSpec.listener;
delete finalSpec.certificates;
} else if (finalSpec.listener.protocol !== 'TERMINATED_HTTPS') {
// Remove certificate containers if not using TERMINATED_HTTPS
delete finalSpec.certificates;
} else {
var containers = [];
angular.forEach(finalSpec.certificates, function(cert) {
containers.push(cert.id);
});
finalSpec.certificates = containers;
}
}
function cleanFinalSpecPool(finalSpec) {
// Pool requires method
if (!finalSpec.pool.method) {
delete finalSpec.pool;
} else {
// The pool protocol must be HTTP if the listener protocol is TERMINATED_HTTPS and
// otherwise has to match it.
var protocol = finalSpec.listener ? finalSpec.listener.protocol : finalSpec.pool.protocol;
finalSpec.pool.protocol = protocol === 'TERMINATED_HTTPS' ? 'HTTP' : protocol;
}
}
function cleanFinalSpecMembers(finalSpec) {
if (finalSpec.members.length === 0) {
delete finalSpec.members;
return;
}
var members = [];
angular.forEach(finalSpec.members, function cleanMember(member) {
if (member.address && member.port) {
var propsToRemove = ['name', 'description', 'addresses', 'allocatedMember'];
propsToRemove.forEach(function deleteProperty(prop) {
if (angular.isDefined(member[prop])) {
delete member[prop];
}
});
if (angular.isObject(member.address)) {
member.subnet = member.address.subnet;
member.address = member.address.ip;
} else if (member.subnet) {
member.subnet = member.subnet.id;
} else {
delete member.subnet;
}
members.push(member);
}
});
if (members.length > 0) {
finalSpec.members = members;
} else {
delete finalSpec.members;
}
}
function cleanFinalSpecMonitor(finalSpec) {
// Monitor requires an interval, retry count, and timeout
if (!angular.isNumber(finalSpec.monitor.interval) ||
!angular.isNumber(finalSpec.monitor.retry) ||
!angular.isNumber(finalSpec.monitor.timeout)) {
delete finalSpec.monitor;
return;
}
// Only include necessary monitor properties
if (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) {
delete finalSpec[groupName][key];
}
});
});
}
function onGetLoadBalancers(response) {
var existingNames = {};
angular.forEach(response.data.items, function nameExists(lb) {
existingNames[lb.name] = 1;
});
var name;
var index = response.data.items.length;
do {
index += 1;
name = interpolate(gettext('Load Balancer %(index)s'), { index: index }, true);
} while (name in existingNames);
model.spec.loadbalancer.name = name;
}
function onGetListeners(response) {
var existingNames = {};
angular.forEach(response.data.items, function nameExists(listener) {
existingNames[listener.name] = 1;
model.listenerPorts.push(listener.protocol_port);
});
var name;
var index = response.data.items.length;
do {
index += 1;
name = interpolate(gettext('Listener %(index)s'), { index: index }, true);
} while (name in existingNames);
model.spec.listener.name = name;
}
function onGetSubnets(response) {
model.subnets.length = 0;
push.apply(model.subnets, response.data.items);
}
function onGetServers(response) {
model.members.length = 0;
var members = [];
angular.forEach(response.data.items, function addMember(server) {
// If the server is in a state where it does not have an IP address then we can't use it
if (server.addresses && !angular.equals({}, server.addresses)) {
members.push({
id: server.id,
name: server.name,
weight: 1
});
}
});
push.apply(model.members, members);
}
function onGetPorts(response) {
ports = response.data.items;
}
function onGetMembers(response) {
setMembersSpec(response.data.items);
}
function initMemberAddresses() {
angular.forEach(model.members, function initAddresses(member) {
var memberPorts = ports.filter(function filterPortByDevice(port) {
return port.device_id === member.id;
});
member.addresses = [];
angular.forEach(memberPorts, function addAddressesForPort(port) {
angular.forEach(port.fixed_ips, function addAddress(ip) {
member.addresses.push({
ip: ip.ip_address,
subnet: ip.subnet_id
});
});
});
member.address = member.addresses[0];
if (model.spec.pool.protocol) {
member.port = {HTTP: 80}[model.spec.pool.protocol];
}
});
}
function getListener() {
return lbaasv2API.getListener(model.context.id, true);
}
function getPool() {
return lbaasv2API.getPool(model.context.id, true);
}
function getMembers() {
return lbaasv2API.getMembers(model.spec.parentResourceId);
}
function onGetLoadBalancer(response) {
var loadbalancer = response.data;
setLoadBalancerSpec(loadbalancer);
}
function onGetListener(response) {
var result = response.data;
setListenerSpec(result.listener || result);
if (result.listener) {
model.visibleResources.push('listener');
model.spec.loadbalancer_id = result.listener.loadbalancers[0].id;
if (result.listener.protocol === 'TERMINATED_HTTPS') {
keymanagerPromise.then(prepareCertificates).then(function addAvailableCertificates() {
result.listener.sni_container_refs.forEach(function addAvailableCertificate(ref) {
model.certificates.filter(function matchCertificate(cert) {
return cert.id === ref;
}).forEach(function addCertificate(cert) {
model.spec.certificates.push(cert);
});
});
});
model.visibleResources.push('certificates');
}
}
if (result.pool) {
setPoolSpec(result.pool);
model.visibleResources.push('pool');
model.visibleResources.push('members');
if (result.members) {
setMembersSpec(result.members);
}
if (result.monitor) {
setMonitorSpec(result.monitor);
model.visibleResources.push('monitor');
}
}
}
function onGetPool(response) {
var result = response.data;
setPoolSpec(result.pool || result);
if (result.pool) {
model.visibleResources.push('pool');
model.visibleResources.push('members');
if (result.members) {
setMembersSpec(result.members);
}
if (result.monitor) {
setMonitorSpec(result.monitor);
model.visibleResources.push('monitor');
}
}
}
function setLoadBalancerSpec(loadbalancer) {
var spec = model.spec.loadbalancer;
spec.name = loadbalancer.name;
spec.description = loadbalancer.description;
spec.ip = loadbalancer.vip_address;
spec.subnet = loadbalancer.vip_subnet_id;
}
function setListenerSpec(listener) {
var spec = model.spec.listener;
spec.id = listener.id;
spec.name = listener.name;
spec.description = listener.description;
spec.protocol = listener.protocol;
spec.port = listener.protocol_port;
}
function setPoolSpec(pool) {
var spec = model.spec.pool;
spec.id = pool.id;
spec.name = pool.name;
spec.description = pool.description;
spec.protocol = pool.protocol;
spec.method = pool.lb_algorithm;
}
function setMembersSpec(membersList) {
model.spec.members.length = 0;
var members = [];
angular.forEach(membersList, function addMember(member) {
members.push({
id: member.id,
address: member.address,
subnet: mapSubnetObj(member.subnet_id),
port: member.protocol_port,
weight: member.weight,
allocatedMember: true
});
});
push.apply(model.spec.members, members);
}
function setMonitorSpec(monitor) {
var spec = model.spec.monitor;
spec.id = monitor.id;
spec.type = monitor.type;
spec.interval = monitor.delay;
spec.timeout = monitor.timeout;
spec.retry = monitor.max_retries;
spec.method = monitor.http_method;
spec.status = monitor.expected_codes;
spec.path = monitor.url_path;
}
function onGetHealthMonitor(response) {
setMonitorSpec(response.data);
}
function prepareCertificates() {
return $q.all([
barbicanAPI.getCertificates(true),
barbicanAPI.getSecrets(true)
]).then(onGetCertificates, certificatesError);
}
function onGetCertificates(results) {
// To use the TERMINATED_HTTPS protocol with a listener, the LBaaS v2 API wants a default
// container ref and a list of containers that hold TLS secrets. In barbican the container
// object has a list of references to the secrets it holds. This assumes that each
// certificate container has exactly one certificate and private key. We fetch both the
// certificate containers and the secrets so that we can display the secret names and
// expirations dates.
model.certificates.length = 0;
var certificates = [];
// Only accept containers that have both a certificate and private_key ref
var containers = results[0].data.items.filter(function validateContainer(container) {
container.refs = {};
container.secret_refs.forEach(function(ref) {
container.refs[ref.name] = ref.secret_ref;
});
return 'certificate' in container.refs && 'private_key' in container.refs;
});
var secrets = {};
results[1].data.items.forEach(function addSecret(secret) {
secrets[secret.secret_ref] = secret;
});
containers.forEach(function addCertificateContainer(container) {
var secret = secrets[container.refs.certificate];
certificates.push({
id: container.container_ref,
name: secret.name || secret.secret_ref.split('/').reverse()[0],
expiration: secret.expiration
});
});
push.apply(model.certificates, certificates);
}
function initSubnet() {
var subnet = model.subnets.filter(function filterSubnetsByLoadBalancer(s) {
return s.id === model.spec.loadbalancer.subnet;
})[0];
model.spec.loadbalancer.subnet = subnet;
}
function mapSubnetObj(subnetId) {
var subnet = model.subnets.filter(function mapSubnet(subnet) {
return subnet.id === subnetId;
});
return subnet[0];
}
function certificatesNotSupported() {
// This function is called if the key-manager service is not available. In that case we
// cannot support the TERMINATED_HTTPS listener protocol so we hide the option if creating
// a new load balancer or listener. However for editing we still need it.
if (!model.context.id) {
model.listenerProtocols.splice(2, 1);
}
}
function certificatesError() {
// This function is called if there is an error fetching the certificate containers or
// secrets. In that case we cannot support the TERMINATED_HTTPS listener protocol but we
// want to make the user aware of the error.
model.certificatesError = true;
}
}
})();