Merge lp:~bac/juju-gui/get-endpoints into lp:juju-gui/experimental

Proposed by Brad Crittenden
Status: Merged
Merged at revision: 487
Proposed branch: lp:~bac/juju-gui/get-endpoints
Merge into: lp:juju-gui/experimental
Diff against target: 1003 lines (+465/-199)
15 files modified
app/app.js (+9/-48)
app/models/endpoints.js (+122/-1)
app/models/models.js (+1/-0)
app/store/env/fakebackend.js (+0/-4)
app/store/env/go.js (+0/-18)
app/store/env/python.js (+0/-17)
app/store/env/sandbox.js (+0/-7)
app/views/environment.js (+0/-1)
app/views/topology/relation.js (+2/-4)
test/test_app.js (+20/-37)
test/test_browser_charm_details.js (+1/-2)
test/test_endpoints.js (+255/-0)
test/test_env_python.js (+0/-11)
test/test_environment_view.js (+4/-4)
test/test_notifications.js (+51/-45)
To merge this branch: bzr merge lp:~bac/juju-gui/get-endpoints
Reviewer Review Type Date Requested Status
Juju GUI Hackers Pending
Review via email: mp+156661@code.launchpad.net

Description of the change

Track endpoints as state.

Remove the get_endpoints calls in the environment and the API. Instead
keep the endpoints map up-to-date when services are added,
deleted, and changed.

https://codereview.appspot.com/8275043/

To post a comment you must log in.
Revision history for this message
Brad Crittenden (bac) wrote :

Reviewers: mp+156661_code.launchpad.net,

Message:
Please take a look.

Description:
Track endpoints as state.

Remove the get_endpoints calls in the environment and the API. Instead
keep the endpoints map up-to-date when services are added,
deleted, and changed.

https://code.launchpad.net/~bac/juju-gui/get-endpoints/+merge/156661

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/8275043/

Affected files:
   A [revision details]
   M app/app.js
   M app/models/endpoints.js
   M app/models/models.js
   M app/store/env/fakebackend.js
   M app/store/env/go.js
   M app/store/env/python.js
   M app/store/env/sandbox.js
   M app/views/environment.js
   M app/views/topology/relation.js
   M test/test_app.js
   M test/test_browser_charm_details.js
   M test/test_endpoints.js
   M test/test_env_python.js
   M test/test_environment_view.js
   M test/test_notifications.js

Revision history for this message
Jeff Pihach (hatch) wrote :

This is looking great Thanks for all this work! It really cleans up a
lot of code.

I'd like to open up a discussion between a few of us to see if this
should be moved into it's own closure and then the rest of the comments
are pretty trivial.

https://codereview.appspot.com/8275043/diff/2001/app/models/endpoints.js
File app/models/endpoints.js (right):

https://codereview.appspot.com/8275043/diff/2001/app/models/endpoints.js#newcode12
app/models/endpoints.js:12: var models = Y.namespace('juju.models');
I know that this is how it was done previously but it feels like these
methods are just hanging off of juju.models with all of the other models
when they should really be within their own closure of utility methods
to clean up the object. This should also make it easier to test.
Thoughts?

https://codereview.appspot.com/8275043/diff/2001/app/models/endpoints.js#newcode15
app/models/endpoints.js:15: models.endpoints_map = {};
camelCase?

https://codereview.appspot.com/8275043/diff/2001/app/models/endpoints.js#newcode169
app/models/endpoints.js:169: // var requires =
evt.currentTarget.get('requires');
commented out old code that should be removed?

https://codereview.appspot.com/8275043/diff/2001/app/models/endpoints.js#newcode255
app/models/endpoints.js:255: if (true) { // Avoid lint warning.
Is this required so that the linter doesn't complain about the vars
being re-declared below?

https://codereview.appspot.com/8275043/diff/2001/app/models/endpoints.js#newcode256
app/models/endpoints.js:256: var rel = {};
I'm surprised that the linter didn't complain about these vars being
re-declared every loop. Typically `k`, `rel`, `j` would be defined at
the top of the fn with `result` then re-assigned in every loop.

https://codereview.appspot.com/8275043/diff/2001/app/models/models.js
File app/models/models.js (right):

https://codereview.appspot.com/8275043/diff/2001/app/models/models.js#newcode563
app/models/models.js:563: this.endpoints_map = {};
camelCase?

https://codereview.appspot.com/8275043/diff/2001/app/views/topology/relation.js
File app/views/topology/relation.js (right):

https://codereview.appspot.com/8275043/diff/2001/app/views/topology/relation.js#newcode563
app/views/topology/relation.js:563: var endpoints =
models.getEndpoints(service, models.endpoints_map, db);
camelCase endpoints_map

https://codereview.appspot.com/8275043/diff/2001/test/test_endpoints.js
File test/test_endpoints.js (right):

https://codereview.appspot.com/8275043/diff/2001/test/test_endpoints.js#newcode135
test/test_endpoints.js:135: var charm = new models.Charm({id:
'cs:precise/wordpress-2'});
Because this attaches events we should destroy it at the end of every
test and make sure that the events that it attaches are destroyed as
well.

https://codereview.appspot.com/8275043/

Revision history for this message
Benjamin Saller (bcsaller) wrote :

This is looking good but I do ask a structural change below. I hope I
explain it well enough (and the reasons for it). If you have questions
let me know.

https://codereview.appspot.com/8275043/diff/2001/app/app.js
File app/app.js (left):

https://codereview.appspot.com/8275043/diff/2001/app/app.js#oldcode478
app/app.js:478:
Nice that this removed so much code from App :)

