Merge lp:~rvb/maas-test/import-images-api into lp:maas-test

Proposed by Raphaël Badin
Status: Merged
Approved by: Raphaël Badin
Approved revision: 152
Merged at revision: 147
Proposed branch: lp:~rvb/maas-test/import-images-api
Merge into: lp:maas-test
Diff against target: 172 lines (+83/-1)
2 files modified
maastest/maasfixture.py (+28/-0)
maastest/tests/test_maasfixture.py (+55/-1)
To merge this branch: bzr merge lp:~rvb/maas-test/import-images-api
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) Approve
Review via email: mp+214913@code.launchpad.net

Commit message

No node can be booted until the boot images have been reported back to the region: wait until the report_boot_images task has populated the BootImage table.

Description of the change

I've tested this change, with success, in the lab: http://d-jenkins.ubuntu-ci:8080/view/MAAS/job/maas-test-manual/55/console.

To post a comment you must log in.
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

Thanks for fixing this — and let's not forget, diagnosing it!

Notes as usual:

Maybe in the docstring for wait_until_boot_images_scanned, say “until the master cluster has reported boot images” (so as not to give the impression that we just wait for them to appear in /var/lib/maas)?

.

Any particular reason why wait_until_boot_images_scanned returns the number of images? It seems a bit arbitrary (why not the actual list, for instance?) and introduces testing for something that's not actually used. Why not just return nothing, and document the fact that either a timeout or a failure to request the listing will raise an exception?

By the way, the initial assignment for nb_images looks dead to me.

.

Also in wait_until_boot_images_scanned, it turns out that RuntimeError isn't really for this kind of error, but rather for errors originating in the Python runtime. And, it's deprecated anyway. Better derive an ad-hoc exception class from Exception.

The exception's error message says “cluster” instead of “the cluster,” which makes it look a little weird to me. But maybe it would be more helpful to say e.g. “Boot image download timed out: cluster reported no images”?

.

In test_maasfixture.py, I wonder if it would make sense to make the patch for wait_until_boot_images_scanned a standard part of make_maas_fixture, with an option to skip it?

.

What's going on with the separate make_kvm_fixture steps? AFAIK you don't need to do that — just call make_maas_fixture and it will create a KVMFixture for you. In test_list_boot_images_lists_images there's even a commented-out line that did it that way. Is there a difference? Or was this a transient change during development that is no longer needed?

.

Setup gets a little lengthy in test_list_boot_images_lists_images. Might it make sense to extract this bit into a helper?

        uuid = self.getUniqueString()
        self.patch(
            maas_fixture, 'get_master_ng_uuid',
            mock.MagicMock(return_value=uuid))

review: Approve
lp:~rvb/maas-test/import-images-api updated
152. By Raphaël Badin

Review fixes.

Revision history for this message
Raphaël Badin (rvb) wrote :

> Thanks for fixing this — and let's not forget, diagnosing it!
>
> Notes as usual:
>
> Maybe in the docstring for wait_until_boot_images_scanned, say “until the
> master cluster has reported boot images” (so as not to give the impression
> that we just wait for them to appear in /var/lib/maas)?

Right, done.

> Any particular reason why wait_until_boot_images_scanned returns the number of
> images? It seems a bit arbitrary (why not the actual list, for instance?) and
> introduces testing for something that's not actually used. Why not just
> return nothing, and document the fact that either a timeout or a failure to
> request the listing will raise an exception?

Okay, done.

> Also in wait_until_boot_images_scanned, it turns out that RuntimeError isn't
> really for this kind of error, but rather for errors originating in the Python
> runtime. And, it's deprecated anyway. Better derive an ad-hoc exception
> class from Exception.
>
> The exception's error message says “cluster” instead of “the cluster,” which
> makes it look a little weird to me. But maybe it would be more helpful to say
> e.g. “Boot image download timed out: cluster reported no images”?

Done.

