Merge lp:~wgrant/juju-deployer/slaughter-suboordinate-slumber into lp:juju-deployer

Proposed by William Grant
Status: Merged
Merged at revision: 201
Proposed branch: lp:~wgrant/juju-deployer/slaughter-suboordinate-slumber
Merge into: lp:juju-deployer
Diff against target: 189 lines (+17/-69)
4 files modified
deployer/action/importer.py (+7/-21)
deployer/tests/base.py (+1/-36)
deployer/tests/test_guiserver.py (+3/-7)
deployer/tests/test_importer.py (+6/-5)
To merge this branch: bzr merge lp:~wgrant/juju-deployer/slaughter-suboordinate-slumber
Reviewer Review Type Date Requested Status
Tim Van Steenburgh (community) Approve
Review via email: mp+309912@code.launchpad.net

Commit message

Refactor subordinate 60-second sleep away.

Description of the change

Refactor subordinate 60-second sleep away.

r146 caused add_units to wait 60 seconds for every subordinate service.
This was to avoid a crash when it attempted to retrieve the number of
units of the service before Juju had had a chance to include the service
in status output yet. But add_units just skips any subordinate service
anyway, so a much less slow fix is to just move the subordinate check to
the top of the loop.

I also cleaned up some tests which patched the mock environment to skip
the formerly dreadfully slow bits of add_units.

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

This is great, thanks William. I left one inline comment for your review.

review: Needs Fixing
Revision history for this message
William Grant (wgrant) :
Revision history for this message
William Grant (wgrant) :
201. By William Grant

Fix potential crash if units is None.

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

LGTM. Bonus points for the alliterate branch name.

