Merge lp:~jjo/juju-deployer/fix-juju-deployer-diff-for-multiple-relations-between-servicepair into lp:juju-deployer

Proposed by JuanJo Ciarlante
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
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.

To post a comment you must log in.
Revision history for this message
JuanJo Ciarlante (jjo) wrote :

- 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 ;)

Revision history for this message
JuanJo Ciarlante (jjo) wrote :

* tests ok:
Ran 106 tests in 61.608s
OK (SKIP=1)

* also verified against live 1501 openstack deploys

Revision history for this message
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.

Revision history for this message
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. :)

review: Needs Fixing
Revision history for this message
David Britton (dpb) wrote :

Some small things in the inline comments.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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+

Subscribers

People subscribed via source and target branches