Merge lp:~frankban/juju-deployer/unit-placement-fix into lp:juju-deployer

Proposed by Francesco Banconi
Status: Merged
Merged at revision: 152
Proposed branch: lp:~frankban/juju-deployer/unit-placement-fix
Merge into: lp:juju-deployer
Diff against target: 593 lines (+156/-107)
11 files modified
deployer/action/importer.py (+15/-24)
deployer/config.py (+5/-5)
deployer/deployment.py (+3/-3)
deployer/service.py (+46/-45)
deployer/tests/base.py (+36/-0)
deployer/tests/test_data/v4/container-existing-machine.yaml (+1/-1)
deployer/tests/test_data/v4/placement-invalid-number.yaml (+8/-0)
deployer/tests/test_deployment.py (+23/-10)
deployer/tests/test_guiserver.py (+7/-3)
deployer/tests/test_importer.py (+11/-15)
setup.py (+1/-1)
To merge this branch: bzr merge lp:~frankban/juju-deployer/unit-placement-fix
Reviewer Review Type Date Requested Status
Madison Scott-Clary (community) code and qa Approve
Tim Van Steenburgh (community) Approve
Brad Crittenden (community) code Approve
Review via email: mp+267187@code.launchpad.net

Description of the change

Fix deployments of bundles v4.

Fix some incorrect behavior of new v4 bundles, mostly
concerning validation and ordering of services.

Improve status retrieval while adding unit.

Fix checking for existing machines: it did work
properly only with top level machines.

Fix a problem with machines mapping while
co-locating units to other services, units or
to declared machines.

Improve the logic used for detecting v4 vs v3
bundle syntax: the machines section in v4 can
be safely omitted accotding to the spec.

Fix sleep loops and memory leaks while running
tests exercising the importer.

Some lint/code style clean up.

Bump version up a little.

To post a comment you must log in.
Revision history for this message
Tim Van Steenburgh (tvansteenburgh) wrote :

One inline comment, plus I wonder if you could point out which change fixes the test memory leak problem? Otherwise looks good.

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

Thanks for this fix Francesco. It LGTM.

review: Approve (code)
Revision history for this message
Francesco Banconi (frankban) wrote :

As mentioned on IRC, the sleep loops are solved by setting up the wnv mock with patch_env_status.
I also removed the reloading logic, as requested.
Thank you both for the reviews!

159. By Francesco Banconi

Remove the reloading logic in importer.add_unit.

Revision history for this message
Tim Van Steenburgh (tvansteenburgh) wrote :

LGTM!

review: Approve
Revision history for this message
Madison Scott-Clary (makyo) wrote :

Thanks Francesco, much appreciated, this LGTM. QA okay on local.