review: Approve

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 2016-09-22 14:10:50 +0000
+++ deployer/action/importer.py 2016-11-03 12:52:41 +0000
@@ -23,22 +23,15 @@
23 # Add units to existing services that don't match count.23 # Add units to existing services that don't match count.
24 env_status = self.env.status()24 env_status = self.env.status()
2525
26 # Workaround an issue where watch output doesn't include subordinate
27 # services right away, and add_unit would fail while attempting to add
28 # units to a non-existant service. See lp:1421315 for details.
29 cur_units = 0
30 for svc in self.deployment.get_services():26 for svc in self.deployment.get_services():
31 delay = time.time() + 6027 charm = self.deployment.get_charm_for(svc.name)
32 while delay > time.time():28 if charm.is_subordinate():
33 if svc.name in env_status['services']:29 self.log.warning(
34 cur_units = len(30 "Config specifies num units for subordinate: %s",
35 (env_status['services'][svc.name].get('units') or ())31 svc.name)
36 )32 continue
37 if cur_units > 0:
38 break
39 time.sleep(5)
40 env_status = self.env.status()
4133
34 cur_units = len(env_status['services'][svc.name].get('units') or ())
42 delta = (svc.num_units - cur_units)35 delta = (svc.num_units - cur_units)
4336
44 if delta <= 0:37 if delta <= 0:
@@ -47,13 +40,6 @@
47 svc.name)40 svc.name)
48 continue41 continue
4942
50 charm = self.deployment.get_charm_for(svc.name)
51 if charm.is_subordinate():
52 self.log.warning(
53 "Config specifies num units for subordinate: %s",
54 svc.name)
55 continue
56
57 self.log.info(43 self.log.info(
58 "Adding %d more units to %s" % (abs(delta), svc.name))44 "Adding %d more units to %s" % (abs(delta), svc.name))
59 if svc.unit_placement:45 if svc.unit_placement:
6046
=== modified file 'deployer/tests/base.py'
--- deployer/tests/base.py 2016-05-07 23:48:30 +0000
+++ deployer/tests/base.py 2016-11-03 12:52:41 +0000
@@ -1,13 +1,12 @@
1from __future__ import absolute_import1from __future__ import absolute_import
2import inspect2import inspect
3import itertools
3import logging4import logging
4import os5import os
5import unittest6import unittest
6import shutil7import shutil
7import tempfile8import tempfile
89
9import mock
10
11import deployer10import deployer
12from deployer.config import ConfigStack11from deployer.config import ConfigStack
1312
@@ -82,37 +81,3 @@
82 os.environ.update(original_environ)81 os.environ.update(original_environ)
8382
84 os.environ.update(kw)83 os.environ.update(kw)
85
86
87def patch_env_status(env, services, machines=()):
88 """Simulate that the given mock env has the status described in services.
89
90 This function is used so that tests do not have to wait minutes for
91 service units presence when the importer is used with the given env.
92
93 The services argument is a dict mapping service names with the number of
94 their units. This will be reflected by the status returned when the
95 importer adds the units (see "deployer.action.importer.Importer.add_unit").
96
97 The machines argument can be used to simulate that the given machines are
98 present in the Juju environment.
99 """
100 services_status = dict(
101 (k, {'units': dict((i, {}) for i in range(v))})
102 for k, v in services.items()
103 )
104 machines_status = dict((i, {}) for i in machines)
105 env.status.side_effect = [
106 # There are no services initially.
107 {'services': {}, 'machines': machines_status},
108 {'services': {}, 'machines': machines_status},
109 # This is the workaround check for subordinate charms presence:
110 # see lp:1421315 for details.
111 {'services': services_status, 'machines': machines_status},
112 {'services': services_status, 'machines': machines_status},
113 # After we exited the workaround loop, we can just mock further
114 # status results.
115 mock.MagicMock(),
116 mock.MagicMock(),
117 mock.MagicMock(),
118 ]
11984
=== modified file 'deployer/tests/test_guiserver.py'
--- deployer/tests/test_guiserver.py 2016-07-12 02:29:52 +0000
+++ deployer/tests/test_guiserver.py 2016-11-03 12:52:41 +0000
@@ -12,10 +12,7 @@
1212
13from deployer import guiserver13from deployer import guiserver
14from deployer.feedback import Feedback14from deployer.feedback import Feedback
15from deployer.tests.base import (15from deployer.tests.base import skip_if_offline
16 patch_env_status,
17 skip_if_offline,
18)
1916
2017
21class TestGetDefaultGuiserverOptions(unittest.TestCase):18class TestGetDefaultGuiserverOptions(unittest.TestCase):
@@ -274,8 +271,6 @@
274 def test_importer_behavior(self, mock_sleep, mock_environment):271 def test_importer_behavior(self, mock_sleep, mock_environment):
275 # The importer executes the expected environment calls.272 # The importer executes the expected environment calls.
276 self.addCleanup(self.cleanup_series_path)273 self.addCleanup(self.cleanup_series_path)
277 patch_env_status(mock_environment(), {'mysql': 1, 'wordpress': 1})
278 mock_environment.reset_mock()
279 with self.patch_juju_home():274 with self.patch_juju_home():
280 self.import_bundle(version=3)275 self.import_bundle(version=3)
281 mock_sleep.assert_has_calls([mock.call(5.1), mock.call(60)])276 mock_sleep.assert_has_calls([mock.call(5.1), mock.call(60)])
@@ -298,7 +293,8 @@
298 None, 1, None, 'precise'),293 None, 1, None, 'precise'),
299 mock.call.set_annotation(294 mock.call.set_annotation(
300 'wordpress', {'gui-y': '414.547', 'gui-x': '425.347'}),295 'wordpress', {'gui-y': '414.547', 'gui-x': '425.347'}),
301 mock.call.add_units('mysql', 1),296 mock.call.add_units('mysql', 2),
297 mock.call.add_units('wordpress', 1),
302 mock.call.add_relation('mysql:db', 'wordpress:db'),298 mock.call.add_relation('mysql:db', 'wordpress:db'),
303 mock.call.close(),299 mock.call.close(),
304 ], any_order=True)300 ], any_order=True)
305301
=== modified file 'deployer/tests/test_importer.py'
--- deployer/tests/test_importer.py 2016-05-07 23:48:30 +0000
+++ deployer/tests/test_importer.py 2016-11-03 12:52:41 +0000
@@ -8,7 +8,6 @@
88
9from .base import (9from .base import (
10 Base,10 Base,
11 patch_env_status,
12 skip_if_offline,11 skip_if_offline,
13)12)
1413
@@ -56,7 +55,6 @@
56 stack = ConfigStack(self.options.configs)55 stack = ConfigStack(self.options.configs)
57 deploy = stack.get('wiki')56 deploy = stack.get('wiki')
58 env = mock.MagicMock()57 env = mock.MagicMock()
59 patch_env_status(env, {'wiki': 1, 'db': 1})
60 importer = Importer(env, deploy, self.options)58 importer = Importer(env, deploy, self.options)
61 importer.run()59 importer.run()
6260
@@ -76,7 +74,6 @@
76 stack = ConfigStack(self.options.configs)74 stack = ConfigStack(self.options.configs)
77 deploy = stack.get('wiki')75 deploy = stack.get('wiki')
78 env = mock.MagicMock()76 env = mock.MagicMock()
79 patch_env_status(env, {'wiki': 1, 'db': 1})
80 importer = Importer(env, deploy, self.options)77 importer = Importer(env, deploy, self.options)
81 importer.run()78 importer.run()
82 self.assertFalse(env.add_relation.called)79 self.assertFalse(env.add_relation.called)
@@ -90,7 +87,6 @@
90 stack = ConfigStack(self.options.configs)87 stack = ConfigStack(self.options.configs)
91 deploy = stack.get(self.options.configs[0])88 deploy = stack.get(self.options.configs[0])
92 env = mock.MagicMock()89 env = mock.MagicMock()
93 patch_env_status(env, {'mediawiki': 1, 'mysql': 1})
94 importer = Importer(env, deploy, self.options)90 importer = Importer(env, deploy, self.options)
95 importer.run()91 importer.run()
9692
@@ -110,7 +106,12 @@
110 stack = ConfigStack(self.options.configs)106 stack = ConfigStack(self.options.configs)
111 deploy = stack.get(self.options.configs[0])107 deploy = stack.get(self.options.configs[0])
112 env = mock.MagicMock()108 env = mock.MagicMock()
113 patch_env_status(env, {'mediawiki': 1, 'mysql': 1}, machines=[1])109
110 config = {'status.return_value.get.return_value': {
111 1: {}
112 }}
113
114 env.configure_mock(**config)
114115
115 importer = Importer(env, deploy, self.options)116 importer = Importer(env, deploy, self.options)
116 importer.run()117 importer.run()

Subscribers

People subscribed via source and target branches