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
=== modified file 'maastest/maasfixture.py'
--- maastest/maasfixture.py 2014-04-08 10:57:13 +0000
+++ maastest/maasfixture.py 2014-04-09 11:15:47 +0000
@@ -17,6 +17,7 @@
17 ]17 ]
1818
1919
20from datetime import timedelta
20import httplib21import httplib
21import json22import json
22import logging23import logging
@@ -75,6 +76,10 @@
75]76]
7677
7778
79class NoBootImagesError(Exception):
80 """No boot images present on the master cluster."""
81
82
78class MAASFixture(Fixture):83class MAASFixture(Fixture):
79 """A fixture for a MAAS server."""84 """A fixture for a MAAS server."""
8085
@@ -241,6 +246,28 @@
241 check_call=True)246 check_call=True)
242 logging.info("Done importing boot images.")247 logging.info("Done importing boot images.")
243248
249 def wait_until_boot_images_scanned(self):
250 """Wait until the master cluster has reported boot images."""
251 # Poll until the boot images have been reported.
252 timeout = timedelta(minutes=10).total_seconds()
253 for _ in utils.retries(timeout=timeout, delay=10):
254 boot_images = self.list_boot_images()
255 if len(boot_images) > 0:
256 return
257
258 raise NoBootImagesError(
259 "Boot image download timed out: cluster reported no images.")
260
261 def list_boot_images(self):
262 """Return the boot images of the master cluster."""
263 ng_uuid = self.get_master_ng_uuid()
264 uri = utils.get_uri('nodegroups/%s/boot-images/' % ng_uuid)
265 response = self.admin_maas_client.get(uri)
266 if response.code != httplib.OK:
267 raise Exception(
268 "Error getting boot images for cluster '%s'" % ng_uuid)
269 return json.loads(response.read())
270
244 def configure(self, name, value):271 def configure(self, name, value):
245 """Set a config value in MAAS."""272 """Set a config value in MAAS."""
246 uri = utils.get_uri('maas/')273 uri = utils.get_uri('maas/')
@@ -293,6 +320,7 @@
293 self.import_maas_images(320 self.import_maas_images(
294 series=self.series, architecture=self.architecture,321 series=self.series, architecture=self.architecture,
295 simplestreams_filter=self.simplestreams_filter)322 simplestreams_filter=self.simplestreams_filter)
323 self.wait_until_boot_images_scanned()
296 self.configure_default_series(self.series)324 self.configure_default_series(self.series)
297 if self.kvm_fixture.direct_ip is not None:325 if self.kvm_fixture.direct_ip is not None:
298 self.check_cluster_connected()326 self.check_cluster_connected()
299327
=== modified file 'maastest/tests/test_maasfixture.py'
--- maastest/tests/test_maasfixture.py 2014-04-09 06:57:50 +0000
+++ maastest/tests/test_maasfixture.py 2014-04-09 11:15:47 +0000
@@ -31,6 +31,7 @@
31 utils,31 utils,
32 )32 )
33from maastest.maas_enums import NODEGROUPINTERFACE_MANAGEMENT33from maastest.maas_enums import NODEGROUPINTERFACE_MANAGEMENT
34from maastest.maasfixture import NoBootImagesError
34from maastest.testing.factory import make_file35from maastest.testing.factory import make_file
35import mock36import mock
36import netaddr37import netaddr
@@ -416,6 +417,9 @@
416 self.patch(417 self.patch(
417 maas_fixture, 'query_api_key',418 maas_fixture, 'query_api_key',
418 mock.MagicMock(return_value='fake:api:key'))419 mock.MagicMock(return_value='fake:api:key'))
420 self.patch(
421 maas_fixture, 'wait_until_boot_images_scanned', mock.MagicMock())
422
419 with maas_fixture:423 with maas_fixture:
420 pass424 pass
421 mock_logging = mock.MagicMock()425 mock_logging = mock.MagicMock()
@@ -570,6 +574,8 @@
570 key = 'fake:api:key'574 key = 'fake:api:key'
571 self.patch(575 self.patch(
572 maas_fixture, 'query_api_key', mock.MagicMock(return_value=key))576 maas_fixture, 'query_api_key', mock.MagicMock(return_value=key))
577 self.patch(
578 maas_fixture, 'wait_until_boot_images_scanned', mock.MagicMock())
573579
574 with maas_fixture:580 with maas_fixture:
575 pass581 pass
@@ -595,7 +601,8 @@
595 mock.MagicMock(return_value='fake:api:key'))601 mock.MagicMock(return_value='fake:api:key'))
596 self.patch(602 self.patch(
597 maas_fixture, 'collect_logs', mock.MagicMock())603 maas_fixture, 'collect_logs', mock.MagicMock())
598604 self.patch(
605 maas_fixture, 'wait_until_boot_images_scanned', mock.MagicMock())
599 with maas_fixture:606 with maas_fixture:
600 pass607 pass
601608
@@ -663,6 +670,8 @@
663 maas_fixture, 'import_maas_images', mock.MagicMock())670 maas_fixture, 'import_maas_images', mock.MagicMock())
664 self.patch(671 self.patch(
665 maas_fixture, 'log_connection_details', mock.MagicMock())672 maas_fixture, 'log_connection_details', mock.MagicMock())
673 self.patch(
674 maas_fixture, 'wait_until_boot_images_scanned', mock.MagicMock())
666675
667 with maas_fixture:676 with maas_fixture:
668 pass677 pass
@@ -696,6 +705,8 @@
696 mock.MagicMock(return_value='fake:api:key'))705 mock.MagicMock(return_value='fake:api:key'))
697 self.patch(706 self.patch(
698 maas_fixture, 'configure_http_proxy', mock.MagicMock())707 maas_fixture, 'configure_http_proxy', mock.MagicMock())
708 self.patch(
709 maas_fixture, 'wait_until_boot_images_scanned', mock.MagicMock())
699710
700 with maas_fixture:711 with maas_fixture:
701 pass712 pass
@@ -719,6 +730,8 @@
719 maas_fixture, 'check_cluster_connected', mock.MagicMock())730 maas_fixture, 'check_cluster_connected', mock.MagicMock())
720 self.patch(731 self.patch(
721 maas_fixture, 'configure_cluster', mock.MagicMock())732 maas_fixture, 'configure_cluster', mock.MagicMock())
733 self.patch(
734 maas_fixture, 'wait_until_boot_images_scanned', mock.MagicMock())
722735
723 with maas_fixture:736 with maas_fixture:
724 pass737 pass
@@ -767,3 +780,44 @@
767 'sudo', 'maas-region-admin', 'dumpdata',780 'sudo', 'maas-region-admin', 'dumpdata',
768 'metadataserver.NodeCommissionResult']),781 'metadataserver.NodeCommissionResult']),
769 maas_fixture.kvm_fixture.run_command.mock_calls)782 maas_fixture.kvm_fixture.run_command.mock_calls)
783
784 def test_wait_until_boot_images_scanned_times_out_eventually(self):
785 maas_fixture = self.make_maas_fixture()
786 self.patch(
787 maas_fixture, 'list_boot_images',
788 mock.MagicMock(return_value=[]))
789 self.patch(
790 utils, 'retries', mock.MagicMock(return_value=[(0, 1), (0, 1)]))
791
792 exception = self.assertRaises(
793 NoBootImagesError, maas_fixture.wait_until_boot_images_scanned)
794 message = text_type(exception)
795 self.assertIn(
796 "Boot image download timed out: cluster reported no images.",
797 message)
798
799 def test_wait_until_boot_images_scanned_returns_with_error(self):
800 maas_fixture = self.make_maas_fixture()
801 self.patch(
802 maas_fixture, 'list_boot_images',
803 mock.MagicMock(return_value=['non-empty1', 'non-empty2']))
804
805 self.assertEqual(None, maas_fixture.wait_until_boot_images_scanned())
806
807 def test_list_boot_images_lists_images(self):
808 maas_fixture = self.make_maas_fixture()
809 response = [{'fake-response': self.getUniqueString()}]
810 self.patch(maas_fixture, 'admin_maas_client', mock.MagicMock())
811 call_mock = self.patch_MAASClient(
812 maas_fixture.admin_maas_client, 'get', response)
813 uuid = self.getUniqueString()
814 self.patch(
815 maas_fixture, 'get_master_ng_uuid',
816 mock.MagicMock(return_value=uuid))
817
818 self.assertEqual(
819 (
820 response,
821 [mock.call('/api/1.0/nodegroups/%s/boot-images/' % uuid)],
822 ),
823 (maas_fixture.list_boot_images(), call_mock.mock_calls))

Subscribers

People subscribed via source and target branches