Merge lp:~frankban/juju-quickstart/maas-detect into lp:juju-quickstart

Proposed by Francesco Banconi
Status: Merged
Merged at revision: 101
Proposed branch: lp:~frankban/juju-quickstart/maas-detect
Merge into: lp:juju-quickstart
Diff against target: 494 lines (+345/-12)
7 files modified
quickstart/cli/views.py (+50/-6)
quickstart/maas.py (+59/-0)
quickstart/models/envs.py (+24/-1)
quickstart/settings.py (+3/-0)
quickstart/tests/cli/test_views.py (+76/-5)
quickstart/tests/models/test_envs.py (+35/-0)
quickstart/tests/test_maas.py (+98/-0)
To merge this branch: bzr merge lp:~frankban/juju-quickstart/maas-detect
Reviewer Review Type Date Requested Status
Juju GUI Hackers Pending
Review via email: mp+237910@code.launchpad.net

Description of the change

Support automatic detection of a logged in MAAS.

Automatically detect a logged in MAAS API, so that
it is possible to use the stored credentials to
create and bootstrap a MAAS environment without
user intervention.

QA:
- ssh to the GUI MAAS;
- destroy the existing environment;
- remove the ~/.juju directory;
- use the MAAS UI (http://maas.jujugui.org/MAAS/nodes/)
  to release the nodes if they are not in a ready state;
- this branch is already checked out in ~/juju-quickstart/sandbox/;
- start quickstart from there:
  cd ~/juju-quickstart/sandbox/ && .venv/bin/python juju-quickstart
- quickstart should show the option to automatically create and
  bootstrap the MAAS environment;
- select the option and wait for the envirnment to be ready:
  this can fail due to juju/maas interaction problems we currently
  have, retrying the process should eventually succeed.

Done, thank you!

https://codereview.appspot.com/157830043/

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

Reviewers: mp+237910_code.launchpad.net,

Message:
Please take a look.

Description:
Support automatic detection of a logged in MAAS.

Automatically detect a logged in MAAS API, so that
it is possible to use the stored credentials to
create and bootstrap a MAAS environment without
user intervention.

QA:
- ssh to the GUI MAAS;
- destroy the existing environment;
- remove the ~/.juju directory;
- use the MAAS UI (http://maas.jujugui.org/MAAS/nodes/)
   to release the nodes if they are not in a ready state;
- this branch is already checked out in ~/juju-quickstart/sandbox/;
- start quickstart from there:
   cd ~/juju-quickstart/sandbox/ && .venv/bin/python juju-quickstart
- quickstart should show the option to automatically create and
   bootstrap the MAAS environment;
- select the option and wait for the envirnment to be ready:
   this can fail due to juju/maas interaction problems we currently
   have, retrying the process should eventually succeed.

Done, thank you!

https://code.launchpad.net/~frankban/juju-quickstart/maas-detect/+merge/237910

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/157830043/

Affected files (+347, -12 lines):
   A [revision details]
   M quickstart/cli/views.py
   A quickstart/maas.py
   M quickstart/models/envs.py
   M quickstart/settings.py
   M quickstart/tests/cli/test_views.py
   M quickstart/tests/models/test_envs.py
   A quickstart/tests/test_maas.py

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

On 2014/10/10 08:03:44, frankban wrote:
> Please take a look.

LGTM Francesco, with a few little comments. Thanks!

https://codereview.appspot.com/157830043/

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

LGTM

https://codereview.appspot.com/157830043/diff/1/quickstart/maas.py
File quickstart/maas.py (right):

https://codereview.appspot.com/157830043/diff/1/quickstart/maas.py#newcode42
quickstart/maas.py:42: Return None if if no authenticated API is found.
s/if if/if/

https://codereview.appspot.com/157830043/diff/1/quickstart/models/envs.py
File quickstart/models/envs.py (right):

https://codereview.appspot.com/157830043/diff/1/quickstart/models/envs.py#newcode388
quickstart/models/envs.py:388: # Assume all missing fields can be
automatically generated.
What happens if this assumption is wrong? Will field.generate() raise an
error?

https://codereview.appspot.com/157830043/diff/1/quickstart/tests/cli/test_views.py
File quickstart/tests/cli/test_views.py (right):

https://codereview.appspot.com/157830043/diff/1/quickstart/tests/cli/test_views.py#newcode64
quickstart/tests/cli/test_views.py:64: default values: "maas-user",
"http://1.2.3.4/MAAS" and "maas-secret".
Maybe not repeat the values set above but just reference them? I reckon
DRY should apply to comments too.

https://codereview.appspot.com/157830043/

111. By Francesco Banconi

Fixt a typo and improve test comments.

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

Please take a look.

https://codereview.appspot.com/157830043/diff/1/quickstart/maas.py
File quickstart/maas.py (right):

https://codereview.appspot.com/157830043/diff/1/quickstart/maas.py#newcode42
quickstart/maas.py:42: Return None if if no authenticated API is found.
On 2014/10/10 08:48:53, bac wrote:
> s/if if/if/

Done.

https://codereview.appspot.com/157830043/diff/1/quickstart/models/envs.py
File quickstart/models/envs.py (right):

https://codereview.appspot.com/157830043/diff/1/quickstart/models/envs.py#newcode388
quickstart/models/envs.py:388: # Assume all missing fields can be
automatically generated.
On 2014/10/10 08:48:53, bac wrote:
> What happens if this assumption is wrong? Will field.generate() raise
an error?

A field without the ability to automatically generate a value does not
have the generate method. So the above call would raise an
AttributeError.

https://codereview.appspot.com/157830043/diff/1/quickstart/tests/cli/test_views.py
File quickstart/tests/cli/test_views.py (right):

https://codereview.appspot.com/157830043/diff/1/quickstart/tests/cli/test_views.py#newcode64
quickstart/tests/cli/test_views.py:64: default values: "maas-user",
"http://1.2.3.4/MAAS" and "maas-secret".
On 2014/10/10 08:48:53, bac wrote:
> Maybe not repeat the values set above but just reference them? I
reckon DRY
> should apply to comments too.

Done.

https://codereview.appspot.com/157830043/

Revision history for this message
Jay R. Wren (evarlast) wrote :
Revision history for this message
Francesco Banconi (frankban) wrote :

*** Submitted:

Support automatic detection of a logged in MAAS.

Automatically detect a logged in MAAS API, so that
it is possible to use the stored credentials to
create and bootstrap a MAAS environment without
user intervention.

QA:
- ssh to the GUI MAAS;
- destroy the existing environment;
- remove the ~/.juju directory;
- use the MAAS UI (http://maas.jujugui.org/MAAS/nodes/)
   to release the nodes if they are not in a ready state;
- this branch is already checked out in ~/juju-quickstart/sandbox/;
- start quickstart from there:
   cd ~/juju-quickstart/sandbox/ && .venv/bin/python juju-quickstart
- quickstart should show the option to automatically create and
   bootstrap the MAAS environment;
- select the option and wait for the envirnment to be ready:
   this can fail due to juju/maas interaction problems we currently
   have, retrying the process should eventually succeed.

Done, thank you!

R=bac, jay.wren
CC=
https://codereview.appspot.com/157830043

https://codereview.appspot.com/157830043/

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/cli/views.py'
2--- quickstart/cli/views.py 2014-06-16 10:20:42 +0000
3+++ quickstart/cli/views.py 2014-10-10 08:54:59 +0000
4@@ -103,6 +103,7 @@
5 import urwid
6
7 from quickstart import (
8+ maas,
9 platform_support,
10 settings,
11 )
12@@ -133,6 +134,18 @@
13 return err.return_value
14
15
16+def _save_and_exit(env_db, env_data, save_callable):
17+ """Add the new environment env_data to the env_db.
18+
19+ Exit the interactive session passing the newly saved environment, so that
20+ it is bootstrapped by the application.
21+ """
22+ envs.set_env_data(env_db, None, env_data)
23+ save_callable(env_db)
24+ # Use the newly created environment.
25+ raise ui.AppExit((env_db, env_data))
26+
27+
28 def env_index(app, env_type_db, env_db, save_callable):
29 """Show the Juju environments list.
30
31@@ -167,11 +180,16 @@
32 # Exit the interactive session selecting the newly created environment.
33 env_data = envs.create_local_env_data(
34 env_type_db, 'local', is_default=True)
35- # Add the new environment to the environments database.
36- envs.set_env_data(env_db, None, env_data)
37- save_callable(env_db)
38- # Use the newly created environment.
39- raise ui.AppExit((env_db, env_data))
40+ _save_and_exit(env_db, env_data, save_callable)
41+
42+ def create_and_start_maas_env(name, server, api_key):
43+ # Automatically create and use a MAAS environment.
44+ # This closure can only be called when there are no environments in the
45+ # database. For this reason, the new environment is set as default.
46+ # Exit the interactive session selecting the newly created environment.
47+ env_data = envs.create_maas_env_data(
48+ env_type_db, name, server, api_key, is_default=True)
49+ _save_and_exit(env_db, env_data, save_callable)
50
51 platform = platform_support.get_platform()
52 supports_local = platform_support.supports_local(platform)
53@@ -196,6 +214,32 @@
54 ('highlight', '$ juju quickstart -i'),
55 ]),
56 ]
57+ # If the MAAS CLI is available and it is logged in to a remote API,
58+ # offer to automatically create and bootstrap a MAAS environment with
59+ # the info retrieved by calling the MAAS CLI.
60+ if maas.cli_available():
61+ maas_api_data = maas.get_api_info()
62+ if maas_api_data is not None:
63+ maas_name, maas_server, maas_api_key = maas_api_data
64+ maas_callback = ui.thunk(
65+ create_and_start_maas_env,
66+ maas_name, maas_server, maas_api_key)
67+ widgets.extend([
68+ urwid.Text([
69+ '\nThe ',
70+ ('highlight', maas_name),
71+ ' MAAS (bare metal) remote API at ',
72+ ('highlight', maas_server),
73+ ' has been detected, and can be used to '
74+ 'automatically set up a MAAS environment. '
75+ 'To do that, just click the link below:',
76+ ]),
77+ urwid.Divider(),
78+ ui.MenuButton(
79+ '\N{BULLET} automatically create and bootstrap the {} '
80+ 'MAAS environment'.format(maas_name), maas_callback),
81+ ])
82+
83 # If the current platform supports local Juju environments, add an
84 # option to automatically create and bootstrap one.
85 if supports_local:
86@@ -204,7 +248,7 @@
87 '\nAt the bottom of the page you can find links to '
88 'manually create new environments. If you instead prefer '
89 'to quickly start your Juju experience in a local '
90- 'environment (LXC), just click the link below:'
91+ 'environment (LXC), just click the link below:',
92 ]),
93 urwid.Divider(),
94 ui.MenuButton(
95
96=== added file 'quickstart/maas.py'
97--- quickstart/maas.py 1970-01-01 00:00:00 +0000
98+++ quickstart/maas.py 2014-10-10 08:54:59 +0000
99@@ -0,0 +1,59 @@
100+# This file is part of the Juju Quickstart Plugin, which lets users set up a
101+# Juju environment in very few steps (https://launchpad.net/juju-quickstart).
102+# Copyright (C) 2014 Canonical Ltd.
103+#
104+# This program is free software: you can redistribute it and/or modify it under
105+# the terms of the GNU Affero General Public License version 3, as published by
106+# the Free Software Foundation.
107+#
108+# This program is distributed in the hope that it will be useful, but WITHOUT
109+# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
110+# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
111+# Affero General Public License for more details.
112+#
113+# You should have received a copy of the GNU Affero General Public License
114+# along with this program. If not, see <http://www.gnu.org/licenses/>.
115+
116+"""Juju Quickstart utilities for supporting MAAS."""
117+
118+from __future__ import unicode_literals
119+
120+import logging
121+import os
122+
123+from quickstart import (
124+ settings,
125+ utils,
126+)
127+
128+
129+def cli_available():
130+ """Report whether the MAAS CLI is available on the system."""
131+ path = settings.MAAS_CMD
132+ return os.path.isfile(path) and os.access(path, os.X_OK)
133+
134+
135+def get_api_info():
136+ """Return API info about the first logged in MAAS remote API.
137+
138+ The info is returned as a tuple including the API name, the MAAS server
139+ address and the MAAS OAuth API key.
140+
141+ Return None if no authenticated API is found.
142+ """
143+ retcode, output, error = utils.call(settings.MAAS_CMD, 'list')
144+ if retcode:
145+ # The MAAS CLI command failed. This is not supposed to happen, but
146+ # also not critical from the quickstart process perspective.
147+ logging.warn('unable to list MAAS remote APIs: ' + error)
148+ return None
149+ if not output:
150+ # No logged in remote API found.
151+ return None
152+ try:
153+ name, api_address, api_key = output.splitlines()[0].split()
154+ except ValueError:
155+ # The MAAS CLI returned an unexpected response.
156+ logging.warn('unexpected response from MAAS CLI: ' + output)
157+ return None
158+ return name, api_address.split('/api/')[0], api_key
159
160=== modified file 'quickstart/models/envs.py'
161--- quickstart/models/envs.py 2014-10-01 15:30:54 +0000
162+++ quickstart/models/envs.py 2014-10-10 08:54:59 +0000
163@@ -338,7 +338,7 @@
164
165
166 def create_local_env_data(env_type_db, name, is_default=False):
167- """Create and return an local (LXC) env_data.
168+ """Create and return a local (LXC) env_data.
169
170 Local environments' fields (except for name and type) are assumed to be
171 either optional or suitable for automatic generation of their values. For
172@@ -367,6 +367,29 @@
173 return env_data
174
175
176+def create_maas_env_data(env_type_db, name, server, api_key, is_default=False):
177+ """Create and return a MAAS (bare metal) env_data.
178+
179+ The env_data is created using the given name, MAAS server and MAAS OAuth
180+ API key.
181+ """
182+ env_data = {
183+ 'type': 'maas',
184+ 'name': name,
185+ 'is-default': is_default,
186+ 'maas-server': server,
187+ 'maas-oauth': api_key,
188+ }
189+ env_metadata = get_env_metadata(env_type_db, env_data)
190+ # Retrieve a list of missing required fields.
191+ missing_fields = [
192+ field for field in env_metadata['fields']
193+ if field.required and field.name not in env_data]
194+ # Assume all missing fields can be automatically generated.
195+ env_data.update((field.name, field.generate()) for field in missing_fields)
196+ return env_data
197+
198+
199 def remove_env(env_db, env_name):
200 """Remove the environment named env_name from the environments database.
201
202
203=== modified file 'quickstart/settings.py'
204--- quickstart/settings.py 2014-10-01 13:43:17 +0000
205+++ quickstart/settings.py 2014-10-10 08:54:59 +0000
206@@ -76,6 +76,9 @@
207 # The set of series supported by the Juju GUI charm.
208 JUJU_GUI_SUPPORTED_SERIES = tuple(DEFAULT_CHARM_URLS.keys())
209
210+# The path to the MAAS command line interface.
211+MAAS_CMD = '/usr/bin/maas'
212+
213 # The minimum Juju GUI charm revision supporting bundle deployments, for each
214 # supported series. Assume not listed series to always support bundles.
215 MINIMUM_REVISIONS_FOR_BUNDLES = collections.defaultdict(
216
217=== modified file 'quickstart/tests/cli/test_views.py'
218--- quickstart/tests/cli/test_views.py 2014-08-01 16:28:28 +0000
219+++ quickstart/tests/cli/test_views.py 2014-10-10 08:54:59 +0000
220@@ -18,7 +18,10 @@
221
222 from __future__ import unicode_literals
223
224-from contextlib import contextmanager
225+from contextlib import (
226+ contextmanager,
227+ nested,
228+)
229 import unittest
230
231 import mock
232@@ -36,6 +39,11 @@
233 from quickstart.tests.cli import helpers as cli_helpers
234
235
236+MAAS_NAME = 'maas-name'
237+MAAS_SERVER = 'http://1.2.3.4/MAAS'
238+MAAS_API_KEY = 'maas-secret'
239+
240+
241 def local_envs_supported(value):
242 """Simulate local environments support in the current platform.
243
244@@ -48,6 +56,26 @@
245 mock.Mock(return_value=value))
246
247
248+def maas_env_detected(value):
249+ """Simulate whether a logged in MAAS remote API has been detected.
250+
251+ The value argument is a boolean representing whether or not a MAAS API
252+ is available. If available, also patch "maas list" to return the following
253+ default values: MAAS_NAME, MAAS_SERVER and MAAS_API_KEY.
254+ Return a context manager that can be used when calling views.
255+ """
256+ patch_cli_available = mock.patch(
257+ 'quickstart.cli.views.maas.cli_available',
258+ mock.Mock(return_value=value))
259+ if value:
260+ return_value = (MAAS_NAME, MAAS_SERVER, MAAS_API_KEY)
261+ patch_get_api_info = mock.patch(
262+ 'quickstart.cli.views.maas.get_api_info',
263+ mock.Mock(return_value=return_value))
264+ return nested(patch_cli_available, patch_get_api_info)
265+ return patch_cli_available
266+
267+
268 class TestShow(unittest.TestCase):
269
270 @contextmanager
271@@ -139,6 +167,9 @@
272 base_status = ' \N{UPWARDS ARROW LEFTWARDS OF DOWNWARDS ARROW} navigate '
273 create_local_caption = (
274 '\N{BULLET} automatically create and bootstrap a local environment')
275+ create_maas_caption = (
276+ '\N{BULLET} automatically create and bootstrap the {} MAAS '
277+ 'environment'.format(MAAS_NAME))
278
279 def test_view_default_return_value_on_exit(self):
280 # The view configures the app so that the return value on user exit is
281@@ -257,9 +288,10 @@
282 # If that option is clicked, the view quits the application returning
283 # the newly created env_data.
284 env_db = envs.create_empty_env_db()
285- with local_envs_supported(True):
286- views.env_index(
287- self.app, self.env_type_db, env_db, self.save_callable)
288+ with maas_env_detected(False):
289+ with local_envs_supported(True):
290+ views.env_index(
291+ self.app, self.env_type_db, env_db, self.save_callable)
292 buttons = self.get_widgets_in_contents(
293 filter_function=self.is_a(ui.MenuButton))
294 # The "create and bootstrap" button is the first one in the contents.
295@@ -285,10 +317,49 @@
296 self.app, self.env_type_db, env_db, self.save_callable)
297 buttons = self.get_widgets_in_contents(
298 filter_function=self.is_a(ui.MenuButton))
299- # No "create and bootstrap" buttons are present.
300+ # No "create and bootstrap local" buttons are present.
301 captions = map(cli_helpers.get_button_caption, buttons)
302 self.assertNotIn(self.create_local_caption, captions)
303
304+ def test_create_and_bootstrap_maas_environment_clicked(self):
305+ # When there are no environments in the env_db and a remote MAAS API is
306+ # detected, the view exposes an option to automatically create and
307+ # bootstrap a new MAAS environment.
308+ # If that option is clicked, the view quits the application returning
309+ # the newly created env_data.
310+ env_db = envs.create_empty_env_db()
311+ with maas_env_detected(True):
312+ views.env_index(
313+ self.app, self.env_type_db, env_db, self.save_callable)
314+ buttons = self.get_widgets_in_contents(
315+ filter_function=self.is_a(ui.MenuButton))
316+ # The "create and bootstrap" button is the first one in the contents.
317+ create_button = buttons[0]
318+ self.assertEqual(
319+ self.create_maas_caption,
320+ cli_helpers.get_button_caption(create_button))
321+ # An AppExit is raised clicking the button.
322+ with self.assertRaises(ui.AppExit) as context_manager:
323+ cli_helpers.emit(create_button)
324+ new_env_db, env_data = context_manager.exception.return_value
325+ # The environments database is no longer empty.
326+ self.assertIn(MAAS_NAME, new_env_db['environments'])
327+ self.assertEqual(envs.get_env_data(new_env_db, MAAS_NAME), env_data)
328+
329+ def test_create_and_bootstrap_maas_environment_missing(self):
330+ # The option to automatically create and bootstrap a new MAAS
331+ # environment is not displayed if no MAAS API endpoints are
332+ # available on the system
333+ env_db = envs.create_empty_env_db()
334+ with maas_env_detected(False):
335+ views.env_index(
336+ self.app, self.env_type_db, env_db, self.save_callable)
337+ buttons = self.get_widgets_in_contents(
338+ filter_function=self.is_a(ui.MenuButton))
339+ # No "create and bootstrap MAAS" buttons are present.
340+ captions = map(cli_helpers.get_button_caption, buttons)
341+ self.assertNotIn(self.create_maas_caption, captions)
342+
343 def test_selected_environment(self):
344 # The default environment is already selected in the list.
345 env_db = helpers.make_env_db(default='lxc')
346
347=== modified file 'quickstart/tests/models/test_envs.py'
348--- quickstart/tests/models/test_envs.py 2014-09-29 13:50:52 +0000
349+++ quickstart/tests/models/test_envs.py 2014-10-10 08:54:59 +0000
350@@ -644,6 +644,41 @@
351 self.assertEqual(expected_env_data, env_data)
352
353
354+class TestCreateMaasEnvData(unittest.TestCase):
355+
356+ def setUp(self):
357+ # Store the env_type_db.
358+ self.env_type_db = envs.get_env_type_db()
359+
360+ def test_not_default(self):
361+ # The resulting env_data is correctly structured for non default envs.
362+ env_data = envs.create_maas_env_data(
363+ self.env_type_db, 'my-maas', 'http://1.2.3.4/MAAS', 'Secret!',
364+ is_default=False)
365+ expected_env_data = {
366+ 'type': 'maas',
367+ 'name': 'my-maas',
368+ 'maas-server': 'http://1.2.3.4/MAAS',
369+ 'maas-oauth': 'Secret!',
370+ 'is-default': False,
371+ }
372+ self.assertEqual(expected_env_data, env_data)
373+
374+ def test_default(self):
375+ # The resulting env_data is correctly structured for default envs.
376+ env_data = envs.create_maas_env_data(
377+ self.env_type_db, 'another-maas', 'http://1.2.3.4/MAAS', 'Boo!',
378+ is_default=True)
379+ expected_env_data = {
380+ 'type': 'maas',
381+ 'name': 'another-maas',
382+ 'maas-server': 'http://1.2.3.4/MAAS',
383+ 'maas-oauth': 'Boo!',
384+ 'is-default': True,
385+ }
386+ self.assertEqual(expected_env_data, env_data)
387+
388+
389 class TestRemoveEnv(
390 EnvDataTestsMixin, helpers.ValueErrorTestsMixin, unittest.TestCase):
391
392
393=== added file 'quickstart/tests/test_maas.py'
394--- quickstart/tests/test_maas.py 1970-01-01 00:00:00 +0000
395+++ quickstart/tests/test_maas.py 2014-10-10 08:54:59 +0000
396@@ -0,0 +1,98 @@
397+# This file is part of the Juju Quickstart Plugin, which lets users set up a
398+# Juju environment in very few steps (https://launchpad.net/juju-quickstart).
399+# Copyright (C) 2014 Canonical Ltd.
400+#
401+# This program is free software: you can redistribute it and/or modify it under
402+# the terms of the GNU Affero General Public License version 3, as published by
403+# the Free Software Foundation.
404+#
405+# This program is distributed in the hope that it will be useful, but WITHOUT
406+# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
407+# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
408+# Affero General Public License for more details.
409+#
410+# You should have received a copy of the GNU Affero General Public License
411+# along with this program. If not, see <http://www.gnu.org/licenses/>.
412+
413+"""Tests for the Juju Quickstart MAAS support."""
414+
415+from __future__ import unicode_literals
416+
417+import os
418+import shutil
419+import tempfile
420+import unittest
421+
422+import mock
423+
424+from quickstart import maas
425+from quickstart.tests import helpers
426+
427+
428+class TestCliAvailable(unittest.TestCase):
429+
430+ def setUp(self):
431+ # Set up a a container for MAAS CLI tests.
432+ self.playground = tempfile.mkdtemp()
433+ self.addCleanup(shutil.rmtree, self.playground)
434+
435+ def test_not_found(self):
436+ # The MAAS CLI is not installed and cannot be found.
437+ path = os.path.join(self.playground, 'no-such')
438+ with mock.patch('quickstart.maas.settings.MAAS_CMD', path):
439+ self.assertFalse(maas.cli_available())
440+
441+ def test_not_executable(self):
442+ # The file is not executable and for this reason the MAAS CLI is not
443+ # considered available.
444+ path = os.path.join(self.playground, 'not-executable')
445+ open(path, 'w').close()
446+ with mock.patch('quickstart.maas.settings.MAAS_CMD', path):
447+ self.assertFalse(maas.cli_available())
448+
449+ def test_available(self):
450+ # The MAAS CLI is there and ready to be called.
451+ path = os.path.join(self.playground, 'executable')
452+ open(path, 'w').close()
453+ os.chmod(path, 0744)
454+ with mock.patch('quickstart.maas.settings.MAAS_CMD', path):
455+ self.assertTrue(maas.cli_available())
456+
457+
458+class TestGetApiInfo(helpers.CallTestsMixin, unittest.TestCase):
459+
460+ def test_command_error(self):
461+ # None is returned and a warning is printed if the MAAS command returns
462+ # an error.
463+ expected_log = 'unable to list MAAS remote APIs: bad wolf'
464+ with self.patch_call(1, '', 'bad wolf'):
465+ with helpers.assert_logs([expected_log], level='warn'):
466+ api_info = maas.get_api_info()
467+ self.assertIsNone(api_info)
468+
469+ def test_no_apis(self):
470+ # None is returned if no remote APIs are detected.
471+ with self.patch_call(0, '', ''):
472+ with helpers.assert_logs([], level='warn'):
473+ api_info = maas.get_api_info()
474+ self.assertIsNone(api_info)
475+
476+ def test_malformed_output(self):
477+ # None is returned and a warning is printed if the MAAS command returns
478+ # an unexpected output.
479+ expected_log = 'unexpected response from MAAS CLI: exterminate!'
480+ with self.patch_call(0, 'exterminate!', ''):
481+ with helpers.assert_logs([expected_log], level='warn'):
482+ api_info = maas.get_api_info()
483+ self.assertIsNone(api_info)
484+
485+ def test_info(self):
486+ # If at least one remote MAAS API is detected, then a tuple including
487+ # the MAAS name, address and API key is returned.
488+ output = 'maas-name http://1.2.3.4/MAAS/api/v1 maas:secret!'
489+ with self.patch_call(0, output, ''):
490+ with helpers.assert_logs([], level='warn'):
491+ api_info = maas.get_api_info()
492+ self.assertEqual(
493+ ('maas-name', 'http://1.2.3.4/MAAS', 'maas:secret!'),
494+ api_info)

Subscribers

People subscribed via source and target branches