Merge lp:~frankban/juju-quickstart/version-handling into lp:juju-quickstart

Proposed by Francesco Banconi
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
Reviewer Review Type Date Requested Status
Juju GUI Hackers Pending
Review via email: mp+210395@code.launchpad.net

Description of the change

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://codereview.appspot.com/74010044/

To post a comment you must log in.
Revision history for this message
Francesco Banconi (frankban) wrote :

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):
   A [revision details]
   M quickstart/__init__.py
   M quickstart/app.py
   M quickstart/manage.py
   M quickstart/settings.py
   M quickstart/tests/test_app.py
   M quickstart/tests/test_manage.py
   M quickstart/tests/test_utils.py
   M quickstart/utils.py

Revision history for this message
Richard Harding (rharding) wrote :

LGTM will qa as well.

https://codereview.appspot.com/74010044/diff/1/quickstart/settings.py
File quickstart/settings.py (right):

https://codereview.appspot.com/74010044/diff/1/quickstart/settings.py#newcode34
quickstart/settings.py:34: # The path to the Juju command.
can/should we get this via `which juju`? I guess it shouldn't change.

https://codereview.appspot.com/74010044/

Revision history for this message
Richard Harding (rharding) wrote :

QA ok doing a local env post 1.17.2 thanks!

https://codereview.appspot.com/74010044/

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

Thank you for the review!

https://codereview.appspot.com/74010044/diff/1/quickstart/settings.py
File quickstart/settings.py (right):

https://codereview.appspot.com/74010044/diff/1/quickstart/settings.py#newcode34
quickstart/settings.py:34: # The path to the Juju command.
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`.

https://codereview.appspot.com/74010044/

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

LGTM, thanks for the clean-up!

https://codereview.appspot.com/74010044/

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

*** Submitted:

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.

R=rharding, matthew.scott
CC=
https://codereview.appspot.com/74010044

https://codereview.appspot.com/74010044/

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

Preview Diff

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

Subscribers

People subscribed via source and target branches