Index: app/models/models.js |
=== modified file 'app/models/models.js' |
--- app/models/models.js 2013-09-18 17:31:18 +0000 |
+++ app/models/models.js 2013-09-19 20:22:02 +0000 |
@@ -707,7 +707,16 @@ |
value: false |
}, |
scope: {}, |
- display_name: {} |
+ display_name: { |
+ 'getter': function(value) { |
+ if (value) { return value;} |
+ var names = []; |
+ this.get('endpoints').forEach(function(endpoint) { |
+ names.push(endpoint[1].name); |
+ }); |
+ return names.join(':'); |
+ } |
+ } |
} |
}); |
models.Relation = Relation; |
@@ -770,113 +779,6 @@ |
}, |
- /** |
- Takes two string endpoints and splits it into usable parts. |
- |
- @method parseEndpointStrings |
- @param {Database} db to resolve charms/services on. |
- @param {Array} endpoints an array of endpoint strings |
- to split in the format wordpress:db. |
- @return {Object} An Array of parsed endpoints, each containing name, type |
- and the related charm. Name is the user defined service name and type is |
- the charms authors name for the relation type. |
- */ |
- parseEndpointStrings: function(db, endpoints) { |
- return Y.Array.map(endpoints, function(endpoint) { |
- var epData = endpoint.split(':'); |
- var result = {}; |
- if (epData.length > 1) { |
- result.name = epData[0]; |
- result.type = epData[1]; |
- } else { |
- result.name = epData[0]; |
- } |
- result.service = db.services.getById(result.name); |
- if (result.service) { |
- result.charm = db.charms.getById( |
- result.service.get('charm')); |
- if (!result.charm) { |
- console.warn('Failed to load charm', |
- result.charm, db.charms.size(), db.charms.get('id')); |
- } |
- } else { |
- console.warn('failed to resolve service', result.name); |
- } |
- return result; |
- }, this); |
- }, |
- |
- /** |
- Loops through the charm endpoint data to determine whether we have a |
- relationship match. The result is either an object with an error |
- attribute, or an object giving the interface, scope, providing endpoint, |
- and requiring endpoint. |
- |
- @method findEndpointMatch |
- @param {Array} endpoints Pair of two endpoint data objects. Each |
- endpoint data object has name, charm, service, and scope. |
- @return {Object} A hash with the keys 'interface', 'scope', 'provides', |
- and 'requires'. |
- */ |
- findEndpointMatch: function(endpoints) { |
- var matches = [], result; |
- Y.each([0, 1], function(providedIndex) { |
- // Identify the candidates. |
- var providingEndpoint = endpoints[providedIndex]; |
- // The merges here result in a shallow copy. |
- var provides = Y.merge(providingEndpoint.charm.get('provides') || {}), |
- requiringEndpoint = endpoints[!providedIndex + 0], |
- requires = Y.merge(requiringEndpoint.charm.get('requires') || {}); |
- if (!provides['juju-info']) { |
- provides['juju-info'] = {'interface': 'juju-info', |
- scope: 'container'}; |
- } |
- // Restrict candidate types as tightly as possible. |
- var candidateProvideTypes, candidateRequireTypes; |
- if (providingEndpoint.type) { |
- candidateProvideTypes = [providingEndpoint.type]; |
- } else { |
- candidateProvideTypes = Y.Object.keys(provides); |
- } |
- if (requiringEndpoint.type) { |
- candidateRequireTypes = [requiringEndpoint.type]; |
- } else { |
- candidateRequireTypes = Y.Object.keys(requires); |
- } |
- // Find matches for candidates and evaluate them. |
- Y.each(candidateProvideTypes, function(provideType) { |
- Y.each(candidateRequireTypes, function(requireType) { |
- var provideMatch = provides[provideType], |
- requireMatch = requires[requireType]; |
- if (provideMatch && |
- requireMatch && |
- provideMatch['interface'] === requireMatch['interface']) { |
- matches.push({ |
- 'interface': provideMatch['interface'], |
- scope: provideMatch.scope || requireMatch.scope, |
- provides: providingEndpoint, |
- requires: requiringEndpoint, |
- provideType: provideType, |
- requireType: requireType |
- }); |
- } |
- }); |
- }); |
- }); |
- if (matches.length === 0) { |
- result = {error: 'Specified relation is unavailable.'}; |
- } else if (matches.length > 1) { |
- result = {error: 'Ambiguous relationship is not allowed.'}; |
- } else { |
- result = matches[0]; |
- // Specify the type for implicit relations. |
- result.provides = Y.merge(result.provides); |
- result.requires = Y.merge(result.requires); |
- result.provides.type = result.provideType; |
- result.requires.type = result.requireType; |
- } |
- return result; |
- }, |
/* Return true if a relation exists for the given endpoint. |
@@ -1231,242 +1133,6 @@ |
}, |
/** |
- Add a relation between two services. |
- |
- @method addRelation |
- @param {String} endpointA A string representation of the service name |
- and endpoint connection type ie) wordpress:db. |
- @param {String} endpointB A string representation of the service name |
- and endpoint connection type ie) wordpress:db. |
- @param {Boolean} ghost When true this is a pending relationship. |
- @return {Object} new relation. |
- */ |
- addRelation: function(endpointA, endpointB, ghost) { |
- if ((typeof endpointA !== 'string') || |
- (typeof endpointB !== 'string')) { |
- return {error: 'Two string endpoint names' + |
- ' required to establish a relation'}; |
- } |
- |
- // Parses the endpoint strings to extract all required data. |
- var endpointData = this.relations |
- .parseEndpointStrings(this, |
- [endpointA, endpointB]); |
- |
- // This error should never be hit but it's here JIC |
- if (!endpointData[0].charm || !endpointData[1].charm) { |
- throw new Error('Required charm for relation endpoint not loaded'); |
- } |
- // If there are matching interfaces this will contain an object of the |
- // charm interface type and scope (if supplied). |
- var match = this.relations.findEndpointMatch(endpointData); |
- |
- // If there is an error fetching a valid interface and scope |
- if (match.error) { return match; } |
- |
- // Assign a unique relation id which is incremented after every |
- // successful relation. |
- var relationId = 'relation-' + this._relationCount; |
- // The ordering of requires and provides is stable in Juju Core, and not |
- // specified in PyJuju. |
- var endpoints = Y.Array.map( |
- [match.requires, match.provides], |
- function(endpoint) { |
- var result = []; |
- result.push(endpoint.name); |
- result.push({name: endpoint.type}); |
- return [endpoint.name, {name: endpoint.type}]; |
- }); |
- var relation = this.relations.add({ |
- relation_id: relationId, |
- type: match['interface'], |
- endpoints: endpoints, |
- pending: Boolean(ghost), |
- scope: match.scope || 'global', |
- display_name: endpointData[0].type |
- }); |
- |
- if (relation) { |
- this._relationCount += 1; |
- } |
- return relation; |
- |
- }, |
- |
- |
- /** |
- Import deployer styled dumps and create the relevant objects in the |
- database. This modifies the database its called on directly. \ |
- |
- Options contains flags controlling import behavior. If 'rewrite-ids' is |
- true then import id conflicts will result in the imported object being |
- assigned a new id. |
- |
- If the deployer data contains multiple bundle definitions then |
- 'targetBundle' must be included and that bundle will be deployed. |
- |
- If 'useGhost' is true then services and relations will be imported as |
- ghosts allowing further customization prior to deploy. This defaults to |
- true. |
- |
- @method importDeployer |
- @param {Object} data to import. |
- @param {Object} charmStore (with its promiseCharm method). |
- @param {Object} options (optional). |
- @return {Promise} that the import is complete. |
- */ |
- importDeployer: function(data, charmStore, options) { |
- if (!data) {return;} |
- options = options || {}; |
- var self = this; |
- var rewriteIds = options.rewriteIds || false; |
- var targetBundle = options.targetBundle; |
- var useGhost = options.useGhost; |
- if (useGhost === undefined) { |
- useGhost = true; |
- } |
- var defaultSeries = self.environment.get('defaultSeries'); |
- |
- if (!targetBundle && Object.keys(data).length > 1) { |
- throw new Error('Import target ambigious, aborting.'); |
- } |
- |
- // Builds out a object with inherited properties. |
- var source = targetBundle && data[targetBundle] || |
- data[Object.keys(data)[0]]; |
- var ancestors = []; |
- var seen = []; |
- |
- /** |
- Helper to build out an inheritence chain |
- |
- @method setupinheritance |
- @param {Object} base object currently being inspected. |
- @param {Array} baseList chain of ancestors to later inherit. |
- @param {Object} bundleData import data used to resolve ancestors. |
- @param {Array} seen list used to track objects already in inheritence |
- chain. @return {Array} of all inherited objects ordered from most base |
- to most specialized. |
- */ |
- function setupInheritance(base, baseList, bundleData, seen) { |
- // local alias for internal function. |
- var sourceData = bundleData; |
- var seenList = seen; |
- |
- baseList.unshift(base); |
- // Normalize to array when present. |
- if (!base.inherits) { return; } |
- if (base.inherits && !Y.Lang.isArray(base.inherits)) { |
- base.inherits = [base.inherits]; |
- } |
- |
- base.inherits.forEach(function(ancestor) { |
- var baseDeploy = sourceData[ancestor]; |
- if (baseDeploy === undefined) { |
- throw new Error('Unable to resolve bundle inheritence.'); |
- } |
- if (seenList.indexOf(ancestor) === -1) { |
- seenList.push(ancestor); |
- setupInheritance(baseDeploy, baseList, bundleData, seenList); |
- } |
- }); |
- |
- } |
- setupInheritance(source, ancestors, data, seen); |
- // Source now merges it all. |
- source = {}; |
- ancestors.forEach(function(ancestor) { |
- // Mix Merge and overwrite in order of inheritance |
- Y.mix(source, ancestor, true, undefined, 0, true); |
- }); |
- |
- // Create an id mapping. This will track the ids of objects |
- // read from data as they are mapped into db. When options |
- // rewriteIds is true this is required for services, but some |
- // types of object ids ('relations' for example) can always |
- // be rewritten but depend on the use of the proper ids. |
- // By building this mapping now we can detect collisions |
- // prior to mutating the database. |
- var serviceIdMap = {}; |
- var charms = []; |
- |
- /** |
- Helper to generate the next valid service id. |
- @method nextServiceId |
- @return {String} next service id to use. |
- */ |
- function nextServiceId(modellist, id) { |
- var existing = modellist.getById(id); |
- var count = 0; |
- var target; |
- while (existing) { |
- count += 1; |
- target = id + '-' + count; |
- existing = modellist.getById(target); |
- } |
- return target; |
- } |
- |
- |
- var charmLookupByName = {}; |
- Object.keys(source.services).forEach(function(serviceName) { |
- var current = source.services[serviceName]; |
- var existing = self.services.getById(serviceName); |
- var targetId = serviceName; |
- if (existing) { |
- if (!rewriteIds) { |
- throw new Error(serviceName + |
- ' is already present in the database.'); |
- } |
- targetId = nextServiceId(self.services, serviceName); |
- } |
- serviceIdMap[serviceName] = targetId; |
- |
- // Also track any new charms we'll have to add. |
- if (current.charm && charmLookupByName[current.charm] === undefined) { |
- charms.push(charmStore.promiseCharm(current.charm, self.charms, |
- defaultSeries)); |
- charmLookupByName[current.charm] = true; |
- } |
- }); |
- |
- // If we made it this far its time for mutation, start by importing |
- // charms and then services. |
- return Y.batch.apply(this, charms) |
- .then(function() { |
- Object.keys(serviceIdMap).forEach(function(serviceName) { |
- var serviceData = source.services[serviceName]; |
- var serviceId = serviceIdMap[serviceName]; |
- var charm = self.charms.find(serviceData.charm, defaultSeries); |
- var charmId = charm && charm.get('id') || undefined; |
- var current = Y.mix(serviceData, { |
- id: serviceId, pending: useGhost, charm: charmId |
- }, true); |
- self.services.add(current); |
- // XXX: This is a questionable use case as we are only creating |
- // client side objects in the database. There would ideally be |
- // a version of this code that returned a list of Promises that |
- // called proper env methods (for all the objects, not just |
- // units) to mutate a real env. |
- // This however will allow us to import bundles into a fresh |
- // database with the intention of only rendering it. |
- //if (!useGhost) {} |
- }); |
- }) |
- .then(function() { |
- if (!source.relations) { return; } |
- source.relations.forEach(function(relationData) { |
- if (relationData.length !== 2) { |
- // Skip peer relations |
- return; |
- } |
- self.addRelation(relationData[0], relationData[1], useGhost); |
- }); |
- }); |
- |
- }, |
- |
- /** |
* Export deployer formatted dump of the current environment. |
* Note: When we have a selection UI in place this should honor |
* that. |