File: app/models/endpoints.js
'use strict';
/**
* Provide the database endpoints handling.
*
* @module models
* @submodule models.endpoints
*/
YUI.add('juju-endpoints', function(Y) {
var models = Y.namespace('juju.models');
var utils = Y.namespace('juju.views.utils');
/**
* Find available relation targets for a service.
*
* @method getEndpoints
* @param {Object} svc A service object.
* @param {Object} controller The endpoints controller.
*
* @return {Object} A mapping with keys of valid relation service targets
* and values consisting of a list of valid endpoints for each.
*/
models.getEndpoints = function(svc, controller) {
var targets = {},
requires = [],
provides = [],
sid = svc.get('id'),
db = controller.get('db'),
ep_map = controller.endpointsMap;
/**
* Convert a service name and its relation endpoint info into a
* valid relation target endpoint, ie. including service name.
*
* @method getEndpoints.convert
*/
function convert(svcName, relInfo) {
return {
service: svcName,
name: relInfo.name,
type: relInfo['interface']
};
}
/**
* Store endpoints for a relation to target the given service.
*
* @method getEndpoints.add
* @param {Object} tep Target endpoint.
* @param {Object} oep Origin endpoint.
*/
function add(svcName, oep, tep) {
if (!Y.Object.owns(targets, svcName)) {
targets[svcName] = [];
}
targets[svcName].push([oep, tep]);
}
// First we process all the endpoints of the origin service.
//
// For required interfaces, we consider them valid for new relations
// only if they are not already satisfied by an existing relation.
Y.each(
ep_map[sid].requires,
function(rdata) {
var ep = convert(sid, rdata);
// Subordinate relations are slightly different:
// a subordinate typically acts as a client to many services,
// against the implicitly provided juju-info interface.
if (svc.get('subordinate') && utils.isSubordinateRelation(rdata)) {
return requires.push(ep);
}
if (db.relations.has_relation_for_endpoint(ep)) {
return;
}
requires.push(ep);
});
// Process origin provides endpoints, a bit simpler, as they are
// always one to many.
Y.each(
ep_map[sid].provides,
function(pdata) {
provides.push(convert(sid, pdata));
});
// Every non subordinate service implicitly provides this.
if (!svc.get('subordinate')) {
provides.push(convert(
sid, {'interface': 'juju-info', 'name': 'juju-info'}));
}
// Now check every other service to see if it can be a valid target.
db.services.each(function(tgt) {
var tid = tgt.get('id'),
tprovides = ep_map[tid].provides.concat();
// Ignore ourselves, peer relations are automatically
// established when a service is deployed. The gui only needs to
// concern itself with client/server relations.
if (tid === sid) {
return;
}
// Process each of the service's required endpoints. It is only
// considered a valid target if it is not satisfied by an existing
// relation.
Y.each(
ep_map[tid].requires,
function(rdata) {
var ep = convert(tid, rdata);
// Subordinates are exceptions again as they are a client
// to many services. We check if a subordinate relation
// exists between this subordinate endpoint and the origin
// service.
if (tgt.get('subordinate') && utils.isSubordinateRelation(rdata)) {
if (db.relations.has_relation_for_endpoint(ep, sid)) {
return;
}
} else if (db.relations.has_relation_for_endpoint(ep)) {
return;
}
// If the origin provides it then it is a valid target.
Y.Array.filter(provides, function(oep) {
if (oep.type === ep.type) {
add(tid, oep, ep);
}
});
});
// Check against the implicit interface juju-info, but not for
// subordinates.
if (!tgt.get('subordinate')) {
tprovides.push({'interface': 'juju-info', 'name': 'juju-info'});
}
Y.each(
tprovides,
function(pdata) {
var ep = convert(tid, pdata);
Y.Array.each(requires,
function(oep) {
if (oep.type !== ep.type ||
db.relations.has_relation_for_endpoint(ep, sid)) {
return;
}
add(tid, oep, ep);
});
});
});
return targets;
};
});