~ubuntu-branches/ubuntu/utopic/maas/utopic

« back to all changes in this revision

Viewing changes to src/provisioningserver/pserv_services/tests/test_image_download_service.py

  • Committer: Package Import Robot
  • Author(s): Andres Rodriguez, Jeroen Vermeulen, Andres Rodriguez, Jason Hobbs, Raphaël Badin, Louis Bouchard, Gavin Panella
  • Date: 2014-08-21 19:36:30 UTC
  • mfrom: (1.3.1)
  • Revision ID: package-import@ubuntu.com-20140821193630-kertpu5hd8yyss8h
Tags: 1.7.0~beta7+bzr3266-0ubuntu1
* New Upstream Snapshot, Beta 7 bzr3266

[ Jeroen Vermeulen ]
* debian/extras/99-maas-sudoers
  debian/maas-dhcp.postinst
  debian/rules
  - Add second DHCP server instance for IPv6.
* debian/maas-region-controller-min.install
  debian/maas-region-controller-min.lintian-overrides
  - Install deployment user-data: maas_configure_interfaces.py script.
* debian/maas-cluster-controller.links
  debian/maas-cluster-controller.install
  debian/maas-cluster-controller.postinst
  - Reflect Celery removal changes made in trunk r3067.
  - Don't install celeryconfig_cluster.py any longer. 
  - Don't install maas_local_celeryconfig_cluster.py any longer.
  - Don't symlink maas_local_celeryconfig_cluster.py from /etc to /usr.
  - Don't insert UUID into maas_local_celeryconfig_cluster.py.

[ Andres Rodriguez ]
* debian/maas-region-controller-min.postrm: Cleanup lefover files.
* debian/maas-dhcp.postrm: Clean leftover configs.
* Provide new maas-proxy package that replaces the usage of
  squid-deb-proxy:
  - debian/control: New maas-proxy package that replaces the usage
    of squid-deb-proxy; Drop depends on squid-deb-proxy.
  - Add upstrart job.
  - Ensure squid3 is stopped as maas-proxy uses a caching proxy.
* Remove Celery references to cluster controller:
  - Rename upstart job from maas-pserv to maas-cluster; rename
    maas-cluster-celery to maas-cluster-register. Ensure services
    are stopped on upgrade.
  - debian/maintscript: Cleanup config files.
  - Remove all references to the MAAS celery daemon and config
    files as we don't use it like that anymore
* Move some entries in debian/maintscript to
  debian/maas-cluster-controller.maintscript
* Remove usage of txlongpoll and rabbitmq-server. Handle upgrades
  to ensure these are removed correctly.

[ Jason Hobbs ]
* debian/maas-region-controller-min.install: Install
  maas-generate-winrm-cert script.

