Merge lp:~frankban/juju-quickstart/version-handling into lp:juju-quickstart
- version-handling
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 56 |
Proposed branch: | lp:~frankban/juju-quickstart/version-handling |
Merge into: | lp:juju-quickstart |
Diff against target: |
669 lines (+159/-159) 8 files modified
quickstart/__init__.py (+1/-1) quickstart/app.py (+35/-39) quickstart/manage.py (+12/-3) quickstart/settings.py (+3/-0) quickstart/tests/test_app.py (+67/-89) quickstart/tests/test_manage.py (+31/-14) quickstart/tests/test_utils.py (+6/-9) quickstart/utils.py (+4/-4) |
To merge this branch: | bzr merge lp:~frankban/juju-quickstart/version-handling |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju GUI Hackers | Pending | ||
Review via email: mp+210395@code.launchpad.net |
Commit message
Description of the change
Improve version handling.
Modify the app.ensure_
so that it returns the Juju version tuple.
This way we allow for reusing the version
through the app and we can avoid calling
"juju version" multiple times.
Support local environments by installing
the juju-local meta-package in place of
specific packages (e.g. lxc, mongo).
This way we don't have to update quickstart
if core devs introduce new local env
dependencies.
Fix the destroy-environment message displayed
at the end of the process. The -e flag is
included when an old Juju is used.
Tests: `make check`.
QA: no strictly required.
Use .venv/bin/python juju-quickstart, check everything
works well, check the destroy-environment message
at the end of the process.
Francesco Banconi (frankban) wrote : | # |
Richard Harding (rharding) wrote : | # |
LGTM will qa as well.
https:/
File quickstart/
https:/
quickstart/
can/should we get this via `which juju`? I guess it shouldn't change.
Richard Harding (rharding) wrote : | # |
QA ok doing a local env post 1.17.2 thanks!
Francesco Banconi (frankban) wrote : | # |
Thank you for the review!
https:/
File quickstart/
https:/
quickstart/
On 2014/03/11 13:24:24, rharding wrote:
> can/should we get this via `which juju`? I guess it shouldn't change.
We can but I am not sure we should. We call juju with sudo, and we use
the complete path for that security reason. AFAICT using `which` would
reintroduce the problem ,and at that point it would be easier to just
call `juju`.
Madison Scott-Clary (makyo) wrote : | # |
LGTM, thanks for the clean-up!
Francesco Banconi (frankban) wrote : | # |
*** Submitted:
Improve version handling.
Modify the app.ensure_
so that it returns the Juju version tuple.
This way we allow for reusing the version
through the app and we can avoid calling
"juju version" multiple times.
Support local environments by installing
the juju-local meta-package in place of
specific packages (e.g. lxc, mongo).
This way we don't have to update quickstart
if core devs introduce new local env
dependencies.
Fix the destroy-environment message displayed
at the end of the process. The -e flag is
included when an old Juju is used.
Tests: `make check`.
QA: no strictly required.
Use .venv/bin/python juju-quickstart, check everything
works well, check the destroy-environment message
at the end of the process.
R=rharding, matthew.scott
CC=
https:/
Francesco Banconi (frankban) wrote : | # |
Thank you!
Preview Diff
1 | === modified file 'quickstart/__init__.py' |
2 | --- quickstart/__init__.py 2014-02-19 10:55:39 +0000 |
3 | +++ quickstart/__init__.py 2014-03-11 13:05:34 +0000 |
4 | @@ -22,7 +22,7 @@ |
5 | from __future__ import unicode_literals |
6 | |
7 | |
8 | -VERSION = (1, 1, 0) |
9 | +VERSION = (1, 1, 1) |
10 | |
11 | |
12 | def get_version(): |
13 | |
14 | === modified file 'quickstart/app.py' |
15 | --- quickstart/app.py 2014-03-10 14:40:21 +0000 |
16 | +++ quickstart/app.py 2014-03-11 13:05:34 +0000 |
17 | @@ -63,32 +63,49 @@ |
18 | If juju is not found in the PATH, then add the juju stable PPA and install |
19 | both juju and lxc. |
20 | |
21 | - Return when juju is available. |
22 | - Raise a ProgramExit if an error occurs installing. |
23 | + Return the Juju version tuple when Juju is available. |
24 | + |
25 | + Raise a ProgramExit if an error occurs installing packages or retrieving |
26 | + the Juju version. |
27 | """ |
28 | required_packages = [] |
29 | - retcode = utils.call('/usr/bin/juju', 'version')[0] |
30 | - if retcode > 0: |
31 | - required_packages.append('juju-core') |
32 | - retcode = utils.call('/usr/bin/lxc-ls')[0] |
33 | - if retcode > 0: |
34 | - # The lxc package is required to create the LXC containers used by |
35 | - # the local provider. When the local provider is enabled, the host |
36 | - # itself is used as bootstrap node. For this reason, the mongodb-server |
37 | - # package from the Juju stable PPA is also required. |
38 | - required_packages.extend(['lxc', 'mongodb-server']) |
39 | + # Check if juju is installed. |
40 | + try: |
41 | + juju_version = utils.get_juju_version() |
42 | + except ValueError: |
43 | + # Juju is not installed or configured properly. To ensure everything |
44 | + # is set up correctly, also install packages required to run |
45 | + # environments using the local provider. |
46 | + required_packages.extend(['juju-core', 'juju-local']) |
47 | + juju_version = None |
48 | + else: |
49 | + # Check if LXC is installed. |
50 | + retcode = utils.call('/usr/bin/lxc-ls')[0] |
51 | + if retcode: |
52 | + # Some packages (e.g. lxc and mongodb-server) are required to |
53 | + # support bootstrapping environments using the local provider. |
54 | + # All those packages are installed as juju-local dependencies. |
55 | + required_packages.append('juju-local') |
56 | if required_packages: |
57 | try: |
58 | utils.add_apt_repository('ppa:juju/stable') |
59 | except OSError as err: |
60 | raise ProgramExit(bytes(err)) |
61 | - print('installing the following packages: {} ' |
62 | - '(this can take a while)'.format(', '.join(required_packages))) |
63 | - print('sudo privileges will be used for package installation') |
64 | + print('sudo privileges will be used for the installation of \n' |
65 | + 'the following packages: {}\n' |
66 | + 'this can take a while...'.format(', '.join(required_packages))) |
67 | retcode, _, error = utils.call( |
68 | 'sudo', '/usr/bin/apt-get', 'install', '-y', *required_packages) |
69 | if retcode: |
70 | raise ProgramExit(error) |
71 | + # Return the current Juju version. |
72 | + if juju_version is None: |
73 | + # Juju has been just installed, retrieve its version. |
74 | + try: |
75 | + juju_version = utils.get_juju_version() |
76 | + except ValueError as err: |
77 | + raise ProgramExit(bytes(err)) |
78 | + return juju_version |
79 | |
80 | |
81 | def ensure_ssh_keys(): |
82 | @@ -139,27 +156,6 @@ |
83 | raise ProgramExit(bytes(err)) |
84 | |
85 | |
86 | -def bootstrap_requires_sudo(is_local): |
87 | - """Return True if "sudo" is required to bootstrap the Juju environment. |
88 | - |
89 | - Return False otherwise. |
90 | - Raise a ProgramExit if any error occurs retrieving the Juju version. |
91 | - """ |
92 | - if not is_local: |
93 | - return False |
94 | - # If this is a local environment, notify the user that "sudo" will be |
95 | - # required to bootstrap the application, even in newer Juju versions where |
96 | - # "sudo" is invoked by juju-core itself. |
97 | - print('sudo privileges required to bootstrap the environment') |
98 | - # If the Juju core version is less than 1.17.2 then use sudo for local |
99 | - # deployments. |
100 | - try: |
101 | - major, minor, patch = utils.get_juju_version() |
102 | - except ValueError as err: |
103 | - raise ProgramExit(bytes(err)) |
104 | - return (major, minor, patch) < (1, 17, 2) |
105 | - |
106 | - |
107 | def bootstrap(env_name, requires_sudo=False, debug=False): |
108 | """Bootstrap the Juju environment with the given name. |
109 | |
110 | @@ -180,7 +176,7 @@ |
111 | Raise a ProgramExit if any error occurs in the bootstrap process. |
112 | """ |
113 | already_bootstrapped = False |
114 | - cmd = ['/usr/bin/juju', 'bootstrap', '-e', env_name] |
115 | + cmd = [settings.JUJU_CMD, 'bootstrap', '-e', env_name] |
116 | if requires_sudo: |
117 | cmd.insert(0, 'sudo') |
118 | if debug: |
119 | @@ -217,7 +213,7 @@ |
120 | timeout = time.time() + (60*10) |
121 | while time.time() < timeout: |
122 | retcode, output, error = utils.call( |
123 | - '/usr/bin/juju', 'status', '-e', env_name, '--format', 'yaml') |
124 | + settings.JUJU_CMD, 'status', '-e', env_name, '--format', 'yaml') |
125 | if retcode: |
126 | continue |
127 | # Ensure the state server is up and the agent is started. |
128 | @@ -266,7 +262,7 @@ |
129 | Raise a ProgramExit if any error occurs. |
130 | """ |
131 | retcode, output, error = utils.call( |
132 | - '/usr/bin/juju', 'api-endpoints', '-e', env_name, '--format', 'json') |
133 | + settings.JUJU_CMD, 'api-endpoints', '-e', env_name, '--format', 'json') |
134 | if retcode: |
135 | raise ProgramExit(error) |
136 | # Assuming there is always at least one API address, grab the first one |
137 | |
138 | === modified file 'quickstart/manage.py' |
139 | --- quickstart/manage.py 2014-02-19 10:55:39 +0000 |
140 | +++ quickstart/manage.py 2014-03-11 13:05:34 +0000 |
141 | @@ -383,13 +383,22 @@ |
142 | """Run the application.""" |
143 | print('juju quickstart v{}'.format(version)) |
144 | logging.debug('ensuring juju and lxc are installed') |
145 | - app.ensure_dependencies() |
146 | + juju_version = app.ensure_dependencies() |
147 | + |
148 | logging.debug('ensuring SSH keys are available') |
149 | app.ensure_ssh_keys() |
150 | + |
151 | print('bootstrapping the {} environment (type: {})'.format( |
152 | options.env_name, options.env_type)) |
153 | is_local = options.env_type == 'local' |
154 | - requires_sudo = app.bootstrap_requires_sudo(is_local) |
155 | + requires_sudo = False |
156 | + if is_local: |
157 | + # If this is a local environment, notify the user that "sudo" will be |
158 | + # required to bootstrap the application, even in newer Juju versions |
159 | + # where "sudo" is invoked by juju-core itself. |
160 | + print('sudo privileges will be required to bootstrap the environment') |
161 | + # If the Juju version is less than 1.17.2 then use sudo for local envs. |
162 | + requires_sudo = juju_version < (1, 17, 2) |
163 | already_bootstrapped, bsn_series = app.bootstrap( |
164 | options.env_name, requires_sudo=requires_sudo, debug=options.debug) |
165 | |
166 | @@ -457,5 +466,5 @@ |
167 | 'to destroy the environment you just bootstrapped.'.format( |
168 | env_name=options.env_name, |
169 | sudo='sudo ' if requires_sudo else '', |
170 | - eflag='-e ' if requires_sudo else '') |
171 | + eflag='-e ' if juju_version < (1, 17, 2) else '') |
172 | ) |
173 | |
174 | === modified file 'quickstart/settings.py' |
175 | --- quickstart/settings.py 2014-02-19 10:55:39 +0000 |
176 | +++ quickstart/settings.py 2014-03-11 13:05:34 +0000 |
177 | @@ -31,6 +31,9 @@ |
178 | # The quickstart app short description. |
179 | DESCRIPTION = 'set up a Juju environment (including the GUI) in very few steps' |
180 | |
181 | +# The path to the Juju command. |
182 | +JUJU_CMD = '/usr/bin/juju' |
183 | + |
184 | # The possible values for the environments.yaml default-series field. |
185 | JUJU_DEFAULT_SERIES = ('precise', 'quantal', 'raring', 'saucy', 'trusty') |
186 | |
187 | |
188 | === modified file 'quickstart/tests/test_app.py' |
189 | --- quickstart/tests/test_app.py 2014-03-10 13:44:20 +0000 |
190 | +++ quickstart/tests/test_app.py 2014-03-11 13:05:34 +0000 |
191 | @@ -68,58 +68,68 @@ |
192 | apt_get = '/usr/bin/apt-get' |
193 | |
194 | def call_ensure_dependencies(self, call_effects): |
195 | + """Execute the quickstart.app.ensure_dependencies call. |
196 | + |
197 | + The call_effects argument is used to customize the values returned by |
198 | + quickstart.utils.call invocations. |
199 | + |
200 | + Return the mock call object and the ensure_dependencies return value. |
201 | + """ |
202 | with self.patch_multiple_calls(call_effects) as mock_call: |
203 | - app.ensure_dependencies() |
204 | - return mock_call |
205 | + juju_version = app.ensure_dependencies() |
206 | + return mock_call, juju_version |
207 | |
208 | def test_success_install(self, mock_print): |
209 | # All the missing packages are installed from the PPA. |
210 | side_effects = ( |
211 | - (127, '', 'no juju'), # Check the juju command. |
212 | - (127, '', 'no lxc'), # Check the lxc-ls command. |
213 | + (127, '', 'no juju'), # Retrieve the Juju version. |
214 | (0, 'saucy', ''), # Retrieve the Ubuntu release codename. |
215 | (0, 'install add repo', ''), # Install add-apt-repository. |
216 | (0, 'add repo', ''), # Add the juju stable repository. |
217 | (0, 'update', ''), # Update the repository with new sources. |
218 | (0, 'install', ''), # Install missing packages. |
219 | + (0, '1.18.0', ''), # Retrieve the version again. |
220 | ) |
221 | - mock_call = self.call_ensure_dependencies(side_effects) |
222 | + mock_call, juju_version = self.call_ensure_dependencies(side_effects) |
223 | self.assertEqual(len(side_effects), mock_call.call_count) |
224 | mock_call.assert_has_calls([ |
225 | - mock.call('/usr/bin/juju', 'version'), |
226 | - mock.call('/usr/bin/lxc-ls'), |
227 | + mock.call(settings.JUJU_CMD, 'version'), |
228 | mock.call('lsb_release', '-cs'), |
229 | mock.call('sudo', self.apt_get, 'install', '-y', |
230 | 'software-properties-common'), |
231 | mock.call('sudo', self.add_repository, '-y', 'ppa:juju/stable'), |
232 | mock.call('sudo', self.apt_get, 'update'), |
233 | mock.call('sudo', self.apt_get, 'install', '-y', |
234 | - 'juju-core', 'lxc', 'mongodb-server'), |
235 | + 'juju-core', 'juju-local'), |
236 | + mock.call(settings.JUJU_CMD, 'version'), |
237 | ]) |
238 | mock_print.assert_has_calls([ |
239 | - mock.call('sudo privileges are required for PPA installation'), |
240 | - mock.call('installing the following packages: juju-core, ' |
241 | - 'lxc, mongodb-server (this can take a while)'), |
242 | - mock.call('sudo privileges will be used for package installation'), |
243 | + mock.call('adding the ppa:juju/stable PPA repository'), |
244 | + mock.call('sudo privileges will be required for PPA installation'), |
245 | + mock.call('sudo privileges will be used for the installation of \n' |
246 | + 'the following packages: juju-core, juju-local\n' |
247 | + 'this can take a while...'), |
248 | ]) |
249 | + self.assertEqual((1, 18, 0), juju_version) |
250 | |
251 | def test_success_no_install(self, mock_print): |
252 | # There is no need to install packages/PPAs if everything is already |
253 | # set up. |
254 | side_effects = ( |
255 | - (0, '1.16', ''), # Check the juju command. |
256 | + (0, '1.16.2-amd64', ''), # Check the juju command. |
257 | (0, '', ''), # Check the lxc-ls command. |
258 | # The remaining call should be ignored. |
259 | (1, '', 'not ignored'), |
260 | ) |
261 | - mock_call = self.call_ensure_dependencies(side_effects) |
262 | + mock_call, juju_version = self.call_ensure_dependencies(side_effects) |
263 | self.assertEqual(2, mock_call.call_count) |
264 | self.assertFalse(mock_print.called) |
265 | + self.assertEqual((1, 16, 2), juju_version) |
266 | |
267 | def test_success_partial_install(self, mock_print): |
268 | # One missing installation is correctly handled. |
269 | side_effects = ( |
270 | - (0, '1.16', ''), # Check the juju command. |
271 | + (0, '1.16.42', ''), # Check the juju command. |
272 | (127, '', 'no lxc'), # Check the lxc-ls command. |
273 | (0, 'saucy', ''), # Retrieve the Ubuntu release codename. |
274 | (0, 'install add repo', ''), # Install add-apt-repository. |
275 | @@ -127,44 +137,43 @@ |
276 | (0, 'update', ''), # Update the repository with new sources. |
277 | (0, 'install', ''), # Install missing packages. |
278 | ) |
279 | - mock_call = self.call_ensure_dependencies(side_effects) |
280 | + mock_call, juju_version = self.call_ensure_dependencies(side_effects) |
281 | self.assertEqual(len(side_effects), mock_call.call_count) |
282 | mock_call.assert_has_calls([ |
283 | - mock.call('/usr/bin/juju', 'version'), |
284 | + mock.call(settings.JUJU_CMD, 'version'), |
285 | mock.call('/usr/bin/lxc-ls'), |
286 | mock.call('lsb_release', '-cs'), |
287 | mock.call('sudo', self.apt_get, 'install', '-y', |
288 | 'software-properties-common'), |
289 | mock.call('sudo', self.add_repository, '-y', 'ppa:juju/stable'), |
290 | mock.call('sudo', self.apt_get, 'update'), |
291 | - mock.call('sudo', self.apt_get, 'install', '-y', |
292 | - 'lxc', 'mongodb-server'), |
293 | + mock.call('sudo', self.apt_get, 'install', '-y', 'juju-local'), |
294 | ]) |
295 | mock_print.assert_has_calls([ |
296 | - mock.call('sudo privileges are required for PPA installation'), |
297 | - mock.call('installing the following packages: ' |
298 | - 'lxc, mongodb-server (this can take a while)'), |
299 | - mock.call('sudo privileges will be used for package installation'), |
300 | + mock.call('adding the ppa:juju/stable PPA repository'), |
301 | + mock.call('sudo privileges will be required for PPA installation'), |
302 | + mock.call('sudo privileges will be used for the installation of \n' |
303 | + 'the following packages: juju-local\n' |
304 | + 'this can take a while...'), |
305 | ]) |
306 | + self.assertEqual((1, 16, 42), juju_version) |
307 | |
308 | def test_add_repository_failure(self, mock_print): |
309 | # A ProgramExit is raised if the PPA is not successfully installed. |
310 | side_effects = ( |
311 | (127, '', 'no juju'), # Check the juju command. |
312 | - (127, '', 'no lxc'), # Check the lxc-ls command. |
313 | (0, 'saucy', ''), # Retrieve the Ubuntu release codename. |
314 | (0, 'install add repo', ''), # Install add-apt-repository. |
315 | (1, '', 'add repo error'), # Add the juju stable repository. |
316 | ) |
317 | with self.assert_program_exit('add repo error'): |
318 | - mock_call = self.call_ensure_dependencies(side_effects) |
319 | + mock_call = self.call_ensure_dependencies(side_effects)[0] |
320 | self.assertEqual(3, mock_call.call_count) |
321 | |
322 | def test_install_failure(self, mock_print): |
323 | # A ProgramExit is raised if the packages installation fails. |
324 | side_effects = ( |
325 | (127, '', 'no juju'), # Check the juju command. |
326 | - (127, '', 'no lxc'), # Check the lxc-ls command. |
327 | (0, 'saucy', ''), # Retrieve the Ubuntu release codename. |
328 | (0, 'install add repo', ''), # Install add-apt-repository. |
329 | (0, 'add repo', ''), # Add the juju stable repository. |
330 | @@ -172,7 +181,23 @@ |
331 | (1, '', 'install error'), # Install missing packages. |
332 | ) |
333 | with self.assert_program_exit('install error'): |
334 | - mock_call = self.call_ensure_dependencies(side_effects) |
335 | + mock_call = self.call_ensure_dependencies(side_effects)[0] |
336 | + self.assertEqual(3, mock_call.call_count) |
337 | + |
338 | + def test_juju_version_failure(self, mock_print): |
339 | + # A ProgramExit is raised if an error occurs while retrieving the Juju |
340 | + # version after the packages installation. |
341 | + side_effects = ( |
342 | + (127, '', 'no juju'), # Check the juju command. |
343 | + (0, 'saucy', ''), # Retrieve the Ubuntu release codename. |
344 | + (0, 'install add repo', ''), # Install add-apt-repository. |
345 | + (0, 'add repo', ''), # Add the juju stable repository. |
346 | + (0, 'update', ''), # Update the repository with new sources. |
347 | + (0, 'install', ''), # Install missing packages. |
348 | + (127, '', 'no juju (again)'), # Retrieve the Juju version. |
349 | + ) |
350 | + with self.assert_program_exit('no juju (again)'): |
351 | + mock_call = self.call_ensure_dependencies(side_effects)[0] |
352 | self.assertEqual(3, mock_call.call_count) |
353 | |
354 | |
355 | @@ -311,61 +336,10 @@ |
356 | |
357 | |
358 | @helpers.mock_print |
359 | -class TestBootstrapRequiresSudo(ProgramExitTestsMixin, unittest.TestCase): |
360 | - |
361 | - sudo_message = 'sudo privileges required to bootstrap the environment' |
362 | - |
363 | - def patch_get_juju_version(self, return_value): |
364 | - """Patch the quickstart.utils.get_juju_version function.""" |
365 | - if isinstance(return_value, Exception): |
366 | - mock_get_juju_version = mock.Mock(side_effect=return_value) |
367 | - else: |
368 | - mock_get_juju_version = mock.Mock(return_value=return_value) |
369 | - return mock.patch( |
370 | - 'quickstart.utils.get_juju_version', mock_get_juju_version) |
371 | - |
372 | - def test_not_local(self, mock_print): |
373 | - # Sudo privileges are never required for non-local environments. |
374 | - self.assertFalse(app.bootstrap_requires_sudo(False)) |
375 | - |
376 | - def test_sudo_required(self, mock_print): |
377 | - # Sudo privileges are required if the Juju version is < 1.17.2. |
378 | - versions = [(0, 7, 9), (1, 0, 0), (1, 16, 42), (1, 17, 0), (1, 17, 1)] |
379 | - for version in versions: |
380 | - with self.patch_get_juju_version(version): |
381 | - requires_sudo = app.bootstrap_requires_sudo(True) |
382 | - self.assertTrue(requires_sudo, version) |
383 | - # On local environments the "sudo privileges required" message is |
384 | - # always printed. |
385 | - mock_print.assert_called_once_with(self.sudo_message) |
386 | - mock_print.reset_mock() |
387 | - |
388 | - def test_sudo_not_required(self, mock_print): |
389 | - # Sudo privileges are not required if the Juju version is >= 1.17.2. |
390 | - versions = [ |
391 | - (1, 17, 2), (1, 17, 10), (1, 18, 0), (1, 18, 2), (2, 16, 1)] |
392 | - for version in versions: |
393 | - with self.patch_get_juju_version(version): |
394 | - requires_sudo = app.bootstrap_requires_sudo(True) |
395 | - self.assertFalse(requires_sudo, version) |
396 | - # On local environments the "sudo privileges required" message is |
397 | - # always printed. |
398 | - mock_print.assert_called_once_with(self.sudo_message) |
399 | - mock_print.reset_mock() |
400 | - |
401 | - def test_invalid_version(self, mock_print): |
402 | - # A ProgramExit is raised if the Juju version is not valid. |
403 | - with self.patch_get_juju_version(ValueError(b'bad wolf')): |
404 | - with self.assert_program_exit('bad wolf'): |
405 | - app.bootstrap_requires_sudo(True) |
406 | - |
407 | - |
408 | -@helpers.mock_print |
409 | class TestBootstrap( |
410 | helpers.CallTestsMixin, ProgramExitTestsMixin, unittest.TestCase): |
411 | |
412 | env_name = 'my-juju-env' |
413 | - juju = '/usr/bin/juju' |
414 | status_message = 'retrieving the environment status' |
415 | |
416 | def make_status_output(self, agent_state, series='hoary'): |
417 | @@ -378,7 +352,8 @@ |
418 | def make_status_calls(self, number): |
419 | """Return a list containing the given number of status calls.""" |
420 | call = mock.call( |
421 | - self.juju, 'status', '-e', self.env_name, '--format', 'yaml') |
422 | + settings.JUJU_CMD, 'status', '-e', self.env_name, |
423 | + '--format', 'yaml') |
424 | return [call for _ in range(number)] |
425 | |
426 | def make_side_effects(self): |
427 | @@ -396,7 +371,7 @@ |
428 | with self.patch_multiple_calls(side_effects) as mock_call: |
429 | app.bootstrap(self.env_name) |
430 | mock_call.assert_has_calls([ |
431 | - mock.call(self.juju, 'bootstrap', '-e', self.env_name), |
432 | + mock.call(settings.JUJU_CMD, 'bootstrap', '-e', self.env_name), |
433 | ] + self.make_status_calls(5)) |
434 | |
435 | def test_success(self, mock_print): |
436 | @@ -406,7 +381,7 @@ |
437 | self.assertFalse(already_bootstrapped) |
438 | self.assertEqual(series, 'hoary') |
439 | mock_call.assert_has_calls([ |
440 | - mock.call(self.juju, 'bootstrap', '-e', self.env_name), |
441 | + mock.call(settings.JUJU_CMD, 'bootstrap', '-e', self.env_name), |
442 | ] + self.make_status_calls(1)) |
443 | mock_print.assert_called_once_with(self.status_message) |
444 | |
445 | @@ -418,7 +393,8 @@ |
446 | self.assertFalse(already_bootstrapped) |
447 | self.assertEqual(series, 'hoary') |
448 | mock_call.assert_has_calls([ |
449 | - mock.call('sudo', self.juju, 'bootstrap', '-e', self.env_name), |
450 | + mock.call( |
451 | + 'sudo', settings.JUJU_CMD, 'bootstrap', '-e', self.env_name), |
452 | ] + self.make_status_calls(1)) |
453 | mock_print.assert_called_once_with(self.status_message) |
454 | |
455 | @@ -430,7 +406,9 @@ |
456 | self.assertFalse(already_bootstrapped) |
457 | self.assertEqual(series, 'hoary') |
458 | mock_call.assert_has_calls([ |
459 | - mock.call(self.juju, 'bootstrap', '-e', self.env_name, '--debug'), |
460 | + mock.call( |
461 | + settings.JUJU_CMD, 'bootstrap', '-e', self.env_name, |
462 | + '--debug'), |
463 | ] + self.make_status_calls(1)) |
464 | |
465 | def test_already_bootstrapped(self, mock_print): |
466 | @@ -445,7 +423,7 @@ |
467 | self.assertTrue(already_bootstrapped) |
468 | self.assertEqual(series, 'precise') |
469 | mock_call.assert_has_calls([ |
470 | - mock.call(self.juju, 'bootstrap', '-e', self.env_name), |
471 | + mock.call(settings.JUJU_CMD, 'bootstrap', '-e', self.env_name), |
472 | ] + self.make_status_calls(1)) |
473 | existing_message = 'reusing the already bootstrapped {} environment' |
474 | mock_print.assert_has_calls([ |
475 | @@ -459,7 +437,7 @@ |
476 | with self.assert_program_exit('bad wolf'): |
477 | app.bootstrap(self.env_name) |
478 | mock_call.assert_called_once_with( |
479 | - self.juju, 'bootstrap', '-e', self.env_name), |
480 | + settings.JUJU_CMD, 'bootstrap', '-e', self.env_name), |
481 | |
482 | def test_status_retry_error(self, mock_print): |
483 | # Before raising a ProgramExit, the functions tries to call |
484 | @@ -520,7 +498,7 @@ |
485 | with self.assert_program_exit(expected): |
486 | app.bootstrap(self.env_name) |
487 | mock_call.assert_has_calls([ |
488 | - mock.call(self.juju, 'bootstrap', '-e', self.env_name), |
489 | + mock.call(settings.JUJU_CMD, 'bootstrap', '-e', self.env_name), |
490 | ] + self.make_status_calls(1)) |
491 | |
492 | def test_status_failure(self, mock_print): |
493 | @@ -546,7 +524,7 @@ |
494 | with self.assert_program_exit(expected): |
495 | app.bootstrap(self.env_name) |
496 | mock_call.assert_has_calls([ |
497 | - mock.call(self.juju, 'bootstrap', '-e', self.env_name), |
498 | + mock.call(settings.JUJU_CMD, 'bootstrap', '-e', self.env_name), |
499 | ] + self.make_status_calls(2)) |
500 | |
501 | |
502 | @@ -582,7 +560,7 @@ |
503 | api_url = app.get_api_url(self.env_name) |
504 | self.assertEqual('wss://api.example.com:17070', api_url) |
505 | mock_call.assert_called_once_with( |
506 | - '/usr/bin/juju', 'api-endpoints', '-e', self.env_name, |
507 | + settings.JUJU_CMD, 'api-endpoints', '-e', self.env_name, |
508 | '--format', 'json') |
509 | |
510 | def test_failure(self): |
511 | @@ -591,7 +569,7 @@ |
512 | with self.assert_program_exit('bad wolf'): |
513 | app.get_api_url(self.env_name) |
514 | mock_call.assert_called_once_with( |
515 | - '/usr/bin/juju', 'api-endpoints', '-e', self.env_name, |
516 | + settings.JUJU_CMD, 'api-endpoints', '-e', self.env_name, |
517 | '--format', 'json') |
518 | |
519 | |
520 | |
521 | === modified file 'quickstart/tests/test_manage.py' |
522 | --- quickstart/tests/test_manage.py 2014-02-19 10:55:39 +0000 |
523 | +++ quickstart/tests/test_manage.py 2014-03-11 13:05:34 +0000 |
524 | @@ -671,18 +671,17 @@ |
525 | |
526 | def test_no_bundle(self, mock_app, mock_open): |
527 | # The application runs correctly if no bundle is provided. |
528 | - token = 'AUTHTOKEN' |
529 | - mock_app.create_auth_token.return_value = token |
530 | + mock_app.ensure_dependencies.return_value = (1, 18, 0) |
531 | + mock_app.bootstrap.return_value = (True, 'precise') |
532 | + mock_app.get_admin_secret = self.mock_get_admin_secret_error |
533 | mock_app.watch.return_value = '1.2.3.4' |
534 | - mock_app.bootstrap.return_value = (True, 'precise') |
535 | - mock_app.get_admin_secret = self.mock_get_admin_secret_error |
536 | + mock_app.create_auth_token.return_value = 'AUTHTOKEN' |
537 | options = self.make_options() |
538 | manage.run(options) |
539 | mock_app.ensure_dependencies.assert_called() |
540 | mock_app.ensure_ssh_keys.assert_called() |
541 | mock_app.bootstrap.assert_called_once_with( |
542 | - options.env_name, requires_sudo=mock_app.bootstrap_requires_sudo(), |
543 | - debug=options.debug) |
544 | + options.env_name, requires_sudo=False, debug=options.debug) |
545 | mock_app.get_api_url.assert_called_once_with(options.env_name) |
546 | mock_app.connect.assert_has_calls([ |
547 | mock.call(mock_app.get_api_url(), options.admin_secret), |
548 | @@ -698,7 +697,7 @@ |
549 | mock_app.connect(), mock_app.deploy_gui()) |
550 | mock_app.create_auth_token.assert_called_once_with(mock_app.connect()) |
551 | mock_open.assert_called_once_with( |
552 | - 'https://{}/?authtoken={}'.format(mock_app.watch(), token)) |
553 | + 'https://{}/?authtoken={}'.format(mock_app.watch(), 'AUTHTOKEN')) |
554 | self.assertFalse(mock_app.deploy_bundle.called) |
555 | |
556 | def test_no_token(self, mock_app, mock_open): |
557 | @@ -724,22 +723,40 @@ |
558 | def test_local_provider_no_sudo(self, mock_app, mock_open): |
559 | # The application correctly handles working with local providers with |
560 | # new Juju versions not requiring "sudo" to bootstrap the environment. |
561 | + # Sudo privileges are not required if the Juju version is >= 1.17.2. |
562 | options = self.make_options(env_type='local') |
563 | - mock_app.bootstrap_requires_sudo.return_value = False |
564 | + versions = [ |
565 | + (1, 17, 2), (1, 17, 10), (1, 18, 0), (1, 18, 2), (2, 16, 1)] |
566 | mock_app.bootstrap.return_value = (True, 'precise') |
567 | - manage.run(options) |
568 | - mock_app.bootstrap.assert_called_once_with( |
569 | - options.env_name, requires_sudo=False, debug=options.debug) |
570 | + for version in versions: |
571 | + mock_app.ensure_dependencies.return_value = version |
572 | + manage.run(options) |
573 | + mock_app.bootstrap.assert_called_once_with( |
574 | + options.env_name, requires_sudo=False, debug=options.debug) |
575 | + mock_app.bootstrap.reset_mock() |
576 | |
577 | - def test_local_provider_req_sudo(self, mock_app, mock_open): |
578 | + def test_local_provider_requiring_sudo(self, mock_app, mock_open): |
579 | # The application correctly handles working with local providers when |
580 | # Juju requires an external "sudo" call to bootstrap the environment. |
581 | + # Sudo privileges are required if the Juju version is < 1.17.2. |
582 | options = self.make_options(env_type='local') |
583 | - mock_app.bootstrap_requires_sudo.return_value = True |
584 | + versions = [(0, 7, 9), (1, 0, 0), (1, 16, 42), (1, 17, 0), (1, 17, 1)] |
585 | + mock_app.bootstrap.return_value = (True, 'precise') |
586 | + for version in versions: |
587 | + mock_app.ensure_dependencies.return_value = version |
588 | + manage.run(options) |
589 | + mock_app.bootstrap.assert_called_once_with( |
590 | + options.env_name, requires_sudo=True, debug=options.debug) |
591 | + mock_app.bootstrap.reset_mock() |
592 | + |
593 | + def test_no_local_no_sudo(self, mock_app, mock_open): |
594 | + # Sudo privileges are never required for non-local environments. |
595 | + options = self.make_options(env_type='ec2') |
596 | + mock_app.ensure_dependencies.return_value = (1, 14, 0) |
597 | mock_app.bootstrap.return_value = (True, 'precise') |
598 | manage.run(options) |
599 | mock_app.bootstrap.assert_called_once_with( |
600 | - options.env_name, requires_sudo=True, debug=options.debug) |
601 | + options.env_name, requires_sudo=False, debug=options.debug) |
602 | |
603 | def test_no_browser(self, mock_app, mock_open): |
604 | # It is possible to avoid opening the GUI in the browser. |
605 | |
606 | === modified file 'quickstart/tests/test_utils.py' |
607 | --- quickstart/tests/test_utils.py 2014-03-10 13:44:20 +0000 |
608 | +++ quickstart/tests/test_utils.py 2014-03-11 13:05:34 +0000 |
609 | @@ -93,7 +93,7 @@ |
610 | self.assertEqual(2, mock_print.call_count) |
611 | mock_print.assert_has_calls([ |
612 | mock.call('adding the {} PPA repository'.format(self.repository)), |
613 | - mock.call('sudo privileges are required for PPA installation'), |
614 | + mock.call('sudo privileges will be required for PPA installation'), |
615 | ]) |
616 | |
617 | def test_command_error(self, mock_print): |
618 | @@ -668,16 +668,13 @@ |
619 | self.assertEqual((1, 17, 1), version) |
620 | |
621 | def test_juju_version_error(self): |
622 | - # If Juju version returns an error this will throw. |
623 | - with self.patch_call(1, 'foo', 'error'): |
624 | - with self.assertRaises(ValueError) as context_manager: |
625 | + # A ValueError is raised if "juju version" exits with an error. |
626 | + with self.patch_call(1, 'foo', 'bad wolf'): |
627 | + with self.assert_value_error('bad wolf'): |
628 | utils.get_juju_version() |
629 | - self.assertEqual('error', bytes(context_manager.exception)) |
630 | |
631 | def test_invalid_version_string(self): |
632 | - # If Juju version returns an invalid id this will throw. |
633 | + # A ValueError is raised if "juju version" outputs an invalid version. |
634 | with self.patch_call(0, '1.17-precise-amd64', ''): |
635 | - with self.assertRaises(ValueError) as context_manager: |
636 | + with self.assert_value_error('invalid version string: 1.17'): |
637 | utils.get_juju_version() |
638 | - msg = 'invalid version string: 1.17' |
639 | - self.assertEqual(msg, bytes(context_manager.exception)) |
640 | |
641 | === modified file 'quickstart/utils.py' |
642 | --- quickstart/utils.py 2014-03-10 13:44:20 +0000 |
643 | +++ quickstart/utils.py 2014-03-11 13:05:34 +0000 |
644 | @@ -51,7 +51,7 @@ |
645 | Raise an OSError if any error occur in the process. |
646 | """ |
647 | print('adding the {} PPA repository'.format(repository)) |
648 | - print('sudo privileges are required for PPA installation') |
649 | + print('sudo privileges will be required for PPA installation') |
650 | # The package including add-apt-repository is python-software-properties |
651 | # in precise and software-properties-common after precise. |
652 | add_repository_package = 'software-properties-common' |
653 | @@ -314,13 +314,13 @@ |
654 | def get_juju_version(): |
655 | """Return the current juju-core version. |
656 | |
657 | - Return a (major:int, minor:int, patch:int) tuple, including |
658 | - major, minor and patch version numbers. |
659 | + Return a (major:int, minor:int, patch:int) tuple, including major, minor |
660 | + and patch version numbers. |
661 | |
662 | Raise a ValueError if the "juju version" call exits with an error |
663 | or the returned version is not well formed. |
664 | """ |
665 | - retcode, output, error = call('juju', 'version') |
666 | + retcode, output, error = call(settings.JUJU_CMD, 'version') |
667 | if retcode: |
668 | raise ValueError(error.encode('utf-8')) |
669 | version_string = output.split('-')[0] |
Reviewers: mp+210395_ code.launchpad. net,
Message:
Please take a look.
Description:
Improve version handling.
Modify the app.ensure_ dependencies function
so that it returns the Juju version tuple.
This way we allow for reusing the version
through the app and we can avoid calling
"juju version" multiple times.
Support local environments by installing
the juju-local meta-package in place of
specific packages (e.g. lxc, mongo).
This way we don't have to update quickstart
if core devs introduce new local env
dependencies.
Fix the destroy-environment message displayed
at the end of the process. The -e flag is
included when an old Juju is used.
Tests: `make check`.
QA: no strictly required.
Use .venv/bin/python juju-quickstart, check everything
works well, check the destroy-environment message
at the end of the process.
https:/ /code.launchpad .net/~frankban/ juju-quickstart /version- handling/ +merge/ 210395
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/74010044/
Affected files (+161, -159 lines): __init_ _.py manage. py settings. py tests/test_ app.py tests/test_ manage. py tests/test_ utils.py
A [revision details]
M quickstart/
M quickstart/app.py
M quickstart/
M quickstart/
M quickstart/
M quickstart/
M quickstart/
M quickstart/utils.py