Merge lp:~bac/juju-gui/get-endpoints into lp:juju-gui/experimental
- get-endpoints
- Merge into trunk
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 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju GUI Hackers | Pending | ||
Review via email: mp+156661@code.launchpad.net |
Commit message
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.
Brad Crittenden (bac) wrote : | # |
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:/
File app/models/
https:/
app/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:/
app/models/
camelCase?
https:/
app/models/
evt.currentTarg
commented out old code that should be removed?
https:/
app/models/
Is this required so that the linter doesn't complain about the vars
being re-declared below?
https:/
app/models/
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:/
File app/models/
https:/
app/models/
camelCase?
https:/
File app/views/
https:/
app/views/
models.
camelCase endpoints_map
https:/
File test/test_
https:/
test/test_
'cs:precise/
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.
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:/
File app/app.js (left):
https:/
app/app.js:478:
Nice that this removed so much code from App :)
https:/
File app/app.js (right):
https:/
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.
and controller.unbind can use the subscription object to remove
listeners.
https:/
File app/models/
https:/
app/models/
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:/
app/models/
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:/
app/models/
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:/
app/models/
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:/
File app/store/
https:/
app/store/
nice catch, thanks for pulling all these from the envs
https:/
File app/views/
https:/
app/views/
models.
as noted before, in causes like this we can't always be sure the db is
the one the listeners were bound to.
Jeff Pihach (hatch) wrote : | # |
LGTM with Ben's changes and those instances in the tests destroyed.
Thanks again, Great work!
Benjamin Saller (bcsaller) wrote : | # |
LGTM
as discussed online the changes I suggested will be pushed to another
branch (and I am fine with that).
- 472. By Brad Crittenden
-
Removed commented-out code
- 473. By Brad Crittenden
-
Clean up tests
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:/
File app/app.js (right):
https:/
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:/
File app/models/
https:/
app/models/
On 2013/04/02 19:56:43, jeff.pihach wrote:
> camelCase?
Done.
https:/
app/models/
No good reason. I'll clean that up tomorrow.
https:/
File app/models/
https:/
app/models/
On 2013/04/02 19:56:43, jeff.pihach wrote:
> camelCase?
Done.
https:/
File test/test_
https:/
test/test_
'cs:precise/
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.
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:/
Preview Diff
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, |
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: endpoints. js models. js env/fakebackend .js env/python. js env/sandbox. js environment. js topology/ relation. js browser_ charm_details. js endpoints. js env_python. js environment_ view.js notifications. js
A [revision details]
M app/app.js
M app/models/
M app/models/
M app/store/
M app/store/env/go.js
M app/store/
M app/store/
M app/views/
M app/views/
M test/test_app.js
M test/test_
M test/test_
M test/test_
M test/test_
M test/test_