Merge lp:~frankban/juju-quickstart/maas-detect into lp:juju-quickstart
- maas-detect
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju GUI Hackers | Pending | ||
Review via email: mp+237910@code.launchpad.net |
Commit message
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://
to release the nodes if they are not in a ready state;
- this branch is already checked out in ~/juju-
- start quickstart from there:
cd ~/juju-
- 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!
Francesco Banconi (frankban) wrote : | # |
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!
Brad Crittenden (bac) wrote : | # |
LGTM
https:/
File quickstart/maas.py (right):
https:/
quickstart/
s/if if/if/
https:/
File quickstart/
https:/
quickstart/
automatically generated.
What happens if this assumption is wrong? Will field.generate() raise an
error?
https:/
File quickstart/
https:/
quickstart/
"http://
Maybe not repeat the values set above but just reference them? I reckon
DRY should apply to comments too.
- 111. By Francesco Banconi
-
Fixt a typo and improve test comments.
Francesco Banconi (frankban) wrote : | # |
Please take a look.
https:/
File quickstart/maas.py (right):
https:/
quickstart/
On 2014/10/10 08:48:53, bac wrote:
> s/if if/if/
Done.
https:/
File quickstart/
https:/
quickstart/
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:/
File quickstart/
https:/
quickstart/
"http://
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.
Jay R. Wren (evarlast) wrote : | # |
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://
to release the nodes if they are not in a ready state;
- this branch is already checked out in ~/juju-
- start quickstart from there:
cd ~/juju-
- 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:/
Francesco Banconi (frankban) wrote : | # |
Thanks for the reviews!
Preview Diff
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) |
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: maas.jujugui. org/MAAS/ nodes/) quickstart/ sandbox/ ; quickstart/ sandbox/ && .venv/bin/python juju-quickstart
- ssh to the GUI MAAS;
- destroy the existing environment;
- remove the ~/.juju directory;
- use the MAAS UI (http://
to release the nodes if they are not in a ready state;
- this branch is already checked out in ~/juju-
- start quickstart from there:
cd ~/juju-
- 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): cli/views. py models/ envs.py settings. py tests/cli/ test_views. py tests/models/ test_envs. py tests/test_ maas.py
A [revision details]
M quickstart/
A quickstart/maas.py
M quickstart/
M quickstart/
M quickstart/
M quickstart/
A quickstart/