> In test_maasfixture.py, I wonder if it would make sense to make the patch for
> wait_until_boot_images_scanned a standard part of make_maas_fixture, with an
> option to skip it?

I'd rather not do that, I know all the setup steps look a bit redundant but I'd rather keep the setup explicit than mess things up with helpers that I fear will soon become spaghetti code.

> What's going on with the separate make_kvm_fixture steps? AFAIK you don't
> need to do that — just call make_maas_fixture and it will create a KVMFixture
> for you. In test_list_boot_images_lists_images there's even a commented-out
> line that did it that way. Is there a difference? Or was this a transient
> change during development that is no longer needed?

True, fixed.

> Setup gets a little lengthy in test_list_boot_images_lists_images. Might it
> make sense to extract this bit into a helper?
>
> uuid = self.getUniqueString()
> self.patch(
> maas_fixture, 'get_master_ng_uuid',
> mock.MagicMock(return_value=uuid))

Again, I don't think it's worth it.

Thanks for the review!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'maastest/maasfixture.py'
2--- maastest/maasfixture.py 2014-04-08 10:57:13 +0000
3+++ maastest/maasfixture.py 2014-04-09 11:15:47 +0000
4@@ -17,6 +17,7 @@
5 ]
6
7
8+from datetime import timedelta
9 import httplib
10 import json
11 import logging
12@@ -75,6 +76,10 @@
13 ]
14
15
16+class NoBootImagesError(Exception):
17+ """No boot images present on the master cluster."""
18+
19+
20 class MAASFixture(Fixture):
21 """A fixture for a MAAS server."""
22
23@@ -241,6 +246,28 @@
24 check_call=True)
25 logging.info("Done importing boot images.")
26
27+ def wait_until_boot_images_scanned(self):
28+ """Wait until the master cluster has reported boot images."""
29+ # Poll until the boot images have been reported.
30+ timeout = timedelta(minutes=10).total_seconds()
31+ for _ in utils.retries(timeout=timeout, delay=10):
32+ boot_images = self.list_boot_images()
33+ if len(boot_images) > 0:
34+ return
35+
36+ raise NoBootImagesError(
37+ "Boot image download timed out: cluster reported no images.")
38+
39+ def list_boot_images(self):
40+ """Return the boot images of the master cluster."""
41+ ng_uuid = self.get_master_ng_uuid()
42+ uri = utils.get_uri('nodegroups/%s/boot-images/' % ng_uuid)
43+ response = self.admin_maas_client.get(uri)
44+ if response.code != httplib.OK:
45+ raise Exception(
46+ "Error getting boot images for cluster '%s'" % ng_uuid)
47+ return json.loads(response.read())
48+
49 def configure(self, name, value):
50 """Set a config value in MAAS."""
51 uri = utils.get_uri('maas/')
52@@ -293,6 +320,7 @@
53 self.import_maas_images(
54 series=self.series, architecture=self.architecture,
55 simplestreams_filter=self.simplestreams_filter)
56+ self.wait_until_boot_images_scanned()
57 self.configure_default_series(self.series)
58 if self.kvm_fixture.direct_ip is not None:
59 self.check_cluster_connected()
60
61=== modified file 'maastest/tests/test_maasfixture.py'
62--- maastest/tests/test_maasfixture.py 2014-04-09 06:57:50 +0000
63+++ maastest/tests/test_maasfixture.py 2014-04-09 11:15:47 +0000
64@@ -31,6 +31,7 @@
65 utils,
66 )
67 from maastest.maas_enums import NODEGROUPINTERFACE_MANAGEMENT
68+from maastest.maasfixture import NoBootImagesError
69 from maastest.testing.factory import make_file
70 import mock
71 import netaddr
72@@ -416,6 +417,9 @@
73 self.patch(
74 maas_fixture, 'query_api_key',
75 mock.MagicMock(return_value='fake:api:key'))
76+ self.patch(
77+ maas_fixture, 'wait_until_boot_images_scanned', mock.MagicMock())
78+
79 with maas_fixture:
80 pass
81 mock_logging = mock.MagicMock()
82@@ -570,6 +574,8 @@
83 key = 'fake:api:key'
84 self.patch(
85 maas_fixture, 'query_api_key', mock.MagicMock(return_value=key))
86+ self.patch(
87+ maas_fixture, 'wait_until_boot_images_scanned', mock.MagicMock())
88
89 with maas_fixture:
90 pass
91@@ -595,7 +601,8 @@
92 mock.MagicMock(return_value='fake:api:key'))
93 self.patch(
94 maas_fixture, 'collect_logs', mock.MagicMock())
95-
96+ self.patch(
97+ maas_fixture, 'wait_until_boot_images_scanned', mock.MagicMock())
98 with maas_fixture:
99 pass
100
101@@ -663,6 +670,8 @@
102 maas_fixture, 'import_maas_images', mock.MagicMock())
103 self.patch(
104 maas_fixture, 'log_connection_details', mock.MagicMock())
105+ self.patch(
106+ maas_fixture, 'wait_until_boot_images_scanned', mock.MagicMock())
107
108 with maas_fixture:
109 pass
110@@ -696,6 +705,8 @@
111 mock.MagicMock(return_value='fake:api:key'))
112 self.patch(
113 maas_fixture, 'configure_http_proxy', mock.MagicMock())
114+ self.patch(
115+ maas_fixture, 'wait_until_boot_images_scanned', mock.MagicMock())
116
117 with maas_fixture:
118 pass
119@@ -719,6 +730,8 @@
120 maas_fixture, 'check_cluster_connected', mock.MagicMock())
121 self.patch(
122 maas_fixture, 'configure_cluster', mock.MagicMock())
123+ self.patch(
124+ maas_fixture, 'wait_until_boot_images_scanned', mock.MagicMock())
125
126 with maas_fixture:
127 pass
128@@ -767,3 +780,44 @@
129 'sudo', 'maas-region-admin', 'dumpdata',
130 'metadataserver.NodeCommissionResult']),
131 maas_fixture.kvm_fixture.run_command.mock_calls)
132+
133+ def test_wait_until_boot_images_scanned_times_out_eventually(self):
134+ maas_fixture = self.make_maas_fixture()
135+ self.patch(
136+ maas_fixture, 'list_boot_images',
137+ mock.MagicMock(return_value=[]))
138+ self.patch(
139+ utils, 'retries', mock.MagicMock(return_value=[(0, 1), (0, 1)]))
140+
141+ exception = self.assertRaises(
142+ NoBootImagesError, maas_fixture.wait_until_boot_images_scanned)
143+ message = text_type(exception)
144+ self.assertIn(
145+ "Boot image download timed out: cluster reported no images.",
146+ message)
147+
148+ def test_wait_until_boot_images_scanned_returns_with_error(self):
149+ maas_fixture = self.make_maas_fixture()
150+ self.patch(
151+ maas_fixture, 'list_boot_images',
152+ mock.MagicMock(return_value=['non-empty1', 'non-empty2']))
153+
154+ self.assertEqual(None, maas_fixture.wait_until_boot_images_scanned())
155+
156+ def test_list_boot_images_lists_images(self):
157+ maas_fixture = self.make_maas_fixture()
158+ response = [{'fake-response': self.getUniqueString()}]
159+ self.patch(maas_fixture, 'admin_maas_client', mock.MagicMock())
160+ call_mock = self.patch_MAASClient(
161+ maas_fixture.admin_maas_client, 'get', response)
162+ uuid = self.getUniqueString()
163+ self.patch(
164+ maas_fixture, 'get_master_ng_uuid',
165+ mock.MagicMock(return_value=uuid))
166+
167+ self.assertEqual(
168+ (
169+ response,
170+ [mock.call('/api/1.0/nodegroups/%s/boot-images/' % uuid)],
171+ ),
172+ (maas_fixture.list_boot_images(), call_mock.mock_calls))

Subscribers

People subscribed via source and target branches