[ Raphaël Badin ]
* debian/extras/maas-region-admin: Bypass django-admin as it prints
  spurious messages to stdout (LP: #1365130).

[Louis Bouchard]
* debian/maas-cluster-controller.postinst:
  - Exclude /var/log/maas/rsyslog when changing ownership
    (LP: #1346703)

[Gavin Panella]
* debian/maas-cluster-controller.maas-clusterd.upstart:
  - Don't start-up the cluster controller unless a shared-secret has
    been installed.
* debian/maas-cluster-controller.maas-cluster-register.upstart: Drop.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2014 Canonical Ltd.  This software is licensed under the
 
2
# GNU Affero General Public License version 3 (see the file LICENSE).
 
3
 
 
4
"""Tests for provisioningserver.pserv_services.image_download_service"""
 
5
 
 
6
from __future__ import (
 
7
    absolute_import,
 
8
    print_function,
 
9
    unicode_literals,
 
10
    )
 
11
 
 
12
str = None
 
13
 
 
14
__metaclass__ = type
 
15
__all__ = []
 
16
 
 
17
 
 
18
from datetime import timedelta
 
19
 
 
20
from fixtures import FakeLogger
 
21
from maastesting.factory import factory
 
22
from maastesting.matchers import (
 
23
    get_mock_calls,
 
24
    MockCalledOnceWith,
 
25
    MockCallsMatch,
 
26
    MockNotCalled,
 
27
    )
 
28
from maastesting.testcase import MAASTwistedRunTest
 
29
from mock import (
 
30
    call,
 
31
    Mock,
 
32
    sentinel,
 
33
    )
 
34
from provisioningserver.boot import tftppath
 
35
from provisioningserver.pserv_services.image_download_service import (
 
36
    ImageDownloadService,
 
37
    )
 
38
from provisioningserver.rpc import boot_images
 
39
from provisioningserver.rpc.boot_images import _run_import
 
40
from provisioningserver.rpc.exceptions import NoConnectionsAvailable
 
41
from provisioningserver.rpc.region import (
 
42
    GetBootSources,
 
43
    GetBootSourcesV2,
 
44
    )
 
45
from provisioningserver.rpc.testing import TwistedLoggerFixture
 
46
from provisioningserver.testing.testcase import PservTestCase
 
47
from testtools.deferredruntest import extract_result
 
48
from twisted.application.internet import TimerService
 
49
from twisted.internet import defer
 
50
from twisted.internet.task import Clock
 
51
from twisted.spread.pb import NoSuchMethod
 
52
 
 
53
 
 
54
class TestPeriodicImageDownloadService(PservTestCase):
 
55
 
 
56
    run_tests_with = MAASTwistedRunTest.make_factory(timeout=5)
 
57
 
 
58
    def test_init(self):
 
59
        service = ImageDownloadService(
 
60
            sentinel.service, sentinel.clock, sentinel.uuid)
 
61
        self.assertIsInstance(service, TimerService)
 
62
        self.assertIs(service.clock, sentinel.clock)
 
63
        self.assertIs(service.uuid, sentinel.uuid)
 
64
        self.assertIs(service.client_service, sentinel.service)
 
65
 
 
66
    def patch_download(self, service, return_value):
 
67
        patched = self.patch(service, '_start_download')
 
68
        patched.return_value = defer.succeed(return_value)
 
69
        return patched
 
70
 
 
71
    def test_is_called_every_interval(self):
 
72
        clock = Clock()
 
73
        service = ImageDownloadService(
 
74
            sentinel.service, clock, sentinel.uuid)
 
75
        # Avoid actual downloads:
 
76
        self.patch_download(service, None)
 
77
        maas_meta_last_modified = self.patch(
 
78
            tftppath, 'maas_meta_last_modified')
 
79
        maas_meta_last_modified.return_value = None
 
80
        service.startService()
 
81
 
 
82
        # The first call is issued at startup.
 
83
        self.assertEqual(1, len(get_mock_calls(maas_meta_last_modified)))
 
84
 
 
85
        # Wind clock forward one second less than the desired interval.
 
86
        clock.advance(service.check_interval - 1)
 
87
        # No more periodic calls made.
 
88
        self.assertEqual(1, len(get_mock_calls(maas_meta_last_modified)))
 
89
 
 
90
        # Wind clock forward one second, past the interval.
 
91
        clock.advance(1)
 
92
 
 
93
        # Now there were two calls.
 
94
        self.assertEqual(2, len(get_mock_calls(maas_meta_last_modified)))
 
95
 
 
96
        # Forward another interval, should be three calls.
 
97
        clock.advance(service.check_interval)
 
98
        self.assertEqual(3, len(get_mock_calls(maas_meta_last_modified)))
 
99
 
 
100
    def test_initiates_download_if_no_meta_file(self):
 
101
        clock = Clock()
 
102
        service = ImageDownloadService(
 
103
            sentinel.service, clock, sentinel.uuid)
 
104
        _start_download = self.patch_download(service, None)
 
105
        self.patch(
 
106
            tftppath,
 
107
            'maas_meta_last_modified').return_value = None
 
108
        service.startService()
 
109
        self.assertThat(_start_download, MockCalledOnceWith())
 
110
 
 
111
    def test_initiates_download_if_15_minutes_has_passed(self):
 
112
        clock = Clock()
 
113
        service = ImageDownloadService(
 
114
            sentinel.service, clock, sentinel.uuid)
 
115
        _start_download = self.patch_download(service, None)
 
116
        one_week_ago = clock.seconds() - timedelta(minutes=15).total_seconds()
 
117
        self.patch(
 
118
            tftppath,
 
119
            'maas_meta_last_modified').return_value = one_week_ago
 
120
        service.startService()
 
121
        self.assertThat(_start_download, MockCalledOnceWith())
 
122
 
 
123
    def test_no_download_if_15_minutes_has_not_passed(self):
 
124
        clock = Clock()
 
125
        service = ImageDownloadService(
 
126
            sentinel.service, clock, sentinel.uuid)
 
127
        _start_download = self.patch_download(service, None)
 
128
        one_week = timedelta(minutes=15).total_seconds()
 
129
        self.patch(
 
130
            tftppath,
 
131
            'maas_meta_last_modified').return_value = clock.seconds()
 
132
        clock.advance(one_week - 1)
 
133
        service.startService()
 
134
        self.assertThat(_start_download, MockNotCalled())
 
135
 
 
136
    def test_download_is_initiated_in_new_thread(self):
 
137
        clock = Clock()
 
138
        maas_meta_last_modified = self.patch(
 
139
            tftppath, 'maas_meta_last_modified')
 
140
        one_week = timedelta(minutes=15).total_seconds()
 
141
        maas_meta_last_modified.return_value = clock.seconds() - one_week
 
142
        rpc_client = Mock()
 
143
        client_call = Mock()
 
144
        client_call.side_effect = [
 
145
            defer.succeed(dict(sources=sentinel.sources)),
 
146
            ]
 
147
        rpc_client.getClient.return_value = client_call
 
148
 
 
149
        # We could patch out 'import_boot_images' instead here but I
 
150
        # don't do that for 2 reasons:
 
151
        # 1. It requires spinning the reactor again before being able to
 
152
        # test the result.
 
153
        # 2. It means there's no thread to clean up after the test.
 
154
        deferToThread = self.patch(boot_images, 'deferToThread')
 
155
        deferToThread.return_value = defer.succeed(None)
 
156
        service = ImageDownloadService(
 
157
            rpc_client, clock, sentinel.uuid)
 
158
        service.startService()
 
159
        self.assertThat(
 
160
            deferToThread, MockCalledOnceWith(
 
161
                _run_import, sentinel.sources))
 
162
 
 
163
    def test_no_download_if_no_rpc_connections(self):
 
164
        rpc_client = Mock()
 
165
        failure = NoConnectionsAvailable()
 
166
        rpc_client.getClient.side_effect = failure
 
167
 
 
168
        deferToThread = self.patch(boot_images, 'deferToThread')
 
169
        service = ImageDownloadService(
 
170
            rpc_client, Clock(), sentinel.uuid)
 
171
        service.startService()
 
172
        self.assertThat(deferToThread, MockNotCalled())
 
173
 
 
174
    def test_logs_other_errors(self):
 
175
        service = ImageDownloadService(
 
176
            sentinel.rpc, Clock(), sentinel.uuid)
 
177
 
 
178
        maybe_start_download = self.patch(service, "maybe_start_download")
 
179
        maybe_start_download.return_value = defer.fail(
 
180
            ZeroDivisionError("Such a shame I can't divide by zero"))
 
181
 
 
182
        with FakeLogger("maas") as maaslog, TwistedLoggerFixture():
 
183
            d = service.try_download()
 
184
 
 
185
        self.assertEqual(None, extract_result(d))
 
186
        self.assertDocTestMatches(
 
187
            "Failed to download images: "
 
188
            "Such a shame I can't divide by zero",
 
189
            maaslog.output)
 
190
 
 
191
 
 
192
class TestGetBootSources(PservTestCase):
 
193
 
 
194
    run_tests_with = MAASTwistedRunTest.make_factory(timeout=5)
 
195
 
 
196
    @defer.inlineCallbacks
 
197
    def test__get_boot_sources_calls_get_boot_sources_v2_before_v1(self):
 
198
        clock = Clock()
 
199
        client_call = Mock()
 
200
        client_call.side_effect = [
 
201
            defer.succeed(dict(sources=sentinel.sources)),
 
202
            ]
 
203
 
 
204
        service = ImageDownloadService(
 
205
            sentinel.rpc, clock, sentinel.uuid)
 
206
        sources = yield service._get_boot_sources(client_call)
 
207
        self.assertEqual(sources.get('sources'), sentinel.sources)
 
208
        self.assertThat(
 
209
            client_call,
 
210
            MockCalledOnceWith(GetBootSourcesV2, uuid=sentinel.uuid))
 
211
 
 
212
    @defer.inlineCallbacks
 
213
    def test__get_boot_sources_calls_get_boot_sources_v1_on_v2_missing(self):
 
214
        clock = Clock()
 
215
        client_call = Mock()
 
216
        client_call.side_effect = [
 
217
            defer.fail(NoSuchMethod()),
 
218
            defer.succeed(dict(sources=[])),
 
219
            ]
 
220
 
 
221
        service = ImageDownloadService(
 
222
            sentinel.rpc, clock, sentinel.uuid)
 
223
        yield service._get_boot_sources(client_call)
 
224
        self.assertThat(
 
225
            client_call,
 
226
            MockCallsMatch(
 
227
                call(GetBootSourcesV2, uuid=sentinel.uuid),
 
228
                call(GetBootSources, uuid=sentinel.uuid)))
 
229
 
 
230
    @defer.inlineCallbacks
 
231
    def test__get_boot_sources_v1_sets_os_to_wildcard(self):
 
232
        sources = [
 
233
            {
 
234
                'path': factory.make_url(),
 
235
                'selections': [
 
236
                    {
 
237
                        'release': "trusty",
 
238
                        'arches': ["amd64"],
 
239
                        'subarches': ["generic"],
 
240
                        'labels': ["release"],
 
241
                    },
 
242
                    {
 
243
                        'release': "precise",
 
244
                        'arches': ["amd64"],
 
245
                        'subarches': ["generic"],
 
246
                        'labels': ["release"],
 
247
                    },
 
248
                ],
 
249
            },
 
250
        ]
 
251
 
 
252
        clock = Clock()
 
253
        client_call = Mock()
 
254
        client_call.side_effect = [
 
255
            defer.fail(NoSuchMethod()),
 
256
            defer.succeed(dict(sources=sources)),
 
257
            ]
 
258
 
 
259
        service = ImageDownloadService(
 
260
            sentinel.rpc, clock, sentinel.uuid)
 
261
        sources = yield service._get_boot_sources(client_call)
 
262
        os_selections = [
 
263
            selection.get('os')
 
264
            for source in sources['sources']
 
265
            for selection in source['selections']
 
266
            ]
 
267
        self.assertEqual(['*', '*'], os_selections)