Merge lp:~jjo/juju-deployer/fix-juju-deployer-diff-for-multiple-relations-between-servicepair into lp:juju-deployer
- fix-juju-deployer-diff-for-multiple-relations-between-servicepair
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 206 |
Proposed branch: | lp:~jjo/juju-deployer/fix-juju-deployer-diff-for-multiple-relations-between-servicepair |
Merge into: | lp:juju-deployer |
Diff against target: |
681 lines (+448/-85) 6 files modified
deployer/action/diff.py (+23/-12) deployer/env/mem.py (+106/-31) deployer/relation.py (+12/-6) deployer/tests/test_data/openstack/openstack-1501.yaml (+172/-0) deployer/tests/test_diff.py (+129/-33) setup.cfg (+6/-3) |
To merge this branch: | bzr merge lp:~jjo/juju-deployer/fix-juju-deployer-diff-for-multiple-relations-between-servicepair |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
David Britton (community) | Needs Fixing | ||
Review via email: mp+252271@code.launchpad.net |
Commit message
[jjo] diff: support multiple relations between service pair(s)
- use a "*" when found, then special-case Endpoint comparison to use it as a wildcard for relation name (ie only compare service names)
- fix env/mem.py to be able to load more complex cfgs, use openstack-1501.yaml
- add a couple of tests simulating an environment created from an edited config file (save original env YAML to tmp file, load it, edit dictionary, export to YAML, inject to ConfigStack via mock_open)
Because juju environment object doesn't contain the named relation originally used, diff support is not deterministic: e.g. if you have a service pair with 3 possible relations, with environment having foo:r1<->bar:r2 while config specifying foo:r2<->bar:r3, it'll show as nil diff -- IMO still better than voiding juju-deployer diff usage if there's any double-relation at environment.
Description of the change
JuanJo Ciarlante (jjo) wrote : | # |
JuanJo Ciarlante (jjo) wrote : | # |
* tests ok:
Ran 106 tests in 61.608s
OK (SKIP=1)
* also verified against live 1501 openstack deploys
Kapil Thangavelu (hazmat) wrote : | # |
looks good... i'm a bit concerned about this much logic in the fake env without just using an actual stub env impl. some of the bits here like * logic go away even just using the actual core status api vs the cli format of it as endpoints are always qualified. need a bit more time to work through fully.
David Britton (dpb) wrote : | # |
Hi jjo, please clean up the conflicts on this branch WRT trunk. I'm continuing the review as well so I don't make you wait another year. :)
David Britton (dpb) wrote : | # |
Some small things in the inline comments.
Preview Diff
1 | === modified file 'deployer/action/diff.py' |
2 | --- deployer/action/diff.py 2015-01-27 15:14:00 +0000 |
3 | +++ deployer/action/diff.py 2015-03-11 03:54:39 +0000 |
4 | @@ -28,9 +28,8 @@ |
5 | 'options'] = self.env.get_config(svc_name) |
6 | self.env_state['services'][svc_name][ |
7 | 'constraints'] = self.env.get_constraints(svc_name) |
8 | - self.env_state['services'][svc_name][ |
9 | - 'unit_count'] = len( |
10 | - self.env_status['services'][svc_name].get('units', {})) |
11 | + self.env_state['services'][svc_name]['unit_count'] = len( |
12 | + self.env_status['services'][svc_name].get('units', {})) |
13 | rels.update(self._load_rels(svc_name)) |
14 | self.env_state['relations'] = sorted(rels) |
15 | |
16 | @@ -59,8 +58,15 @@ |
17 | for r, eps in svc_rels.items(): |
18 | if src in eps: |
19 | if found: |
20 | - raise ValueError("Ambigious relations for service") |
21 | - found = r |
22 | + self.deployment.log.warning( |
23 | + "Service: %s will show ambiguous relations to %s" |
24 | + % (src, tgt)) |
25 | + # use "*" to note the ambiguity - will be specially |
26 | + # processed by Endpoint comparisons as a kind of |
27 | + # ~wildcard |
28 | + found = "*" |
29 | + else: |
30 | + found = r |
31 | return found |
32 | |
33 | def get_delta(self): |
34 | @@ -130,15 +136,19 @@ |
35 | if d_sc != e_s['constraints']: |
36 | mod['env-constraints'] = e_s['constraints'] |
37 | mod['cfg-constraints'] = d_sc |
38 | - for k, v in d_s.get('options', {}).items(): |
39 | - # Deploy options not known to the env may originate |
40 | - # from charm version delta or be an invalid config. |
41 | - if not k in e_s['options']: |
42 | - continue |
43 | + all_options = set(d_s.get('options', {}).keys() + |
44 | + e_s.get('options', {}).keys()) |
45 | + for k in all_options: |
46 | + # deploy cfg value |
47 | + d_v = d_s.get('options', {}).get(k) |
48 | + # if unset at config, use charm's default |
49 | + if d_v is None: |
50 | + d_v = charm.config.get(k, {}).get('default', None) |
51 | + # environment cfg value |
52 | e_v = e_s['options'].get(k, {}).get('value') |
53 | - if e_v != v: |
54 | + if e_v != d_v: |
55 | mod.setdefault('env-config', {}).update({k: e_v}) |
56 | - mod.setdefault('cfg-config', {}).update({k: v}) |
57 | + mod.setdefault('cfg-config', {}).update({k: d_v}) |
58 | if not charm or not charm.is_subordinate(): |
59 | if e_s['unit_count'] != d_s.get('num_units', 1): |
60 | mod['num_units'] = e_s['unit_count'] - d_s.get('num_units', 1) |
61 | @@ -146,6 +156,7 @@ |
62 | |
63 | def do_diff(self): |
64 | self.start_time = time.time() |
65 | + self.deployment.fetch_charms() |
66 | self.deployment.resolve() |
67 | self.env.connect() |
68 | self.env_status = self.env.status() |
69 | |
70 | === modified file 'deployer/env/mem.py' |
71 | --- deployer/env/mem.py 2014-04-21 22:49:05 +0000 |
72 | +++ deployer/env/mem.py 2015-03-11 03:54:39 +0000 |
73 | @@ -48,15 +48,52 @@ |
74 | raise EnvError("Invalid service name") |
75 | return self._services[svc_name] |
76 | |
77 | - def add_relation(self, endpoint_a, endpoint_b): |
78 | - """Add relations |
79 | - """ |
80 | + def _remove_relation(self, s_from, r_from, s_to): |
81 | + """Low level _remove_relation |
82 | + """ |
83 | + rels = self._relations.get(s_from, {}) |
84 | + related_svcs = rels.get(r_from, []) |
85 | + if s_to in related_svcs: |
86 | + related_svcs.remove(s_to) |
87 | + |
88 | + def remove_relation(self, src_ep, dst_ep): |
89 | + """Remove relation, eps are "service_name:rel_name" |
90 | + """ |
91 | + assert ":" in src_ep and ":" in dst_ep |
92 | + (svc0, rel0, svc1, rel1) = src_ep.split(':') + dst_ep.split(':') |
93 | + for (s_from, r_from, s_to) in ((svc0, rel0, svc1), |
94 | + (svc1, rel1, svc0)): |
95 | + self._remove_relation(s_from, r_from, s_to) |
96 | + |
97 | + def _remove_service_relations(self, svc): |
98 | + self._relations.pop(svc, None) |
99 | + for _, rels in self._relations.items(): |
100 | + for _, dst_svcs in rels.items(): |
101 | + while svc in dst_svcs: |
102 | + dst_svcs.remove(svc) |
103 | + |
104 | + def _add_relation(self, s_from, r_from, s_to): |
105 | + """Low level _add_relation (only one-way) |
106 | + """ |
107 | + src_rels = self._relations.setdefault(s_from, {}) |
108 | + src_rels.setdefault(r_from, []) |
109 | + src_rels[r_from].append(s_to) |
110 | + |
111 | + def add_relation(self, src_ep, dst_ep): |
112 | + """Add relation, eps are "service_name:rel_name" |
113 | + """ |
114 | + assert ":" in src_ep and ":" in dst_ep |
115 | + (svc0, rel0, svc1, rel1) = src_ep.split(':') + dst_ep.split(':') |
116 | + for (s_from, r_from, s_to) in ((svc0, rel0, svc1), |
117 | + (svc1, rel1, svc0)): |
118 | + self._add_relation(s_from, r_from, s_to) |
119 | |
120 | def destroy_service(self, svc_name): |
121 | """ Destroy a service |
122 | """ |
123 | if not svc_name in self._services: |
124 | raise EnvError("Invalid service name") |
125 | + self._remove_service_relations(svc_name) |
126 | del self._services[svc_name] |
127 | |
128 | def close(self): |
129 | @@ -129,40 +166,78 @@ |
130 | self.set_config(svc_name, service.config) |
131 | self.set_constraints(svc_name, service.constraints) |
132 | |
133 | + def _get_interfaces_namesets(self, svc, req_prov): |
134 | + charm_rels = {} |
135 | + try: |
136 | + if req_prov: |
137 | + charm_interfaces = self._deployment.get_charm_for( |
138 | + svc).get_requires() |
139 | + else: |
140 | + charm_interfaces = self._deployment.get_charm_for( |
141 | + svc).get_provides() |
142 | + except KeyError: |
143 | + return {} |
144 | + for interfaces in charm_interfaces.values(): |
145 | + for interface in interfaces: |
146 | + iface = interface.get('interface') |
147 | + names = charm_rels.setdefault(iface, set()) |
148 | + names.add(interface.get('name')) |
149 | + return charm_rels |
150 | + |
151 | def _compile_relations(self): |
152 | """ Compile a relation dictionary by svc_name, with |
153 | their values structured for status() return |
154 | """ |
155 | for rel in self._deployment.get_relations(): |
156 | + # src, dst are svcname[:if_name], e.g.: |
157 | + # "rabbitmq" or "rabbitmq:amqp" |
158 | for src, dst in (rel[0], rel[1]), (rel[1], rel[0]): |
159 | - try: |
160 | - src_requires = self._deployment.get_charm_for( |
161 | - src).get_requires() |
162 | - dst_provides = self._deployment.get_charm_for( |
163 | - dst).get_provides() |
164 | - except KeyError: |
165 | - continue |
166 | - # Create dicts key-ed by: |
167 | - # { interface_type: (interface_name, svc_name)} |
168 | - src_dict = {} |
169 | - dst_dict = {} |
170 | - # {rel_name: [{interface: name }...]} |
171 | - for _, interfaces in src_requires.items(): |
172 | - for interface in interfaces: |
173 | - src_dict[interface.get('interface')] = ( |
174 | - interface.get('name'), src) |
175 | - for _, interfaces in dst_provides.items(): |
176 | - for interface in interfaces: |
177 | - dst_dict[interface.get('interface')] = ( |
178 | - interface.get('name'), dst) |
179 | - # Create juju env relation entries as: |
180 | - # {svc_name: { interface_name: [ svc_name2, ...] }, ...} |
181 | - for src_rel, (if_name, src_svc_name) in src_dict.items(): |
182 | - if src_rel in dst_dict: |
183 | - src_rels = self._relations.setdefault(src_svc_name, {}) |
184 | - src_rels.setdefault(if_name, []) |
185 | - dst_svc_name = dst_dict[src_rel][1] |
186 | - src_rels[if_name].append(dst_svc_name) |
187 | + # hack to get src_ifname, dst_ifname set to '' |
188 | + # if string doesn't have ':' |
189 | + src_svc, src_ifname = (src + ':').split(':')[0:2] |
190 | + dst_svc, dst_ifname = (dst + ':').split(':')[0:2] |
191 | + |
192 | + # Get charm_rels dicts keyed by interface, value |
193 | + # is a set with all possible if_names: |
194 | + # src_charm_rels: |
195 | + # { "rabbitmq": set("amqp") } |
196 | + # dst_charm_rels: |
197 | + # { "rabbitmq": set("amqp", "amqp-nova") } |
198 | + src_charm_rels = self._get_interfaces_namesets(src_svc, 0) |
199 | + dst_charm_rels = self._get_interfaces_namesets(dst_svc, 1) |
200 | + |
201 | + # Join src_charm_rels, dst_charm_rels by their keys (interface) |
202 | + for src_charm_if, src_charm_ifnames in src_charm_rels.items(): |
203 | + dst_charm_ifnames = dst_charm_rels.get(src_charm_if) |
204 | + # if interface found in dst_charm_rels: |
205 | + if dst_charm_ifnames: |
206 | + # explicit svc_name:ifname, e.g.: "rabbitmq:amqp" |
207 | + if src_ifname: |
208 | + if not src_ifname in src_charm_ifnames: |
209 | + assert ('interface name {}' |
210 | + 'not found for service {}').format( |
211 | + src_ifname, src_svc) |
212 | + else: |
213 | + src_ifnames = list(src_charm_ifnames) |
214 | + assert len(src_ifnames) == 1, ( |
215 | + "{}<->{} relation needs explicit " |
216 | + "{}:NAME, with NAME from: {}".format( |
217 | + src, dst, src_ifnames)) |
218 | + src_ifname = src_ifnames[0] |
219 | + if dst_ifname: |
220 | + if not dst_ifname in dst_charm_ifnames: |
221 | + assert ('interface name {}' |
222 | + 'not found for service {}').format( |
223 | + dst_ifname, dst_svc) |
224 | + else: |
225 | + dst_ifnames = list(dst_charm_ifnames) |
226 | + assert len(dst_ifnames) == 1, ( |
227 | + "{}<->{} relation needs explicit " |
228 | + "{}:NAME, with NAME from: {}".format( |
229 | + dst, src, dst_ifnames)) |
230 | + dst_ifname = dst_ifnames[0] |
231 | + self._add_relation(src_svc, src_ifname, dst_svc) |
232 | + self._add_relation(dst_svc, dst_ifname, src_svc) |
233 | |
234 | def status(self): |
235 | """ Return all services status """ |
236 | |
237 | === modified file 'deployer/relation.py' |
238 | --- deployer/relation.py 2013-07-22 15:29:31 +0000 |
239 | +++ deployer/relation.py 2015-03-11 03:54:39 +0000 |
240 | @@ -11,6 +11,14 @@ |
241 | else: |
242 | self.service = ep |
243 | |
244 | + def __eq__(self, ep): |
245 | + # exactly equal by full endpoint (service:name), or |
246 | + # equal only by service if name is "*" or unset |
247 | + return ((self.ep == ep.ep) |
248 | + or |
249 | + ((not self.name or self.name == "*" or |
250 | + not ep.name or ep.name == "*") |
251 | + and self.service == ep.service)) |
252 | |
253 | class EndpointPair(object): |
254 | # Really simple endpoint service matching that does not work for multiple |
255 | @@ -23,14 +31,12 @@ |
256 | def __eq__(self, ep_pair): |
257 | if not isinstance(ep_pair, EndpointPair): |
258 | return False |
259 | - return (ep_pair.ep_x.service in self |
260 | + return (ep_pair.ep_x in self |
261 | and |
262 | - ep_pair.ep_y.service in self) |
263 | + ep_pair.ep_y in self) |
264 | |
265 | - def __contains__(self, svc_name): |
266 | - return (svc_name == self.ep_x.service |
267 | - or |
268 | - svc_name == self.ep_y.service) |
269 | + def __contains__(self, ep): |
270 | + return (self.ep_x == ep or self.ep_y == ep) |
271 | |
272 | def __hash__(self): |
273 | return hash(tuple(sorted( |
274 | |
275 | === added file 'deployer/tests/test_data/openstack/openstack-1501.yaml' |
276 | --- deployer/tests/test_data/openstack/openstack-1501.yaml 1970-01-01 00:00:00 +0000 |
277 | +++ deployer/tests/test_data/openstack/openstack-1501.yaml 2015-03-11 03:54:39 +0000 |
278 | @@ -0,0 +1,172 @@ |
279 | +# based on https://api.jujucharms.com/v4/bundle/openstack-telemetry-30/archive/bundle.yaml |
280 | +openstack: |
281 | + series: trusty |
282 | + overrides: |
283 | + openstack-origin: cloud:trusty-icehouse |
284 | + source: cloud:trusty-updates/icehouse |
285 | + |
286 | + services: |
287 | + ceilometer: |
288 | + charm: cs:trusty/ceilometer |
289 | + num_units: 1 |
290 | + ceilometer-agent: |
291 | + charm: cs:trusty/ceilometer-agent |
292 | + num_units: 0 |
293 | + ceph: |
294 | + charm: cs:trusty/ceph |
295 | + num_units: 3 |
296 | + options: |
297 | + fsid: 5a791d94-980b-11e4-b6f6-3c970e8b1cf7 |
298 | + monitor-secret: AQAi5a9UeJXUExAA+By9u+GPhl8/XiUQ4nwI3A== |
299 | + osd-devices: /dev/sdb |
300 | + osd-reformat: "yes" |
301 | + ceph-osd: |
302 | + charm: cs:trusty/ceph-osd |
303 | + num_units: 1 |
304 | + options: |
305 | + osd-devices: /dev/sdb |
306 | + osd-reformat: "yes" |
307 | + ceph-radosgw: |
308 | + charm: cs:trusty/ceph-radosgw |
309 | + num_units: 1 |
310 | + options: |
311 | + use-embedded-webserver: true |
312 | + cinder: |
313 | + charm: cs:trusty/cinder |
314 | + num_units: 1 |
315 | + options: |
316 | + block-device: None |
317 | + glance-api-version: 2 |
318 | + cinder-ceph: |
319 | + charm: cs:trusty/cinder-ceph |
320 | + num_units: 0 |
321 | + glance: |
322 | + charm: cs:trusty/glance |
323 | + num_units: 1 |
324 | + keystone: |
325 | + charm: cs:trusty/keystone |
326 | + num_units: 1 |
327 | + options: |
328 | + admin-password: openstack |
329 | + mongodb: |
330 | + charm: cs:trusty/mongodb |
331 | + num_units: 1 |
332 | + mysql: |
333 | + charm: cs:trusty/mysql |
334 | + num_units: 1 |
335 | + options: |
336 | + max-connections: 20000 |
337 | + neutron-api: |
338 | + charm: cs:trusty/neutron-api |
339 | + num_units: 1 |
340 | + options: |
341 | + neutron-security-groups: true |
342 | + neutron-gateway: |
343 | + charm: cs:trusty/quantum-gateway |
344 | + num_units: 1 |
345 | + options: |
346 | + ext-port: eth1 |
347 | + neutron-openvswitch: |
348 | + charm: cs:trusty/neutron-openvswitch |
349 | + num_units: 0 |
350 | + nova-cloud-controller: |
351 | + charm: cs:trusty/nova-cloud-controller |
352 | + num_units: 1 |
353 | + options: |
354 | + network-manager: Neutron |
355 | + quantum-security-groups: "yes" |
356 | + nova-compute: |
357 | + charm: cs:trusty/nova-compute |
358 | + num_units: 3 |
359 | + options: |
360 | + enable-live-migration: true |
361 | + enable-resize: true |
362 | + migration-auth-type: ssh |
363 | + openstack-dashboard: |
364 | + charm: cs:trusty/openstack-dashboard |
365 | + num_units: 1 |
366 | + rabbitmq-server: |
367 | + charm: cs:trusty/rabbitmq-server |
368 | + num_units: 1 |
369 | + series: trusty |
370 | + relations: |
371 | + - - nova-compute:amqp |
372 | + - rabbitmq-server:amqp |
373 | + - - neutron-gateway:amqp |
374 | + - rabbitmq-server:amqp |
375 | + - - keystone:shared-db |
376 | + - mysql:shared-db |
377 | + - - nova-cloud-controller:identity-service |
378 | + - keystone:identity-service |
379 | + - - glance:identity-service |
380 | + - keystone:identity-service |
381 | + - - neutron-api:identity-service |
382 | + - keystone:identity-service |
383 | + - - neutron-openvswitch:neutron-plugin-api |
384 | + - neutron-api:neutron-plugin-api |
385 | + - - neutron-api:shared-db |
386 | + - mysql:shared-db |
387 | + - - neutron-api:amqp |
388 | + - rabbitmq-server:amqp |
389 | + - - neutron-gateway:neutron-plugin-api |
390 | + - neutron-api:neutron-plugin-api |
391 | + - - glance:shared-db |
392 | + - mysql:shared-db |
393 | + - - glance:amqp |
394 | + - rabbitmq-server:amqp |
395 | + - - nova-cloud-controller:image-service |
396 | + - glance:image-service |
397 | + - - nova-compute:image-service |
398 | + - glance:image-service |
399 | + - - nova-cloud-controller:cloud-compute |
400 | + - nova-compute:cloud-compute |
401 | + - - nova-cloud-controller:amqp |
402 | + - rabbitmq-server:amqp |
403 | + - - nova-cloud-controller:quantum-network-service |
404 | + - neutron-gateway:quantum-network-service |
405 | + - - nova-compute:neutron-plugin |
406 | + - neutron-openvswitch:neutron-plugin |
407 | + - - neutron-openvswitch:amqp |
408 | + - rabbitmq-server:amqp |
409 | + - - openstack-dashboard:identity-service |
410 | + - keystone:identity-service |
411 | + - - nova-cloud-controller:shared-db |
412 | + - mysql:shared-db |
413 | + - - nova-cloud-controller:neutron-api |
414 | + - neutron-api:neutron-api |
415 | + - - cinder:image-service |
416 | + - glance:image-service |
417 | + - - cinder:amqp |
418 | + - rabbitmq-server:amqp |
419 | + - - cinder:identity-service |
420 | + - keystone:identity-service |
421 | + - - cinder:cinder-volume-service |
422 | + - nova-cloud-controller:cinder-volume-service |
423 | + - - cinder-ceph:storage-backend |
424 | + - cinder:storage-backend |
425 | + - - ceph:client |
426 | + - nova-compute:ceph |
427 | + - - cinder:shared-db |
428 | + - mysql:shared-db |
429 | + - - ceph:client |
430 | + - cinder-ceph:ceph |
431 | + - - ceph:client |
432 | + - glance:ceph |
433 | + - - ceph-osd:mon |
434 | + - ceph:osd |
435 | + - - ceph-radosgw:mon |
436 | + - ceph:radosgw |
437 | + - - ceph-radosgw:identity-service |
438 | + - keystone:identity-service |
439 | + - - ceilometer:amqp |
440 | + - rabbitmq-server:amqp |
441 | + - - ceilometer-agent:ceilometer-service |
442 | + - ceilometer:ceilometer-service |
443 | + - - ceilometer:identity-service |
444 | + - keystone:identity-service |
445 | + - - ceilometer:identity-notifications |
446 | + - keystone:identity-notifications |
447 | + - - ceilometer-agent:nova-ceilometer |
448 | + - nova-compute:nova-ceilometer |
449 | + - - ceilometer:shared-db |
450 | + - mongodb:database |
451 | |
452 | === modified file 'deployer/tests/test_diff.py' |
453 | --- deployer/tests/test_diff.py 2014-01-23 12:40:57 +0000 |
454 | +++ deployer/tests/test_diff.py 2015-03-11 03:54:39 +0000 |
455 | @@ -5,6 +5,8 @@ |
456 | import unittest |
457 | import shutil |
458 | import tempfile |
459 | +import yaml |
460 | +from deployer.tests.mock import patch, mock_open |
461 | from deployer.env.mem import MemoryEnvironment |
462 | from deployer.config import ConfigStack |
463 | from deployer.utils import setup_logging |
464 | @@ -27,9 +29,11 @@ |
465 | def setUpClass(cls): |
466 | deployment = ConfigStack( |
467 | [os.path.join( |
468 | - cls.test_data_dir, "blog.yaml")]).get('wordpress-prod') |
469 | + cls.test_data_dir, |
470 | + "openstack", |
471 | + "openstack-1501.yaml")]).get('openstack') |
472 | cls._dir = tempfile.mkdtemp() |
473 | - os.mkdir(os.path.join(cls._dir, "precise")) |
474 | + os.mkdir(os.path.join(cls._dir, "trusty")) |
475 | deployment.repo_path = cls._dir |
476 | deployment.fetch_charms() |
477 | deployment.resolve() |
478 | @@ -40,6 +44,12 @@ |
479 | shutil.rmtree(cls._dir) |
480 | |
481 | @classmethod |
482 | + def get_dir(cls): |
483 | + """ Return temp working dir |
484 | + """ |
485 | + return cls._dir |
486 | + |
487 | + @classmethod |
488 | def get_deployment(cls): |
489 | """ Return saved deployment at class initialization |
490 | """ |
491 | @@ -56,54 +66,140 @@ |
492 | # Removing 1 unit must show -1 'num_units' |
493 | dpl = self.get_deployment() |
494 | env = MemoryEnvironment(dpl.name, dpl) |
495 | - env.remove_unit(env.status()['services']['haproxy']['units'][0]) |
496 | + env.remove_unit(env.status()['services']['nova-compute']['units'][0]) |
497 | diff = Diff(env, dpl, {}).do_diff() |
498 | self.assertEqual( |
499 | - diff['services']['modified']['haproxy']['num_units'], -1) |
500 | + diff['services']['modified']['nova-compute']['num_units'], -1) |
501 | # re-adding a unit -> nil diff |
502 | - env.add_units('haproxy', 1) |
503 | + env.add_units('nova-compute', 1) |
504 | diff = Diff(env, dpl, {}).do_diff() |
505 | self.assertEqual(diff, {}) |
506 | |
507 | def test_diff_config(self): |
508 | dpl = self.get_deployment() |
509 | env = MemoryEnvironment(dpl.name, dpl) |
510 | - env.set_config('blog', {'tuning': 'bare'}) |
511 | + env.set_config('keystone', {'admin-password': 'NOTAPASS'}) |
512 | diff = Diff(env, dpl, {}).do_diff() |
513 | - mod_blog = diff['services']['modified']['blog'] |
514 | - self.assertTrue(mod_blog['env-config']['tuning'] != |
515 | - mod_blog['cfg-config']['tuning']) |
516 | - self.assertEquals(mod_blog['env-config']['tuning'], 'bare') |
517 | + mod_svc = diff['services']['modified']['keystone'] |
518 | + self.assertTrue(mod_svc['env-config']['admin-password'] != |
519 | + mod_svc['cfg-config']['admin-password']) |
520 | + self.assertEquals(mod_svc['env-config']['admin-password'], |
521 | + 'NOTAPASS') |
522 | |
523 | def test_diff_config_many(self): |
524 | dpl = self.get_deployment() |
525 | env = MemoryEnvironment(dpl.name, dpl) |
526 | - env.set_config('blog', {'tuning': 'bare', 'engine': 'duck'}) |
527 | + env.set_config('keystone', {'admin-password': 'NOTAPASS', |
528 | + 'admin-token': 'NOTATOKEN'}) |
529 | diff = Diff(env, dpl, {}).do_diff() |
530 | - mod_blog = diff['services']['modified']['blog'] |
531 | + mod_svc = diff['services']['modified']['keystone'] |
532 | self.assertEqual( |
533 | - set(mod_blog['env-config'].keys()), |
534 | - set(['tuning', 'engine'])) |
535 | - self.assertTrue(mod_blog['env-config']['tuning'] != |
536 | - mod_blog['cfg-config']['tuning']) |
537 | - self.assertTrue(mod_blog['env-config']['engine'] != |
538 | - mod_blog['cfg-config']['engine']) |
539 | + set(mod_svc['env-config'].keys()), |
540 | + set(['admin-password', 'admin-token'])) |
541 | + self.assertTrue(mod_svc['env-config']['admin-password'] != |
542 | + mod_svc['cfg-config']['admin-password']) |
543 | + self.assertTrue(mod_svc['env-config']['admin-token'] != |
544 | + mod_svc['cfg-config']['admin-token']) |
545 | |
546 | def test_diff_constraints(self): |
547 | dpl = self.get_deployment() |
548 | env = MemoryEnvironment(dpl.name, dpl) |
549 | - env.set_constraints('haproxy', 'foo=bar') |
550 | - diff = Diff(env, dpl, {}).do_diff() |
551 | - mod_haproxy = diff['services']['modified']['haproxy'] |
552 | - self.assertTrue( |
553 | - mod_haproxy['env-constraints'] != mod_haproxy['cfg-constraints']) |
554 | - self.assertEqual(mod_haproxy['env-constraints'], {'foo': 'bar'}) |
555 | - |
556 | - def test_diff_service_destroy(self): |
557 | - dpl = self.get_deployment() |
558 | - env = MemoryEnvironment(dpl.name, dpl) |
559 | - env.destroy_service('haproxy') |
560 | - diff = Diff(env, dpl, {}).do_diff() |
561 | - self.assertTrue(str(diff['relations']['missing'][0]).find('haproxy') |
562 | - != -1) |
563 | - self.assertTrue(diff['services']['missing'].keys() == ['haproxy']) |
564 | + env.set_constraints('nova-compute', 'foo=bar') |
565 | + diff = Diff(env, dpl, {}).do_diff() |
566 | + mod_svc = diff['services']['modified']['nova-compute'] |
567 | + self.assertTrue( |
568 | + mod_svc['env-constraints'] != mod_svc['cfg-constraints']) |
569 | + self.assertEqual(mod_svc['env-constraints'], {'foo': 'bar'}) |
570 | + |
571 | + def test_diff_env_remove_relation(self): |
572 | + dpl = self.get_deployment() |
573 | + env = MemoryEnvironment(dpl.name, dpl) |
574 | + env.remove_relation('cinder:amqp', 'rabbitmq-server:amqp') |
575 | + diff = Diff(env, dpl, {}).do_diff() |
576 | + self.assertEqual(str(diff['relations']['missing']), |
577 | + '[cinder:amqp <-> rabbitmq-server:amqp]') |
578 | + |
579 | + def test_diff_env_service_destroy(self): |
580 | + dpl = self.get_deployment() |
581 | + env = MemoryEnvironment(dpl.name, dpl) |
582 | + env.destroy_service('nova-compute') |
583 | + diff = Diff(env, dpl, {}).do_diff() |
584 | + self.assertTrue( |
585 | + str(diff['relations']['missing'][0]).find('nova-compute') != -1) |
586 | + self.assertTrue(diff['services']['missing'].keys() == ['nova-compute']) |
587 | + |
588 | + def test_diff_cfg_remove_relation(self): |
589 | + dpl = self.get_deployment() |
590 | + # need a tmp file to sneak and save deployment YAML |
591 | + edited_config_file = os.path.join(self.get_dir(), 'saved.yaml') |
592 | + dpl.save(edited_config_file) |
593 | + # in memory edit yaml content |
594 | + edited_config = yaml.load(open(edited_config_file)) |
595 | + edited_config["relations"].remove( |
596 | + ['nova-compute:amqp', 'rabbitmq-server:amqp']) |
597 | + edited_config = yaml.dump({'openstack': edited_config}) |
598 | + |
599 | + # mock open to inyect edited_config YAML to ConfigStack |
600 | + with patch('deployer.config.open', mock_open(read_data=edited_config), |
601 | + create=True): |
602 | + edited_dpl = ConfigStack("mocked").get('openstack') |
603 | + |
604 | + edited_dpl.repo_path = self.get_dir() |
605 | + edited_dpl.fetch_charms() |
606 | + edited_dpl.resolve() |
607 | + env = MemoryEnvironment(dpl.name, dpl) |
608 | + diff = Diff(env, edited_dpl, {}).do_diff() |
609 | + self.assertTrue( |
610 | + diff['relations']['unknown'], |
611 | + '[nova-compute:amqp <-> rabbitmq-server:amqp]') |
612 | + |
613 | + def test_diff_cfg_remove_ambiguous_relation(self): |
614 | + dpl = self.get_deployment() |
615 | + # need a tmp file to sneak and save deployment YAML |
616 | + edited_config_file = os.path.join(self.get_dir(), 'saved.yaml') |
617 | + dpl.save(edited_config_file) |
618 | + # in memory edit yaml content |
619 | + edited_config = yaml.load(open(edited_config_file)) |
620 | + edited_config["relations"].remove( |
621 | + ['ceilometer:identity-service', 'keystone:identity-service']) |
622 | + edited_config = yaml.dump({'openstack': edited_config}) |
623 | + |
624 | + # mock open to inyect edited_config YAML to ConfigStack |
625 | + with patch('deployer.config.open', mock_open(read_data=edited_config), |
626 | + create=True): |
627 | + edited_dpl = ConfigStack("mocked").get('openstack') |
628 | + |
629 | + edited_dpl.repo_path = self.get_dir() |
630 | + edited_dpl.fetch_charms() |
631 | + edited_dpl.resolve() |
632 | + env = MemoryEnvironment(dpl.name, dpl) |
633 | + diff = Diff(env, edited_dpl, {}).do_diff() |
634 | + # verify ceilometer<->keystone present in unknown relations |
635 | + self.assertTrue('ceilometer' in str(diff['relations']['unknown']) |
636 | + and |
637 | + 'keystone' in str(diff['relations']['unknown'])) |
638 | + |
639 | + def test_diff_cfg_replace_with_unnamed_relations(self): |
640 | + dpl = self.get_deployment() |
641 | + # need a tmp file to sneak and save deployment YAML |
642 | + edited_config_file = os.path.join(self.get_dir(), 'saved.yaml') |
643 | + dpl.save(edited_config_file) |
644 | + # in memory edit yaml content |
645 | + edited_config = yaml.load(open(edited_config_file)) |
646 | + # remove ':name' from service endpoint specification |
647 | + for relation in edited_config["relations"]: |
648 | + relation = [(relation[0] + ":").split(":")[0], |
649 | + (relation[1] + ":").split(":")[0]] |
650 | + edited_config = yaml.dump({'openstack': edited_config}) |
651 | + |
652 | + # mock open to inyect edited_config YAML to ConfigStack |
653 | + with patch('deployer.config.open', mock_open(read_data=edited_config), |
654 | + create=True): |
655 | + edited_dpl = ConfigStack("mocked").get('openstack') |
656 | + |
657 | + edited_dpl.repo_path = self.get_dir() |
658 | + edited_dpl.fetch_charms() |
659 | + edited_dpl.resolve() |
660 | + env = MemoryEnvironment(dpl.name, dpl) |
661 | + diff = Diff(env, edited_dpl, {}).do_diff() |
662 | + self.assertEqual(diff, {}) |
663 | |
664 | === modified file 'setup.cfg' |
665 | --- setup.cfg 2013-05-16 03:21:04 +0000 |
666 | +++ setup.cfg 2015-03-11 03:54:39 +0000 |
667 | @@ -1,7 +1,10 @@ |
668 | [build_sphinx] |
669 | source-dir = doc/ |
670 | -build-dir = doc/_build |
671 | -all_files = 1 |
672 | +build-dir = doc/_build |
673 | +all_files = 1 |
674 | |
675 | [upload_sphinx] |
676 | -upload-dir = doc/_build/html |
677 | \ No newline at end of file |
678 | +upload-dir = doc/_build/html |
679 | + |
680 | +[easy_install] |
681 | + |
- WIP, fixing tests to:
- env/mem.py: be able to load openstack-1501.yaml into
'mem'(testing) env
- test_diff.py: use 'openstack' instead of 'blog'
- could reproduce "ValueError: Ambigious relations for
service" at tests (now I need to fix it ;)