https://codereview.appspot.com/8275043/diff/2001/app/app.js
File app/app.js (right):

https://codereview.appspot.com/8275043/diff/2001/app/app.js#newcode364
app/app.js:364: this);
This is fine for now but When I have this many subscriptions I'd usually
wrap it in a controller object such that

controller._subscriptions = controller.bind()

and controller.unbind can use the subscription object to remove
listeners.

https://codereview.appspot.com/8275043/diff/2001/app/models/endpoints.js
File app/models/endpoints.js (right):

https://codereview.appspot.com/8275043/diff/2001/app/models/endpoints.js#newcode29
app/models/endpoints.js:29: models.getEndpoints = function(svc, ep_map,
db) {
This signature doesn't work well anymore, the db may or may not be the
one the service handlers were bound to. By making a controller object we
can reference the db when we bind the handlers and drop this argument
making it consistent.

https://codereview.appspot.com/8275043/diff/2001/app/models/endpoints.js#newcode167
app/models/endpoints.js:167: charm.on('load', Y.bind(function(svcName,
evt) {
It shouldn't matter, but this seems like a case for charm.once(...)
rather than on() as the charm should be static post load and we don't
need to keep the listener active.

https://codereview.appspot.com/8275043/diff/2001/app/models/endpoints.js#newcode181
app/models/endpoints.js:181: models.serviceAddHandler = function(evt) {
If we had a controller here like EndpointManager or something we could
hang the handlers off that rather than models which would be nicer (In
addition to the bind/unbind stuff mentioned before).

https://codereview.appspot.com/8275043/diff/2001/app/models/endpoints.js#newcode254
app/models/endpoints.js:254: for (var k in meta) {
Why is this done in native JS rather than with each() or map()? (I
think) The linter complaining here is because in a loop we want to know
if the current item is owned by the object or part of its prototype, the
higher level constructs can safely handle those cases for us.

https://codereview.appspot.com/8275043/diff/2001/app/store/env/fakebackend.js
File app/store/env/fakebackend.js (left):

https://codereview.appspot.com/8275043/diff/2001/app/store/env/fakebackend.js#oldcode427
app/store/env/fakebackend.js:427: // },
nice catch, thanks for pulling all these from the envs

https://codereview.appspot.com/8275043/diff/2001/app/views/topology/relation.js
File app/views/topology/relation.js (right):

https://codereview.appspot.com/8275043/diff/2001/app/views/topology/relation.js#newcode563
app/views/topology/relation.js:563: var endpoints =
models.getEndpoints(service, models.endpoints_map, db);
as noted before, in causes like this we can't always be sure the db is
the one the listeners were bound to.

https://codereview.appspot.com/8275043/

Revision history for this message
Jeff Pihach (hatch) wrote :

LGTM with Ben's changes and those instances in the tests destroyed.

Thanks again, Great work!

https://codereview.appspot.com/8275043/

Revision history for this message
Benjamin Saller (bcsaller) wrote :

LGTM

as discussed online the changes I suggested will be pushed to another
branch (and I am fine with that).

https://codereview.appspot.com/8275043/

lp:~bac/juju-gui/get-endpoints updated
472. By Brad Crittenden

Removed commented-out code

473. By Brad Crittenden

Clean up tests

Revision history for this message
Brad Crittenden (bac) wrote :

Thanks for the reviews and the great suggestions. Since none of them
are show stoppers I'll address them in a follow-on branch so this
blocking work can land, albeit a bit unclean.

https://codereview.appspot.com/8275043/diff/2001/app/app.js
File app/app.js (right):

https://codereview.appspot.com/8275043/diff/2001/app/app.js#newcode364
app/app.js:364: this);
Great suggestion Ben. I'll do it in the follow-on branch. A card has
been created with my face on it.

https://codereview.appspot.com/8275043/diff/2001/app/models/endpoints.js
File app/models/endpoints.js (right):

https://codereview.appspot.com/8275043/diff/2001/app/models/endpoints.js#newcode15
app/models/endpoints.js:15: models.endpoints_map = {};
On 2013/04/02 19:56:43, jeff.pihach wrote:
> camelCase?

Done.

https://codereview.appspot.com/8275043/diff/2001/app/models/endpoints.js#newcode254
app/models/endpoints.js:254: for (var k in meta) {
No good reason. I'll clean that up tomorrow.

https://codereview.appspot.com/8275043/diff/2001/app/models/models.js
File app/models/models.js (right):

https://codereview.appspot.com/8275043/diff/2001/app/models/models.js#newcode563
app/models/models.js:563: this.endpoints_map = {};
On 2013/04/02 19:56:43, jeff.pihach wrote:
> camelCase?

Done.

https://codereview.appspot.com/8275043/diff/2001/test/test_endpoints.js
File test/test_endpoints.js (right):

https://codereview.appspot.com/8275043/diff/2001/test/test_endpoints.js#newcode135
test/test_endpoints.js:135: var charm = new models.Charm({id:
'cs:precise/wordpress-2'});
On 2013/04/02 19:56:43, jeff.pihach wrote:
> Because this attaches events we should destroy it at the end of every
test and
> make sure that the events that it attaches are destroyed as well.

Done.

https://codereview.appspot.com/8275043/

Revision history for this message
Brad Crittenden (bac) wrote :

*** Submitted:

Track endpoints as state.

Remove the get_endpoints calls in the environment and the API. Instead
keep the endpoints map up-to-date when services are added,
deleted, and changed.

R=jeff.pihach, bcsaller
CC=
https://codereview.appspot.com/8275043

https://codereview.appspot.com/8275043/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'app/app.js'
2--- app/app.js 2013-04-01 17:31:13 +0000
3+++ app/app.js 2013-04-02 21:29:23 +0000
4@@ -262,7 +262,6 @@
5
6 // Create a client side database to store state.
7 this.db = new models.Database();
8- this.serviceEndpoints = {};
9
10 // Optional Landscape integration helper.
11 this.landscape = new views.Landscape();
12@@ -359,6 +358,14 @@
13 this.env.on('delta', this.notifications.generate_notices,
14 this.notifications);
15
16+ // Handlers for adding and removing services to the service list.
17+ this.db.services.after('add', models.serviceAddHandler, this);
18+ this.db.services.after('remove', models.serviceRemoveHandler, this);
19+ this.db.services.after('*:pendingChange', models.serviceChangeHandler,
20+ this);
21+ this.db.services.after('*:charmChange', models.serviceChangeHandler,
22+ this);
23+
24 // When the connection resets, reset the db, re-login (a delta will
25 // arrive with successful authentication), and redispatch.
26 this.env.after('connectedChange', function(ev) {
27@@ -451,7 +458,7 @@
28 },
29
30 /**
31- * On database changes, update the endpoints map and update the view.
32+ * On database changes update the view.
33 *
34 * @method on_database_changed
35 */
36@@ -461,24 +468,6 @@
37 var self = this;
38 var active = this.get('activeView');
39
40- // Compare endpoints map against db to see if services have been added.
41- var servicesAdded = this.db.services.some(function(service) {
42- return (self.serviceEndpoints[service.get('id')] === undefined);
43- });
44-
45- // If there are new services in the DB, pull an updated endpoints map.
46- if (servicesAdded) {
47- this.updateEndpoints();
48- } else {
49- // If any services have been removed, delete them from the map
50- // rather than updating it as a whole.
51- Y.Object.each(this.serviceEndpoints, function(key, value, obj) {
52- if (self.db.services.getById(key) === null) {
53- delete(self.serviceEndpoints[key]);
54- }
55- });
56- }
57-
58 // Update Landscape annotations.
59 this.landscape.update();
60
61@@ -495,27 +484,6 @@
62 }
63 },
64
65- /**
66- * When services are added, update the endpoints map.
67- *
68- * @method updateEndpoints
69- */
70- updateEndpoints: function(callback) {
71- var self = this;
72-
73- // Defensive code to aid tests. Other systems
74- // do not have to mock enough to get_endpoints below.
75- if (!this.env.get('connected')) {
76- return;
77- }
78- self.env.get_endpoints([], function(evt) {
79- self.serviceEndpoints = evt.result;
80- if (Y.Lang.isFunction(callback)) {
81- callback(self.serviceEndpoints);
82- }
83- });
84- },
85-
86 // Route handlers
87
88 /**
89@@ -851,13 +819,6 @@
90 options = {
91 getModelURL: Y.bind(this.getModelURL, this),
92 nsRouter: this.nsRouter,
93- /**
94- * A simple closure so changes to the value are available.
95- *
96- * @method show_environment.options.getServiceEndpoints
97- */
98- getServiceEndpoints: function() {
99- return self.serviceEndpoints;},
100 loadService: this.loadService,
101 landscape: this.landscape,
102 db: this.db,
103
104=== modified file 'app/models/endpoints.js'
105--- app/models/endpoints.js 2013-02-08 17:36:58 +0000
106+++ app/models/endpoints.js 2013-04-02 21:29:23 +0000
107@@ -12,10 +12,12 @@
108 var models = Y.namespace('juju.models');
109 var utils = Y.namespace('juju.views.utils');
110
111+ models.endpointsMap = {};
112+
113 /**
114 * Find available relation targets for a service.
115 *
116- * @class getEndpoints
117+ * @method getEndpoints
118 * @param {Object} svc A service object.
119 * @param {Object} ep_map A mapping of service name to available endpoints
120 * for the service in the form [{'interface':x, 'name':y, 'role': z}, ...].
121@@ -152,4 +154,123 @@
122 });
123 return targets;
124 };
125+
126+ /**
127+ * Setup on('load') handler for a charm.
128+ *
129+ * @method setupCharmOnLoad
130+ * @param {Object} charm The charm to watch.
131+ * @param {String} svcName The name of the service the charm is attached.
132+ * @return {undefined} Nothing.
133+ */
134+ var setupCharmOnLoad = function(charm, svcName) {
135+ charm.on('load', Y.bind(function(svcName, evt) {
136+ addServiceToEndpointsMap(svcName, evt.currentTarget);
137+ }, null, svcName));
138+ };
139+
140+ /**
141+ * Handle event for a service being added to the services modellist.
142+ *
143+ * @method serviceAddHandler
144+ * @param {Object} evt The event, containing a model object.
145+ * @return {undefined} Nothing.
146+ */
147+ models.serviceAddHandler = function(evt) {
148+ var svcName = evt.model.get('id');
149+ var charm_id = evt.model.get('charm'),
150+ self = this,
151+ charm = this.db.charms.getById(charm_id);
152+
153+ // If the charm doesn't exist, add and load it.
154+ if (!Y.Lang.isValue(charm)) {
155+ charm = this.db.charms.add({id: charm_id})
156+ .load(this.env,
157+ // If views are bound to the charm model, firing "update" is
158+ // unnecessary, and potentially even mildly harmful.
159+ function(err, result) { self.db.fire('update'); });
160+ }
161+
162+ // If the service is not a ghost (i.e. 'pending' is false), process it.
163+ if (!evt.model.get('pending')) {
164+ if (charm.loaded) {
165+ addServiceToEndpointsMap(svcName, charm);
166+ } else {
167+ setupCharmOnLoad(charm, svcName);
168+ }
169+ }
170+ };
171+
172+ /**
173+ * Handle event for a service transitioning from a ghost to a corporeal
174+ * object as indicated by the 'pending' attribute becoming false. Also
175+ * handles changes in the service's charm.
176+ *
177+ * @method serviceChangeHandler
178+ * @param {Object} evt The event, containing the service as the target.
179+ * @return {undefined} Nothing.
180+ */
181+ models.serviceChangeHandler = function(evt) {
182+ var charm_id = evt.target.get('charm'),
183+ charm = this.db.charms.getById(charm_id),
184+ service = evt.target,
185+ svcName = service.get('id');
186+ // Ensure the service is no longer pending.
187+ if (service.get('pending')) {
188+ return;
189+ }
190+ if (charm.loaded) {
191+ addServiceToEndpointsMap(svcName, charm);
192+ } else {
193+ setupCharmOnLoad(charm, svcName);
194+ }
195+ };
196+
197+ /**
198+ * Handle event for a service removal.
199+ *
200+ * @method serviceRemoveHandler
201+ * @param {Object} evt The event, containing the service as the target.
202+ * @return {undefined} Nothing.
203+ */
204+ models.serviceRemoveHandler = function(evt) {
205+ var svcName = evt.model.get('id');
206+ delete(models.endpointsMap[svcName]);
207+ };
208+
209+ /**
210+ * Flatten the relation metadata.
211+ *
212+ * @method flatten
213+ * @param {Object} meta The relation metadata.
214+ * @return {List} A list of objects, where each entry is a hash with a
215+ * 'name' key and the key value pairs from the metadata.
216+ */
217+ var flatten = function(meta) {
218+ var result = [];
219+ if (Y.Lang.isValue(meta)) {
220+ for (var k in meta) {
221+ if (true) { // Avoid lint warning.
222+ var rel = {};
223+ rel.name = k;
224+ for (var j in meta[k]) {
225+ if (true) { // Avoid lint warning.
226+ rel[j] = meta[k][j];
227+ }
228+ }
229+ result.push(rel);
230+ }
231+ }
232+ }
233+ return result;
234+ };
235+
236+ var addServiceToEndpointsMap = function(svcName, charm) {
237+ models.endpointsMap[svcName] = {};
238+ models.endpointsMap[svcName].provides = flatten(charm.get('provides'));
239+ models.endpointsMap[svcName].requires = flatten(charm.get('requires'));
240+ };
241+
242+ models.addServiceToEndpointsMap = addServiceToEndpointsMap;
243+
244 });
245
246=== modified file 'app/models/models.js'
247--- app/models/models.js 2013-03-21 21:39:47 +0000
248+++ app/models/models.js 2013-04-02 21:29:23 +0000
249@@ -560,6 +560,7 @@
250 this.relations.reset();
251 this.units.reset();
252 this.notifications.reset();
253+ this.endpointsMap = {};
254 },
255
256 on_delta: function(delta_evt) {
257
258=== modified file 'app/store/env/fakebackend.js'
259--- app/store/env/fakebackend.js 2013-03-21 21:39:47 +0000
260+++ app/store/env/fakebackend.js 2013-04-02 21:29:23 +0000
261@@ -422,10 +422,6 @@
262
263 // },
264
265- // getEndpoints: function() {
266-
267- // },
268-
269 // updateAnnotations: function() {
270
271 // },
272
273=== modified file 'app/store/env/go.js'
274--- app/store/env/go.js 2013-04-02 15:18:57 +0000
275+++ app/store/env/go.js 2013-04-02 21:29:23 +0000
276@@ -149,24 +149,6 @@
277 },
278
279 /**
280- XXX FAKE FAKE FAKE, does not do anything.
281-
282- Get the available endpoints (by interface) for a collection of
283- services.
284-
285- @method get_endpoints
286- @param {Array} services Zero or more currently deployed services for
287- which the endpoints should be collected. Specifying an empty array
288- indicates that all deployed services should be analyzed.
289- @param {Function} callback A callable that must be called once the
290- operation is performed.
291- @return {undefined} Sends a message to the server only.
292- */
293- get_endpoints: function(services, callback) {
294- },
295-
296-
297- /**
298 * See "app.store.env.base.BaseEnvironment.dispatch_result".
299 *
300 * @method dispatch_result
301
302=== modified file 'app/store/env/python.js'
303--- app/store/env/python.js 2013-03-27 14:50:52 +0000
304+++ app/store/env/python.js 2013-04-02 21:29:23 +0000
305@@ -381,23 +381,6 @@
306 },
307
308 /**
309- Get the available endpoints (by interface) for a collection of
310- services.
311-
312- @method get_endpoints
313- @param {Array} services Zero or more currently deployed services for
314- which the endpoints should be collected. Specifying an empty array
315- indicates that all deployed services should be analyzed.
316- @param {Function} callback A callable that must be called once the
317- operation is performed.
318- @return {undefined} Sends a message to the server only.
319- */
320- get_endpoints: function(services, callback) {
321- this._send_rpc({'op': 'get_endpoints', 'service_names': services},
322- callback);
323- },
324-
325- /**
326 * Update the annotations for an entity by name.
327 *
328 * @param {Object} entity The name of a machine, unit, service, or
329
330=== modified file 'app/store/env/sandbox.js'
331--- app/store/env/sandbox.js 2013-04-01 22:32:24 +0000
332+++ app/store/env/sandbox.js 2013-04-02 21:29:23 +0000
333@@ -341,13 +341,6 @@
334 },
335
336 /**
337- Handles get_endpoints operations from client. Called by receive.
338- PLACEHOLDER. This exists to demo existing functionality.
339- **/
340- performOp_get_endpoints: function(data) {
341- },
342-
343- /**
344 Handles update_annotations operations from client. Called by receive.
345 PLACEHOLDER. This exists to demo existing functionality.
346 **/
347
348=== modified file 'app/views/environment.js'
349--- app/views/environment.js 2013-03-21 15:32:06 +0000
350+++ app/views/environment.js 2013-04-02 21:29:23 +0000
351@@ -69,7 +69,6 @@
352 db: this.get('db'),
353 landscape: this.get('landscape'),
354 getModelURL: this.get('getModelURL'),
355- getServiceEndpoints: this.get('getServiceEndpoints'),
356 container: container,
357 nsRouter: this.get('nsRouter')});
358 // Bind all the behaviors we need as modules.
359
360=== modified file 'app/views/topology/relation.js'
361--- app/views/topology/relation.js 2013-03-28 15:08:38 +0000
362+++ app/views/topology/relation.js 2013-04-02 21:29:23 +0000
363@@ -560,10 +560,8 @@
364 topo.fire('show', { selection: vis.selectAll('.service') });
365
366 var db = this.get('component').get('db');
367- var getServiceEndpoints = this.get('component')
368- .get('getServiceEndpoints');
369- var endpoints = models.getEndpoints(
370- service, getServiceEndpoints(), db);
371+ var endpoints = models.getEndpoints(service, models.endpointsMap, db);
372+
373 // Transform endpoints into a list of relatable services (to the
374 // service).
375 var possible_relations = Y.Array.map(
376
377=== modified file 'test/test_app.js'
378--- test/test_app.js 2013-03-22 00:00:29 +0000
379+++ test/test_app.js 2013-04-02 21:29:23 +0000
380@@ -34,12 +34,14 @@
381 (function() {
382
383 describe('Application basics', function() {
384- var Y, app, container;
385+ var Y, app, container, utils, juju, env, conn;
386
387 before(function(done) {
388 Y = YUI(GlobalConfig).use(
389 ['juju-gui', 'juju-tests-utils', 'juju-view-utils'],
390 function(Y) {
391+ utils = Y.namespace('juju-tests.utils');
392+ juju = Y.namespace('juju');
393 done();
394 });
395 });
396@@ -54,13 +56,18 @@
397 .append(Y.Node.create('<span/>')
398 .addClass('provider-type'))
399 .hide();
400+ conn = new utils.SocketStub();
401+ env = juju.newEnvironment({conn: conn});
402+ env.connect();
403 app = new Y.juju.App(
404 { container: container,
405- viewContainer: container});
406+ viewContainer: container,
407+ env: env});
408 injectData(app);
409 });
410
411 afterEach(function() {
412+ app.destroy();
413 container.remove(true);
414 sessionStorage.setItem('credentials', null);
415 });
416@@ -77,6 +84,7 @@
417 var the_username = 'nehi';
418 var the_password = 'moonpie';
419 // Replace the existing app.
420+ app.destroy();
421 app = new Y.juju.App(
422 { container: container,
423 user: the_username,
424@@ -89,6 +97,7 @@
425
426 it('propagates the readOnly option from the configuration', function() {
427 // Replace the existing app.
428+ app.destroy();
429 app = new Y.juju.App({
430 container: container,
431 readOnly: true,
432@@ -108,7 +117,7 @@
433 // needed to show them.
434 var wordpress = app.db.services.getById('wordpress'),
435 wp0 = app.db.units.get_units_for_service(wordpress)[0],
436- wp_charm = app.db.charms.add({id: wordpress.get('charm')});
437+ wp_charm = app.db.charms.getById(wordpress.get('charm'));
438
439 // 'service/wordpress/' is the primary route,
440 // so other URLs are not returned.
441@@ -127,6 +136,7 @@
442
443 it('should display the configured environment name', function() {
444 var environment_name = 'This is the environment name. Deal with it.';
445+ app.destroy();
446 app = new Y.juju.App(
447 { container: container,
448 viewContainer: container,
449@@ -138,6 +148,7 @@
450
451 it('should show a generic environment name if none configured',
452 function() {
453+ app.destroy();
454 app = new Y.juju.App(
455 { container: container,
456 viewContainer: container});
457@@ -330,7 +341,6 @@
458 viewContainer: container,
459 env: env,
460 charm_store: {} });
461- env.get_endpoints = function() {};
462 });
463
464 afterEach(function() {
465@@ -339,46 +349,19 @@
466 });
467
468 it('must prefetch charm and service for service pages', function() {
469- injectData(app);
470 var _ = expect(
471 app.db.charms.getById('cs:precise/wordpress-6')).to.not.exist;
472+ injectData(app);
473 app.show_service({params: {id: 'wordpress'}, query: {}});
474- // The app made a request of juju for the service info.
475- conn.messages[conn.messages.length - 2].op.should.equal('get_service');
476- // The app also requested juju (not the charm store--see discussion in
477- // app/models/charm.js) for the charm info.
478- conn.last_message().op.should.equal('get_charm');
479+ // When the service was added to the service modellist, that triggered
480+ // the loading of the charm.
481+ conn.messages[conn.messages.length - 2].op.should.equal('get_charm');
482+ // The service was later loaded.
483+ conn.last_message().op.should.equal('get_service');
484 // Tests of the actual load machinery are in the model and env tests, and
485 // so are not repeated here.
486 });
487
488- it('must request endpoints only when necessary', function() {
489- var get_endpoints_count = 0,
490- tmp_data = {
491- result: [
492- ['service', 'add', {
493- 'charm': 'cs:precise/mysql-6',
494- 'id': 'mysql2'
495- }],
496- ['unit', 'add', {
497- 'machine': 0,
498- 'agent-state': 'started',
499- 'public-address': '192.168.122.222',
500- 'id': 'mysql2/0'
501- }]
502- ],
503- op: 'delta'
504- };
505- env.get_endpoints = function(services, callback) {
506- get_endpoints_count += 1;
507- };
508- // Inject default data, should only get_endpoints once.
509- injectData(app);
510- get_endpoints_count.should.equal(1);
511- // Additional deltas should only call get_endpoints once.
512- app.db.on_delta({ data: tmp_data });
513- get_endpoints_count.should.equal(2);
514- });
515 });
516 })();
517
518
519=== modified file 'test/test_browser_charm_details.js'
520--- test/test_browser_charm_details.js 2013-04-02 17:24:31 +0000
521+++ test/test_browser_charm_details.js 2013-04-02 21:29:23 +0000
522@@ -162,7 +162,7 @@
523 });
524
525 view.render(node);
526- Y.one('#bws_hooks').all('ul li a').size().should.eql(2);
527+ Y.one('#bws_hooks').all('ul li a').size().should.equal(2);
528
529 // Click on the hooks install and the content should update.
530 Y.one('#bws_hooks').one('ul li a').simulate('click');
531@@ -267,4 +267,3 @@
532 });
533 });
534 })();
535-
536
537=== modified file 'test/test_endpoints.js'
538--- test/test_endpoints.js 2013-01-24 16:46:38 +0000
539+++ test/test_endpoints.js 2013-04-02 21:29:23 +0000
540@@ -116,3 +116,258 @@
541 });
542
543
544+describe('Endpoints map', function() {
545+ var Y, juju, models;
546+
547+ beforeEach(function(done) {
548+ Y = YUI(GlobalConfig).use(['juju-models',
549+ 'juju-tests-utils',
550+ 'juju-controllers'],
551+ function(Y) {
552+ juju = Y.namespace('juju');
553+ models = Y.namespace('juju.models');
554+ done();
555+ });
556+ });
557+
558+ it('should add a service to the map', function() {
559+ models.endpointsMap = {};
560+ var charm = new models.Charm({id: 'cs:precise/wordpress-2'});
561+ charm.set('provides', {
562+ url: {
563+ 'interface': 'http',
564+ optional: 'false'
565+ },
566+ 'logging-dir': {
567+ 'interface': 'logging',
568+ scope: 'container'
569+ }
570+ });
571+ charm.set('requires', {
572+ db: {
573+ 'interface': 'mysql',
574+ optional: 'false'
575+ },
576+ cache: {
577+ 'interface': 'varnish',
578+ optional: 'true'
579+ }
580+ });
581+ models.addServiceToEndpointsMap('wordpress', charm);
582+ models.endpointsMap.should.eql({wordpress: {
583+ provides: [
584+ {
585+ name: 'url',
586+ 'interface': 'http',
587+ optional: 'false'
588+ }, {
589+ name: 'logging-dir',
590+ 'interface': 'logging',
591+ scope: 'container'
592+ }
593+ ],
594+ requires: [
595+ {
596+ name: 'db',
597+ 'interface': 'mysql',
598+ optional: 'false'
599+ }, {
600+ name: 'cache',
601+ 'interface': 'varnish',
602+ optional: 'true'
603+ }
604+ ]
605+ }});
606+ charm.destroy();
607+ });
608+
609+ it('should add a service to the map, requires only', function() {
610+ models.endpointsMap = {};
611+ var charm = new models.Charm({id: 'cs:precise/wordpress-2'});
612+ charm.set('requires', {
613+ db: {
614+ 'interface': 'mysql',
615+ optional: 'false'
616+ },
617+ cache: {
618+ 'interface': 'varnish',
619+ optional: 'true'
620+ }
621+ });
622+ models.addServiceToEndpointsMap('wordpress', charm);
623+ models.endpointsMap.should.eql({wordpress: {
624+ provides: [],
625+ requires: [
626+ {
627+ name: 'db',
628+ 'interface': 'mysql',
629+ optional: 'false'
630+ }, {
631+ name: 'cache',
632+ 'interface': 'varnish',
633+ optional: 'true'
634+ }
635+ ]
636+ }});
637+ charm.destroy();
638+ });
639+
640+ it('should add a service to the map, provides only', function() {
641+ models.endpointsMap = {};
642+ var charm = new models.Charm({id: 'cs:precise/wordpress-2'});
643+ charm.set('provides', {
644+ url: {
645+ 'interface': 'http',
646+ optional: 'false'
647+ },
648+ 'logging-dir': {
649+ 'interface': 'logging',
650+ scope: 'container'
651+ }
652+ });
653+ models.addServiceToEndpointsMap('wordpress', charm);
654+ models.endpointsMap.should.eql({wordpress: {
655+ requires: [],
656+ provides: [
657+ {
658+ name: 'url',
659+ 'interface': 'http',
660+ optional: 'false'
661+ }, {
662+ name: 'logging-dir',
663+ 'interface': 'logging',
664+ scope: 'container'
665+ }
666+ ] }});
667+ charm.destroy();
668+ });
669+
670+ it('should add a service to the map, neither provides nor requires',
671+ function() {
672+ models.endpointsMap = {};
673+ var charm = new models.Charm({id: 'cs:precise/wordpress-2'});
674+ models.addServiceToEndpointsMap('wordpress', charm);
675+ models.endpointsMap.should.eql({wordpress: {
676+ requires: [],
677+ provides: []}});
678+ charm.destroy();
679+ });
680+
681+});
682+
683+describe('Endpoints map handlers', function() {
684+ var Y, juju, utils, models, app, conn, env;
685+
686+ before(function(done) {
687+ Y = YUI(GlobalConfig).use(['juju-gui',
688+ 'juju-models',
689+ 'juju-tests-utils',
690+ 'juju-controllers'],
691+ function(Y) {
692+ juju = Y.namespace('juju');
693+ utils = Y.namespace('juju-tests.utils');
694+ models = Y.namespace('juju.models');
695+ done();
696+ });
697+ });
698+
699+ beforeEach(function() {
700+ conn = new utils.SocketStub();
701+ env = juju.newEnvironment({conn: conn});
702+ env.connect();
703+ app = new Y.juju.App({env: env});
704+ models.endpointsMap = {};
705+ });
706+
707+ afterEach(function() {
708+ env.destroy();
709+ app.destroy();
710+ });
711+
712+ it('should not update endpoints map when pending services are added',
713+ function() {
714+ var service_name = 'wordpress';
715+ var charm_id = 'cs:precise/wordpress-2';
716+ app.db.charms.add({id: charm_id});
717+ var charm = app.db.charms.getById(charm_id);
718+ charm.loaded = true;
719+ app.db.services.add({
720+ id: service_name,
721+ pending: true,
722+ charm: charm_id});
723+ models.endpointsMap.should.eql({});
724+ charm.destroy();
725+ });
726+
727+ it('should update endpoints map when non-pending services are added',
728+ function() {
729+ var service_name = 'wordpress';
730+ var charm_id = 'cs:precise/wordpress-2';
731+ app.db.charms.add({id: charm_id});
732+ var charm = app.db.charms.getById(charm_id);
733+ charm.loaded = true;
734+ app.db.services.add({
735+ id: service_name,
736+ pending: true,
737+ charm: charm_id});
738+ var svc = app.db.services.getById(service_name);
739+ svc.set('pending', false);
740+ models.endpointsMap.should.eql({wordpress: {
741+ requires: [],
742+ provides: []}});
743+ charm.destroy();
744+ });
745+
746+ it('should update endpoints map when a service\'s charm changes', function() {
747+ var service_name = 'wordpress';
748+ var charm_id = 'cs:precise/wordpress-2';
749+ app.db.charms.add({id: charm_id});
750+ var charm = app.db.charms.getById(charm_id);
751+ charm.loaded = true;
752+ app.db.services.add({
753+ id: service_name,
754+ pending: true,
755+ charm: charm_id});
756+ var svc = app.db.services.getById(service_name);
757+ svc.set('pending', false);
758+ models.endpointsMap.should.eql({wordpress: {
759+ requires: [],
760+ provides: []}});
761+
762+ charm_id = 'cs:precise/wordpress-3';
763+ app.db.charms.add({id: charm_id});
764+ var charm2 = app.db.charms.getById(charm_id);
765+ charm2.set('provides', {
766+ url: {
767+ 'interface': 'http'
768+ }
769+ });
770+
771+ charm2.loaded = true;
772+ svc.set('charm', charm_id);
773+ models.endpointsMap.should.eql({wordpress: {
774+ requires: [],
775+ provides: [
776+ {
777+ name: 'url',
778+ 'interface': 'http'
779+ }]}});
780+ charm.destroy();
781+ charm2.destroy();
782+ });
783+
784+ it('should remove service from endpoints map when it is deleted', function() {
785+ var service_name = 'wordpress';
786+ var charm_id = 'cs:precise/wordpress-2';
787+ app.db.charms.add({id: charm_id});
788+ app.db.services.add({
789+ id: service_name,
790+ charm: charm_id});
791+ models.endpointsMap = {wordpress: 'foo'};
792+ var service = app.db.services.getById(service_name);
793+ app.db.services.remove(service);
794+ models.endpointsMap.should.eql({});
795+ app.db.charms.getById(charm_id).destroy();
796+ });
797+
798+});
799
800=== modified file 'test/test_env_python.js'
801--- test/test_env_python.js 2013-03-22 12:49:57 +0000
802+++ test/test_env_python.js 2013-04-02 21:29:23 +0000
803@@ -224,13 +224,6 @@
804 assert.equal(env.get('defaultSeries'), defaultSeries);
805 });
806
807- it('can get endpoints for a service', function() {
808- env.get_endpoints(['mysql']);
809- msg = conn.last_message();
810- msg.op.should.equal('get_endpoints');
811- msg.service_names.should.eql(['mysql']);
812- });
813-
814 it('can update annotations', function() {
815 var unit_name = 'mysql/0';
816 env.update_annotations(unit_name, {name: 'A'});
817@@ -375,10 +368,6 @@
818 assertOperationAllowed('get_charm', ['cs:precise/haproxy']);
819 });
820
821- it('allows retrieving endpoints if the GUI is read-only', function() {
822- assertOperationAllowed('get_endpoints', [['haproxy']]);
823- });
824-
825 it('allows getting services if the GUI is read-only', function() {
826 assertOperationAllowed('get_service', ['haproxy']);
827 });
828
829=== modified file 'test/test_environment_view.js'
830--- test/test_environment_view.js 2013-03-26 16:09:53 +0000
831+++ test/test_environment_view.js 2013-04-02 21:29:23 +0000
832@@ -674,7 +674,6 @@
833 function() {
834 var view = new views.environment({
835 container: container,
836- getServiceEndpoints: function() {return {};},
837 db: db,
838 env: env
839 }).render();
840@@ -795,15 +794,16 @@
841 it('should stop creating a relation if the background is clicked',
842 function() {
843 var db = new models.Database(),
844- endpoint_map = {'service-1': {requires: [], provides: []}},
845+ endpointMap = {'service-1': {requires: [], provides: []}},
846 view = new views.environment(
847 { container: container,
848 db: db,
849- env: env,
850- getServiceEndpoints: function() {return endpoint_map;}}),
851+ env: env}),
852 service = new models.Service({ id: 'service-1'});
853
854 db.services.add([service]);
855+ // Reset the global endpointsMap after adding the service.
856+ models.endpointsMap = endpointMap;
857 view.render();
858
859 // If the user has clicked on the "Add Relation" menu item...
860
861=== modified file 'test/test_notifications.js'
862--- test/test_notifications.js 2013-03-20 20:40:10 +0000
863+++ test/test_notifications.js 2013-04-02 21:29:23 +0000
864@@ -189,12 +189,15 @@
865 it('must be able to include and show object links', function() {
866 var container = Y.Node.create('<div id="test">'),
867 logoNode = Y.Node.create('<div id="nav-brand-env"></div>'),
868- conn = new(Y.namespace('juju-tests.utils')).SocketStub(),
869- env = juju.newEnvironment({conn: conn}),
870- app = new Y.juju.App({env: env, container: container}),
871+ conn = new(Y.namespace('juju-tests.utils')).SocketStub();
872+ var env = juju.newEnvironment({conn: conn});
873+ env.connect();
874+ var app = new Y.juju.App({env: env, container: container}),
875 db = app.db,
876- mw = db.services.create({id: 'mediawiki',
877- name: 'mediawiki'}),
878+ mw = db.services.create({
879+ id: 'mediawiki',
880+ name: 'mediawiki',
881+ charm: 'cs:precise/mediawiki-2'}),
882 notifications = db.notifications,
883 view = new views.NotificationsOverview({
884 container: container,
885@@ -202,10 +205,10 @@
886 app: app,
887 env: env,
888 nsRouter: nsRouter}).render();
889- // we use overview here for testing as it defaults
890- // to showing all notices
891+ // We use overview here for testing as it defaults
892+ // to showing all notices.
893
894- // we can use app's routing table to derive a link
895+ // We can use app's routing table to derive a link.
896 notifications.create({title: 'Service Down',
897 message: 'Your service has an error',
898 link: app.getModelURL(mw)
899@@ -216,7 +219,6 @@
900 '/:gui:/service/mediawiki/');
901 link.getHTML().should.contain('View Details');
902
903-
904 // create a new notice passing the link_title
905 notifications.create({title: 'Service Down',
906 message: 'Your service has an error',
907@@ -241,6 +243,7 @@
908 container: container,
909 viewContainer: container
910 });
911+ env.connect();
912 var environment_delta = default_env;
913
914 var notifications = app.db.notifications,
915@@ -250,7 +253,6 @@
916 env: app.env,
917 nsRouter: nsRouter}).render();
918
919-
920 app.env.dispatch_result(environment_delta);
921
922
923@@ -274,41 +276,45 @@
924 it('must properly construct title and message based on level from ' +
925 'event data',
926 function() {
927- var container = Y.Node.create(
928- '<div id="test" class="container"></div>'),
929- app = new Y.juju.App({
930- container: container,
931- viewContainer: container
932- });
933- var environment_delta = {
934- 'result': [
935- ['service', 'add', {
936- 'charm': 'cs:precise/wordpress-6',
937- 'id': 'wordpress'
938- }],
939- ['service', 'add', {
940- 'charm': 'cs:precise/mediawiki-3',
941- 'id': 'mediawiki'
942- }],
943- ['service', 'add', {
944- 'charm': 'cs:precise/mysql-6',
945- 'id': 'mysql'
946- }],
947- ['unit', 'add', {
948- 'agent-state': 'install-error',
949- 'id': 'wordpress/0'
950- }],
951- ['unit', 'add', {
952- 'agent-state': 'error',
953- 'public-address': '192.168.122.222',
954- 'id': 'mysql/0'
955- }],
956- ['unit', 'add', {
957- 'public-address': '192.168.122.222',
958- 'id': 'mysql/2'
959- }]
960- ],
961- 'op': 'delta'
962+ var container = Y.Node.create(
963+ '<div id="test" class="container"></div>');
964+ var conn = new(Y.namespace('juju-tests.utils')).SocketStub();
965+ var env = juju.newEnvironment({conn: conn});
966+ env.connect();
967+ var app = new Y.juju.App({
968+ env: env,
969+ container: container,
970+ viewContainer: container
971+ });
972+ var environment_delta = {
973+ 'result': [
974+ ['service', 'add', {
975+ 'charm': 'cs:precise/wordpress-6',
976+ 'id': 'wordpress'
977+ }],
978+ ['service', 'add', {
979+ 'charm': 'cs:precise/mediawiki-3',
980+ 'id': 'mediawiki'
981+ }],
982+ ['service', 'add', {
983+ 'charm': 'cs:precise/mysql-6',
984+ 'id': 'mysql'
985+ }],
986+ ['unit', 'add', {
987+ 'agent-state': 'install-error',
988+ 'id': 'wordpress/0'
989+ }],
990+ ['unit', 'add', {
991+ 'agent-state': 'error',
992+ 'public-address': '192.168.122.222',
993+ 'id': 'mysql/0'
994+ }],
995+ ['unit', 'add', {
996+ 'public-address': '192.168.122.222',
997+ 'id': 'mysql/2'
998+ }]
999+ ],
1000+ 'op': 'delta'
1001 };
1002
1003 var notifications = app.db.notifications,

Subscribers

People subscribed via source and target branches