review: Approve (code and qa)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'deployer/action/importer.py'
--- deployer/action/importer.py 2015-08-04 12:50:12 +0000
+++ deployer/action/importer.py 2015-08-06 14:43:20 +0000
@@ -14,13 +14,11 @@
14 self.options = options14 self.options = options
15 self.env = env15 self.env = env
16 self.deployment = deployment16 self.deployment = deployment
17 self.machines = {}
1817
19 def add_units(self):18 def add_units(self):
20 self.log.debug("Adding units...")19 self.log.debug("Adding units...")
21 # Add units to existing services that don't match count.20 # Add units to existing services that don't match count.
22 env_status = self.env.status()21 env_status = self.env.status()
23 reloaded = False
2422
25 # Workaround an issue where watch output doesn't include subordinate23 # Workaround an issue where watch output doesn't include subordinate
26 # services right away, and add_unit would fail while attempting to add24 # services right away, and add_unit would fail while attempting to add
@@ -56,18 +54,9 @@
56 self.log.info(54 self.log.info(
57 "Adding %d more units to %s" % (abs(delta), svc.name))55 "Adding %d more units to %s" % (abs(delta), svc.name))
58 if svc.unit_placement:56 if svc.unit_placement:
59 # Reload status once after non placed services units are done.57 # Reload status before each placed unit is deployed, so that
60 if reloaded is False:58 # co-location to other units can take place properly.
61 # Improved crappy workaround juju-core api inconsistency59 env_status = self.env.status()
62 delay = time.time() + 60
63 while delay > time.time():
64 time.sleep(5.1)
65 env_status = self.env.status()
66 if svc.name in env_status['services']:
67 break
68
69 reloaded = True
70
71 placement = self.deployment.get_unit_placement(svc, env_status)60 placement = self.deployment.get_unit_placement(svc, env_status)
72 for mid in range(cur_units, svc.num_units):61 for mid in range(cur_units, svc.num_units):
73 self.env.add_unit(svc.name, placement.get(mid))62 self.env.add_unit(svc.name, placement.get(mid))
@@ -76,8 +65,7 @@
7665
77 def machine_exists(self, id):66 def machine_exists(self, id):
78 """Checks if the given id exists on the current environment."""67 """Checks if the given id exists on the current environment."""
79 return int(id) in map(int,68 return str(id) in map(str, self.env.status().get('machines', {}))
80 self.env.status().get('machines', {}).keys())
8169
82 def create_machines(self):70 def create_machines(self):
83 """Create machines as specified in the machine spec in the bundle.71 """Create machines as specified in the machine spec in the bundle.
@@ -104,19 +92,23 @@
104 creation is skipped.92 creation is skipped.
105 """93 """
106 machines = self.deployment.get_machines()94 machines = self.deployment.get_machines()
95 machines_map = {}
10796
108 if machines:97 if machines:
109 self.log.info("Creating machines...")98 self.log.info("Creating machines...")
110 for machine_name, spec in machines.items():99 for machine_name, spec in machines.items():
111 if self.machine_exists(machine_name):100 if self.machine_exists(machine_name):
101 # XXX frankban: do we really want this? The machine
102 # identifiers as included in v4 bundles are not intended
103 # to refer to real machines. A mapping could be provided
104 # but this kind of implicit mapping seems weak.
112 self.log.info(105 self.log.info(
113 """Machine %s already exists on environment, """106 """Machine %s already exists on environment, """
114 """skipping creation""" % machine_name)107 """skipping creation""" % machine_name)
115 self.machines[machine_name] = self.deployment.get_machine(108 machines_map[machine_name] = str(machine_name)
116 machine_name)
117 else:109 else:
118 self.log.info("Machine %s will be created" % machine_name)110 self.log.info("Machine %s will be created" % machine_name)
119 self.machines[machine_name] = self.env.add_machine(111 machines_map[machine_name] = self.env.add_machine(
120 series=spec.get('series',112 series=spec.get('series',
121 self.deployment.data['series']),113 self.deployment.data['series']),
122 constraints=spec.get('constraints'))114 constraints=spec.get('constraints'))
@@ -125,7 +117,7 @@
125 annotations = spec.get('annotations')117 annotations = spec.get('annotations')
126 if annotations:118 if annotations:
127 self.env.set_annotation(119 self.env.set_annotation(
128 self.machines[machine_name],120 machines_map[machine_name],
129 annotations,121 annotations,
130 entity_type='machine')122 entity_type='machine')
131123
@@ -139,13 +131,12 @@
139 if self.machine_exists(machine_name):131 if self.machine_exists(machine_name):
140 self.log.info("Machine %s already exists,"132 self.log.info("Machine %s already exists,"
141 "skipping creation" % machine_name)133 "skipping creation" % machine_name)
142 self.machines[container_host] = self.deployment.get_machine(134 machines_map[container_host] = str(container_host)
143 container_host)
144 else:135 else:
145 self.log.info("A new container will be created"136 self.log.info("A new container will be created"
146 "on machine: %s" % container_host)137 "on machine: %s" % container_host)
147 self.machines[container_host] = self.env.add_machine()138 machines_map[container_host] = self.env.add_machine()
148 self.deployment.set_machines(machines)139 self.deployment.set_machines(machines_map)
149140
150 def get_charms(self):141 def get_charms(self):
151 # Get Charms142 # Get Charms
152143
=== modified file 'deployer/config.py'
--- deployer/config.py 2015-03-16 20:00:02 +0000
+++ deployer/config.py 2015-08-06 14:43:20 +0000
@@ -51,9 +51,9 @@
51 raise51 raise
5252
53 # Check if this is a v4 bundle.53 # Check if this is a v4 bundle.
54 if 'services' in yaml_result:54 services = yaml_result.get('services')
55 if 'machines' in yaml_result:55 if isinstance(services, dict) and 'services' not in services:
56 self.version = 456 self.version = 4
57 yaml_result = {config_file: yaml_result}57 yaml_result = {config_file: yaml_result}
5858
59 self.yaml[config_file] = yaml_result59 self.yaml[config_file] = yaml_result
@@ -64,7 +64,7 @@
64 return sorted(self.data)64 return sorted(self.data)
6565
66 def get(self, key):66 def get(self, key):
67 if not key in self.data:67 if key not in self.data:
68 self.log.warning("Deployment %r not found. Available %s",68 self.log.warning("Deployment %r not found. Available %s",
69 key, ", ".join(self.keys()))69 key, ", ".join(self.keys()))
70 raise ErrorExit()70 raise ErrorExit()
@@ -99,7 +99,7 @@
99 return parents99 return parents
100100
101 def _resolve_inherited(self, deploy_data):101 def _resolve_inherited(self, deploy_data):
102 if not 'inherits' in deploy_data:102 if 'inherits' not in deploy_data:
103 return deploy_data103 return deploy_data
104 inherits = parents = self._inherits(deploy_data)104 inherits = parents = self._inherits(deploy_data)
105 for parent_name in parents:105 for parent_name in parents:
106106
=== modified file 'deployer/deployment.py'
--- deployer/deployment.py 2015-05-13 14:16:46 +0000
+++ deployer/deployment.py 2015-08-06 14:43:20 +0000
@@ -38,7 +38,7 @@
38 pprint.pprint(self.data)38 pprint.pprint(self.data)
3939
40 def get_service(self, name):40 def get_service(self, name):
41 if not name in self.data['services']:41 if name not in self.data['services']:
42 return42 return
43 return Service(name, self.data['services'][name])43 return Service(name, self.data['services'][name])
4444
@@ -95,9 +95,9 @@
95 # Check for colocation. This naively assumes that there is no95 # Check for colocation. This naively assumes that there is no
96 # circularity in placements.96 # circularity in placements.
97 if x_in_y(svc_b, svc_a):97 if x_in_y(svc_b, svc_a):
98 return 1
99 if x_in_y(svc_a, svc_b):
98 return -1100 return -1
99 if x_in_y(svc_a, svc_b):
100 return 1
101 # If no colocation exists, simply compare names.101 # If no colocation exists, simply compare names.
102 return cmp(svc_a.name, svc_b.name)102 return cmp(svc_a.name, svc_b.name)
103 return 1103 return 1
104104
=== modified file 'deployer/service.py'
--- deployer/service.py 2015-03-18 18:19:43 +0000
+++ deployer/service.py 2015-08-06 14:43:20 +0000
@@ -220,7 +220,7 @@
220220
221 def _fill_placement(self):221 def _fill_placement(self):
222 """Fill the placement spec with necessary data.222 """Fill the placement spec with necessary data.
223 223
224 From the spec:224 From the spec:
225 A unit placement may be specified with a service name only, in which225 A unit placement may be specified with a service name only, in which
226 case its unit number is assumed to be one more than the unit number of226 case its unit number is assumed to be one more than the unit number of
@@ -238,7 +238,7 @@
238 return238 return
239239
240 self.service.svc_data['to'] = (240 self.service.svc_data['to'] = (
241 unit_mapping + 241 unit_mapping +
242 list(itertools.repeat(unit_mapping[-1], unit_count - len(unit_mapping)))242 list(itertools.repeat(unit_mapping[-1], unit_count - len(unit_mapping)))
243 )243 )
244 unit_mapping = self.service.unit_placement244 unit_mapping = self.service.unit_placement
@@ -256,9 +256,9 @@
256256
257 def _parse_placement(self, placement):257 def _parse_placement(self, placement):
258 """Parse a unit placement statement.258 """Parse a unit placement statement.
259 259
260 In version 4 bundles, unit placement statements take the form of260 In version 4 bundles, unit placement statements take the form of
261 261
262 (<containertype>:)?(<unit>|<machine>|new)262 (<containertype>:)?(<unit>|<machine>|new)
263263
264 This splits the placement into a container, a placement, and a unit264 This splits the placement into a container, a placement, and a unit
@@ -278,7 +278,7 @@
278 containers, and/or services must be internally consistent, consistent278 containers, and/or services must be internally consistent, consistent
279 with other services in the deployment, and consistent with any machines279 with other services in the deployment, and consistent with any machines
280 specified in the 'machines' block of the deployment.280 specified in the 'machines' block of the deployment.
281 281
282 A feedback object is returned, potentially with errors and warnings282 A feedback object is returned, potentially with errors and warnings
283 inside it.283 inside it.
284 """284 """
@@ -294,55 +294,51 @@
294294
295 services = dict([(s.name, s) for s in self.deployment.get_services()])295 services = dict([(s.name, s) for s in self.deployment.get_services()])
296 machines = self.deployment.get_machines()296 machines = self.deployment.get_machines()
297 container = None297 service_name = self.service.name
298 unit_number = None
299298
300 for i, placement in enumerate(unit_placement):299 for i, placement in enumerate(unit_placement):
301 container, placement, unit_number = self._parse_placement(placement)300 container, target, unit_number = self._parse_placement(placement)
302301
302 # Validate the container type.
303 if container and container not in ('lxc', 'kvm'):303 if container and container not in ('lxc', 'kvm'):
304 feedback.error(304 feedback.error(
305 "Invalid container type: %s service: %s placement: %s" \305 'Invalid container type: {} service: {} placement: {}'
306 % (container, self.service.name, unit_placement[i]))306 ''.format(container, service_name, placement))
307 # XXX Nesting containers not supported yet.
308 # Makyo - 2015-03-01
309 if container is not None and not (placement.isdigit()
310 or placement == 'new'):
311 feedback.error(
312 "Invalid target for container: %s" % (
313 unit_placement[i]))
314 # Specify an existing machine (or, if the number is in the307 # Specify an existing machine (or, if the number is in the
315 # list of machine specs, one of those).308 # list of machine specs, one of those).
316 if placement.isdigit():309 if str(target) in machines:
317 if placement in machines:
318 continue
319 else:
320 feedback.error(
321 ("Service placement to machine "
322 "not supported %s to %s") % (
323 self.service.name, unit_placement[i]))
324 # Specify a machine from the machine spec.
325 elif placement in self.deployment.get_machines():
326 continue310 continue
327 # Specify a service for colocation.311 if target.isdigit():
328 elif placement in services:312 feedback.error(
329 # Specify a particular unit for colocation.313 'Service placement to machine not supported: {} to {}'
330 if unit_number is not None and \314 ''.format(service_name, placement))
331 unit_number > services[placement].num_units:315 # Specify a service for co-location.
332 feedback.error(316 elif target in services:
333 "Service unit does not exist, %s to %s/%s" % (317 # Specify a particular unit for co-location.
334 self.service.name, placement, unit_number))318 if unit_number is not None:
335 elif self.deployment.get_charm_for(placement).is_subordinate():319 try:
336 feedback.error(320 unit_number = int(unit_number)
337 "Cannot place to a subordinate service: %s -> %s" % (321 except (TypeError, ValueError):
338 self.service.name, placement))322 feedback.error(
323 'Invalid unit number for placement: {} to {}'
324 ''.format(service_name, unit_number))
325 continue
326 if unit_number > services[target].num_units:
327 feedback.error(
328 'Service unit does not exist: {} to {}/{}'
329 ''.format(service_name, target, unit_number))
330 continue
331 if self.deployment.get_charm_for(target).is_subordinate():
332 feedback.error(
333 'Cannot place to a subordinate service: {} -> {}'
334 ''.format(service_name, target))
339 # Create a new machine or container.335 # Create a new machine or container.
340 elif placement == 'new':336 elif target == 'new':
341 continue337 continue
342 else:338 else:
343 feedback.error(339 feedback.error(
344 "Invalid service placement %s to %s" % (340 'Invalid service placement: {} to {}'
345 self.service.name, unit_placement[i]))341 ''.format(service_name, placement))
346 return feedback342 return feedback
347343
348 def get_new_machines_for_containers(self):344 def get_new_machines_for_containers(self):
@@ -367,6 +363,8 @@
367 svc = self.service363 svc = self.service
368364
369 unit_mapping = svc.unit_placement365 unit_mapping = svc.unit_placement
366 if not unit_mapping:
367 return None
370 unit_placement = placement = str(unit_mapping[unit_number])368 unit_placement = placement = str(unit_mapping[unit_number])
371 container = None369 container = None
372 u_idx = unit_number370 u_idx = unit_number
@@ -375,14 +373,17 @@
375 if placement == 'new':373 if placement == 'new':
376 return None374 return None
377375
378 container, placement, unit_number = self._parse_placement(unit_placement)376 container, placement, unit_number = self._parse_placement(
377 unit_placement)
379378
380 if placement in self.machines_map:379 if placement in self.machines_map:
381 return self._format_placement(self.machines_map[placement], container)380 return self._format_placement(
381 self.machines_map[placement], container)
382382
383 # Handle <container_type>:new383 # Handle <container_type>:new
384 if placement == 'new':384 if placement == 'new':
385 return self._format_placement(385 return self._format_placement(
386 self.machines_map['%s/%d' % (self.service.name, u_idx)], container)386 self.machines_map['%s/%d' % (self.service.name, u_idx)],
387 container)
387388
388 return self.colocate(status, placement, u_idx, container, svc)389 return self.colocate(status, placement, u_idx, container, svc)
389390
=== modified file 'deployer/tests/base.py'
--- deployer/tests/base.py 2015-03-18 18:19:43 +0000
+++ deployer/tests/base.py 2015-08-06 14:43:20 +0000
@@ -6,6 +6,8 @@
6import StringIO6import StringIO
7import tempfile7import tempfile
88
9import mock
10
9import deployer11import deployer
10from deployer.config import ConfigStack12from deployer.config import ConfigStack
1113
@@ -77,3 +79,37 @@
77 os.environ.update(original_environ)79 os.environ.update(original_environ)
7880
79 os.environ.update(kw)81 os.environ.update(kw)
82
83
84def patch_env_status(env, services, machines=()):
85 """Simulate that the given mock env has the status described in services.
86
87 This function is used so that tests do not have to wait minutes for
88 service units presence when the importer is used with the given env.
89
90 The services argument is a dict mapping service names with the number of
91 their units. This will be reflected by the status returned when the
92 importer adds the units (see "deployer.action.importer.Importer.add_unit").
93
94 The machines argument can be used to simulate that the given machines are
95 present in the Juju environment.
96 """
97 services_status = dict(
98 (k, {'units': dict((i, {}) for i in range(v))})
99 for k, v in services.items()
100 )
101 machines_status = dict((i, {}) for i in machines)
102 env.status.side_effect = [
103 # There are no services initially.
104 {'services': {}, 'machines': machines_status},
105 {'services': {}, 'machines': machines_status},
106 # This is the workaround check for subordinate charms presence:
107 # see lp:1421315 for details.
108 {'services': services_status, 'machines': machines_status},
109 {'services': services_status, 'machines': machines_status},
110 # After we exited the workaround loop, we can just mock further
111 # status results.
112 mock.MagicMock(),
113 mock.MagicMock(),
114 mock.MagicMock(),
115 ]
80116
=== modified file 'deployer/tests/test_data/v4/container-existing-machine.yaml'
--- deployer/tests/test_data/v4/container-existing-machine.yaml 2015-05-13 14:06:34 +0000
+++ deployer/tests/test_data/v4/container-existing-machine.yaml 2015-08-06 14:43:20 +0000
@@ -39,4 +39,4 @@
39- - mediawiki:db39- - mediawiki:db
40 - mysql:db40 - mysql:db
41machines:41machines:
42 1: 142 1: {}
4343
=== added file 'deployer/tests/test_data/v4/placement-invalid-number.yaml'
--- deployer/tests/test_data/v4/placement-invalid-number.yaml 1970-01-01 00:00:00 +0000
+++ deployer/tests/test_data/v4/placement-invalid-number.yaml 2015-08-06 14:43:20 +0000
@@ -0,0 +1,8 @@
1series: trusty
2services:
3 django:
4 charm: cs:trusty/django
5 to:
6 - lxc:haproxy/bad-wolf
7 haproxy:
8 charm: cs:trusty/haproxy
09
=== modified file 'deployer/tests/test_deployment.py'
--- deployer/tests/test_deployment.py 2015-03-18 18:19:43 +0000
+++ deployer/tests/test_deployment.py 2015-08-06 14:43:20 +0000
@@ -106,12 +106,17 @@
106 d._machines_placement_sort(106 d._machines_placement_sort(
107 FauxService(name="x", unit_placement=['asdf']),107 FauxService(name="x", unit_placement=['asdf']),
108 FauxService(name="y", unit_placement=['lxc:x/1'])108 FauxService(name="y", unit_placement=['lxc:x/1'])
109 ), 1)109 ), -1)
110 self.assertEqual(110 self.assertEqual(
111 d._machines_placement_sort(111 d._machines_placement_sort(
112 FauxService(name="y", unit_placement=['lxc:x/1']),112 FauxService(name="y", unit_placement=['lxc:x/1']),
113 FauxService(name="x", unit_placement=['asdf'])113 FauxService(name="x", unit_placement=['asdf'])
114 ), -1)114 ), 1)
115 self.assertEqual(
116 d._machines_placement_sort(
117 FauxService(name="x", unit_placement=['y']),
118 FauxService(name="y")
119 ), 1)
115 self.assertEqual(120 self.assertEqual(
116 d._machines_placement_sort(121 d._machines_placement_sort(
117 FauxService(name="x", unit_placement=['asdf']),122 FauxService(name="x", unit_placement=['asdf']),
@@ -195,12 +200,12 @@
195 self.assertIn(200 self.assertIn(
196 'Cannot place to a subordinate service: nova-compute -> nrpe\n',201 'Cannot place to a subordinate service: nova-compute -> nrpe\n',
197 output)202 output)
198 203
199 @skip_if_offline204 @skip_if_offline
200 def test_validate_invalid_placement_subordinate_v4(self):205 def test_validate_invalid_placement_subordinate_v4(self):
201 # Placement validation fails if a subordinate charm is provided.206 # Placement validation fails if a subordinate charm is provided.
202 deployment = self.get_deployment_and_fetch_v4(207 deployment = self.get_deployment_and_fetch_v4(
203 'placement-invalid-subordinate.yaml')208 'placement-invalid-subordinate.yaml')
204 with self.assertRaises(ErrorExit):209 with self.assertRaises(ErrorExit):
205 deployment.validate_placement()210 deployment.validate_placement()
206 output = self.output.getvalue()211 output = self.output.getvalue()
@@ -208,6 +213,15 @@
208 'Cannot place to a subordinate service: nova-compute -> nrpe\n',213 'Cannot place to a subordinate service: nova-compute -> nrpe\n',
209 output)214 output)
210215
216 def test_validate_invalid_unit_number(self):
217 # Placement validation fails if an invalid unit number is provided.
218 deployment = self.get_deployment_v4('placement-invalid-number.yaml')
219 with self.assertRaises(ErrorExit):
220 deployment.validate_placement()
221 output = self.output.getvalue()
222 self.assertIn(
223 'Invalid unit number for placement: django to bad-wolf\n', output)
224
211 def test_get_unit_placement_v3(self):225 def test_get_unit_placement_v3(self):
212 d = self.get_named_deployment_v3("stack-placement.yaml", "stack")226 d = self.get_named_deployment_v3("stack-placement.yaml", "stack")
213 status = {227 status = {
@@ -281,12 +295,11 @@
281 feedback = placement.validate()295 feedback = placement.validate()
282 self.assertEqual(feedback.get_errors(), [296 self.assertEqual(feedback.get_errors(), [
283 'Invalid container type: asdf service: mysql placement: asdf:0',297 'Invalid container type: asdf service: mysql placement: asdf:0',
284 'Service placement to machine not supported mysql to asdf:0',298 'Service placement to machine not supported: mysql to asdf:0',
285 'Invalid target for container: lxc:asdf',299 'Invalid service placement: mysql to lxc:asdf',
286 'Invalid service placement mysql to lxc:asdf',300 'Service placement to machine not supported: mysql to 1',
287 'Service placement to machine not supported mysql to 1',301 'Service unit does not exist: mysql to wordpress/3',
288 'Service unit does not exist, mysql to wordpress/3',302 'Invalid service placement: mysql to asdf'])
289 'Invalid service placement mysql to asdf'])
290303
291 def test_get_unit_placement_v4_simple(self):304 def test_get_unit_placement_v4_simple(self):
292 d = self.get_deployment_v4('simple.yaml')305 d = self.get_deployment_v4('simple.yaml')
293306
=== modified file 'deployer/tests/test_guiserver.py'
--- deployer/tests/test_guiserver.py 2015-03-17 10:40:34 +0000
+++ deployer/tests/test_guiserver.py 2015-08-06 14:43:20 +0000
@@ -11,7 +11,10 @@
1111
12from deployer import guiserver12from deployer import guiserver
13from deployer.feedback import Feedback13from deployer.feedback import Feedback
14from deployer.tests.base import skip_if_offline14from deployer.tests.base import (
15 patch_env_status,
16 skip_if_offline,
17)
1518
1619
17class TestGetDefaultGuiserverOptions(unittest.TestCase):20class TestGetDefaultGuiserverOptions(unittest.TestCase):
@@ -268,6 +271,8 @@
268 def test_importer_behavior(self, mock_sleep, mock_environment):271 def test_importer_behavior(self, mock_sleep, mock_environment):
269 # The importer executes the expected environment calls.272 # The importer executes the expected environment calls.
270 self.addCleanup(self.cleanup_series_path)273 self.addCleanup(self.cleanup_series_path)
274 patch_env_status(mock_environment(), {'mysql': 1, 'wordpress': 1})
275 mock_environment.reset_mock()
271 with self.patch_juju_home():276 with self.patch_juju_home():
272 self.import_bundle()277 self.import_bundle()
273 mock_sleep.assert_has_calls([mock.call(5.1), mock.call(60)])278 mock_sleep.assert_has_calls([mock.call(5.1), mock.call(60)])
@@ -289,8 +294,7 @@
289 None, 1, None),294 None, 1, None),
290 mock.call.set_annotation(295 mock.call.set_annotation(
291 'wordpress', {'gui-y': '414.547', 'gui-x': '425.347'}),296 'wordpress', {'gui-y': '414.547', 'gui-x': '425.347'}),
292 mock.call.add_units('mysql', 2),297 mock.call.add_units('mysql', 1),
293 mock.call.add_units('wordpress', 1),
294 mock.call.add_relation('mysql:db', 'wordpress:db'),298 mock.call.add_relation('mysql:db', 'wordpress:db'),
295 mock.call.close(),299 mock.call.close(),
296 ], any_order=True)300 ], any_order=True)
297301
=== modified file 'deployer/tests/test_importer.py'
--- deployer/tests/test_importer.py 2015-05-13 14:06:34 +0000
+++ deployer/tests/test_importer.py 2015-08-06 14:43:20 +0000
@@ -6,7 +6,11 @@
6from deployer.action.importer import Importer6from deployer.action.importer import Importer
7from deployer.utils import yaml_dump, yaml_load7from deployer.utils import yaml_dump, yaml_load
88
9from base import Base, skip_if_offline9from base import (
10 Base,
11 patch_env_status,
12 skip_if_offline,
13)
1014
1115
12class Options(dict):16class Options(dict):
@@ -51,6 +55,7 @@
51 stack = ConfigStack(self.options.configs)55 stack = ConfigStack(self.options.configs)
52 deploy = stack.get('wiki')56 deploy = stack.get('wiki')
53 env = mock.MagicMock()57 env = mock.MagicMock()
58 patch_env_status(env, {'wiki': 1, 'db': 1})
54 importer = Importer(env, deploy, self.options)59 importer = Importer(env, deploy, self.options)
55 importer.run()60 importer.run()
5661
@@ -60,10 +65,6 @@
60 'wiki', 'cs:precise/mediawiki', '', config, None, 1, None))65 'wiki', 'cs:precise/mediawiki', '', config, None, 1, None))
61 env.add_relation.assert_called_once_with('wiki', 'db')66 env.add_relation.assert_called_once_with('wiki', 'db')
6267
63 self.assertEqual(
64 yaml_load(yaml_dump(yaml_load(yaml_dump(config)))),
65 config)
66
67 @skip_if_offline68 @skip_if_offline
68 @mock.patch('deployer.action.importer.time')69 @mock.patch('deployer.action.importer.time')
69 def test_importer_no_relations(self, mock_time):70 def test_importer_no_relations(self, mock_time):
@@ -71,10 +72,10 @@
71 stack = ConfigStack(self.options.configs)72 stack = ConfigStack(self.options.configs)
72 deploy = stack.get('wiki')73 deploy = stack.get('wiki')
73 env = mock.MagicMock()74 env = mock.MagicMock()
75 patch_env_status(env, {'wiki': 1, 'db': 1})
74 importer = Importer(env, deploy, self.options)76 importer = Importer(env, deploy, self.options)
75 importer.run()77 importer.run()
7678 self.assertFalse(env.add_relation.called)
77 self.assertFalse(env.add_relation.called, False)
7879
79 @skip_if_offline80 @skip_if_offline
80 @mock.patch('deployer.action.importer.time')81 @mock.patch('deployer.action.importer.time')
@@ -84,6 +85,7 @@
84 stack = ConfigStack(self.options.configs)85 stack = ConfigStack(self.options.configs)
85 deploy = stack.get(self.options.configs[0])86 deploy = stack.get(self.options.configs[0])
86 env = mock.MagicMock()87 env = mock.MagicMock()
88 patch_env_status(env, {'mediawiki': 1, 'mysql': 1})
87 importer = Importer(env, deploy, self.options)89 importer = Importer(env, deploy, self.options)
88 importer.run()90 importer.run()
8991
@@ -104,14 +106,8 @@
104 stack = ConfigStack(self.options.configs)106 stack = ConfigStack(self.options.configs)
105 deploy = stack.get(self.options.configs[0])107 deploy = stack.get(self.options.configs[0])
106 env = mock.MagicMock()108 env = mock.MagicMock()
107109 patch_env_status(env, {'mediawiki': 1, 'mysql': 1}, machines=[1])
108 config = {'status.return_value.get.return_value': {
109 1: {}
110 }}
111
112 env.configure_mock(**config)
113110
114 importer = Importer(env, deploy, self.options)111 importer = Importer(env, deploy, self.options)
115 importer.run()112 importer.run()
116113 self.assertFalse(env.add_machine.called)
117 env.add_machine.assert_not_called()
118114
=== modified file 'setup.py'
--- setup.py 2015-08-04 17:57:46 +0000
+++ setup.py 2015-08-06 14:43:20 +0000
@@ -3,7 +3,7 @@
33
4setup(4setup(
5 name="juju-deployer",5 name="juju-deployer",
6 version="0.5.0",6 version="0.5.1",
7 description="A tool for deploying complex stacks with juju.",7 description="A tool for deploying complex stacks with juju.",
8 long_description=open("README").read(),8 long_description=open("README").read(),
9 author="Kapil Thangavelu",9 author="Kapil Thangavelu",

Subscribers

People subscribed via source and target branches