Merge lp:~barry/ubuntu-system-image/citrain31 into lp:~ubuntu-managed-branches/ubuntu-system-image/system-image
- citrain31
- Merge into system-image
Status: | Merged | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Approved by: | Barry Warsaw | ||||||||||||
Approved revision: | 248 | ||||||||||||
Merged at revision: | 246 | ||||||||||||
Proposed branch: | lp:~barry/ubuntu-system-image/citrain31 | ||||||||||||
Merge into: | lp:~ubuntu-managed-branches/ubuntu-system-image/system-image | ||||||||||||
Diff against target: |
2102 lines (+742/-252) 66 files modified
.bzr-builddeb/default.conf (+2/-0) NEWS.rst (+13/-0) PKG-INFO (+1/-1) cli-manpage.rst (+18/-6) coverage-curl.ini (+9/-4) coverage-udm.ini (+8/-6) dbus-manpage.rst (+13/-17) debian/changelog (+24/-0) debian/control (+1/-1) debian/tests/control (+1/-1) debian/watch (+0/-2) ini-manpage.rst (+2/-2) setup.cfg (+1/-1) setup.py (+1/-1) system_image.egg-info/PKG-INFO (+1/-1) system_image.egg-info/SOURCES.txt (+0/-1) system_image.egg-info/pbr.json (+0/-1) systemimage/api.py (+10/-3) systemimage/apply.py (+1/-1) systemimage/bag.py (+1/-1) systemimage/candidates.py (+11/-1) systemimage/channel.py (+1/-1) systemimage/config.py (+3/-2) systemimage/curl.py (+5/-3) systemimage/dbus.py (+16/-11) systemimage/device.py (+1/-1) systemimage/docs/conf.py (+10/-7) systemimage/download.py (+22/-4) systemimage/gpg.py (+1/-1) systemimage/helpers.py (+1/-1) systemimage/image.py (+1/-1) systemimage/index.py (+1/-1) systemimage/keyring.py (+1/-1) systemimage/logging.py (+1/-1) systemimage/main.py (+23/-8) systemimage/reactor.py (+1/-1) systemimage/scores.py (+1/-1) systemimage/service.py (+2/-3) systemimage/settings.py (+1/-1) systemimage/state.py (+24/-15) systemimage/testing/controller.py (+1/-1) systemimage/testing/dbus.py (+1/-1) systemimage/testing/demo.py (+1/-1) systemimage/testing/helpers.py (+2/-3) systemimage/testing/nose.py (+2/-2) systemimage/testing/service.py (+1/-8) systemimage/tests/test_api.py (+1/-1) systemimage/tests/test_bag.py (+1/-1) systemimage/tests/test_candidates.py (+1/-1) systemimage/tests/test_channel.py (+1/-1) systemimage/tests/test_config.py (+1/-1) systemimage/tests/test_dbus.py (+179/-28) systemimage/tests/test_download.py (+1/-1) systemimage/tests/test_gpg.py (+1/-1) systemimage/tests/test_helpers.py (+1/-1) systemimage/tests/test_image.py (+1/-1) systemimage/tests/test_index.py (+1/-1) systemimage/tests/test_keyring.py (+1/-1) systemimage/tests/test_main.py (+230/-65) systemimage/tests/test_scores.py (+1/-1) systemimage/tests/test_settings.py (+1/-1) systemimage/tests/test_state.py (+32/-4) systemimage/tests/test_winner.py (+1/-1) systemimage/udm.py (+35/-4) systemimage/version.txt (+1/-1) tox.ini (+9/-5) |
||||||||||||
To merge this branch: | bzr merge lp:~barry/ubuntu-system-image/citrain31 | ||||||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu CI managed package branches | Pending | ||
Review via email: mp+287848@code.launchpad.net |
Commit message
Description of the change
system-image (3.1-0ubuntu1) xenial; urgency=medium
* New upstream release.
- LP: #1386302 - In ``system-
flag which can be used to cap a winning upgrade path to a maximum
image number.
- LP: #1380678 - Remove the previously deprecated ``Info()`` D-Bus method.
- Remove the previously deprecated ``--no-reboot`` command line option.
- LP: #1508081 - Add support for temporarily overriding the wifi-only
setting when using ubuntu-
+ Added ``ForceAllowGSM
+ Added ``DownloadStarted`` D-Bus signal, which gets sent when the
download for an update has begun.
+ Added ``--override-gsm`` flag to ``system-
* d/control: Add python3-dbusmock to Build-Depends.
* d/tests/control: ADd python3-dbusmock as a dependency for dryrun.
-- Barry Warsaw <email address hidden> Wed, 02 Mar 2016 15:33:13 -0500
- 247. By Barry Warsaw
-
* d/control:
- Add python3-dbusmock to Build-Depends.
- Remove the X-Auto-Uploader header; let the train mangle the version
numbers so that we can do dual landings. - 248. By Barry Warsaw
-
* .bzr-builddeb/
default. conf: Added for split building.
* d/watch: Dropped.
Preview Diff
1 | === added directory '.bzr-builddeb' |
2 | === added file '.bzr-builddeb/default.conf' |
3 | --- .bzr-builddeb/default.conf 1970-01-01 00:00:00 +0000 |
4 | +++ .bzr-builddeb/default.conf 2016-03-03 20:25:53 +0000 |
5 | @@ -0,0 +1,2 @@ |
6 | +[BUILDDEB] |
7 | +split = True |
8 | |
9 | === modified file 'NEWS.rst' |
10 | --- NEWS.rst 2015-09-25 20:34:10 +0000 |
11 | +++ NEWS.rst 2016-03-03 20:25:53 +0000 |
12 | @@ -2,6 +2,19 @@ |
13 | NEWS for system-image updater |
14 | ============================= |
15 | |
16 | +3.1 (2016-03-02) |
17 | +================ |
18 | + * In ``system-image-cli``, add a ``-m``/``--maximage`` flag which can be used |
19 | + to cap a winning upgrade path to a maximum image number. (LP: #1386302) |
20 | + * Remove the previously deprecated ``Info()`` D-Bus method. (LP: #1380678) |
21 | + * Remove the previously deprecated ``--no-reboot`` command line option. |
22 | + * Add support for temporarily overriding the wifi-only setting when using |
23 | + ubuntu-download-manager. (LP: #1508081) |
24 | + - Added ``ForceAllowGSMDownload()`` method to the D-Bus API. |
25 | + - Added ``DownloadStarted`` D-Bus signal, which gets sent when the download |
26 | + for an update has begun. |
27 | + - Added ``--override-gsm`` flag to ``system-image-cli``. |
28 | + |
29 | 3.0.2 (2015-09-22) |
30 | ================== |
31 | * Don't crash when one of the .ini files is a dangling symlink. |
32 | |
33 | === modified file 'PKG-INFO' |
34 | --- PKG-INFO 2015-09-25 20:34:10 +0000 |
35 | +++ PKG-INFO 2016-03-03 20:25:53 +0000 |
36 | @@ -1,6 +1,6 @@ |
37 | Metadata-Version: 1.0 |
38 | Name: system-image |
39 | -Version: 3.0.2 |
40 | +Version: 3.1 |
41 | Summary: Ubuntu System Image Based Upgrades |
42 | Home-page: UNKNOWN |
43 | Author: Barry Warsaw |
44 | |
45 | === modified file 'cli-manpage.rst' |
46 | --- cli-manpage.rst 2015-05-08 21:41:15 +0000 |
47 | +++ cli-manpage.rst 2016-03-03 20:25:53 +0000 |
48 | @@ -7,9 +7,9 @@ |
49 | ------------------------------------------------ |
50 | |
51 | :Author: Barry Warsaw <barry@ubuntu.com> |
52 | -:Date: 2015-01-15 |
53 | -:Copyright: 2013-2015 Canonical Ltd. |
54 | -:Version: 3.0 |
55 | +:Date: 2016-02-25 |
56 | +:Copyright: 2013-2016 Canonical Ltd. |
57 | +:Version: 3.1 |
58 | :Manual section: 1 |
59 | |
60 | |
61 | @@ -58,10 +58,17 @@ |
62 | -d DEVICE, --device DEVICE |
63 | Override the device name just this once. |
64 | |
65 | ---f FILTER, --filter FILTER |
66 | +-f FILTER, --filter FILTER |
67 | Filter the candidate upgrade paths to only contain full or delta updates. |
68 | ``FILTER`` must be either ``full`` or ``delta``. |
69 | |
70 | +-m IMAGENO, --maximage IMAGENO |
71 | + Cap a winning upgrade path to image number ``IMAGENO``. All images with a |
72 | + version number greater than ``IMAGENO`` will be ignored. For example, if |
73 | + the winning upgrade path is ``200:204:304`` and you give ``-m 205``, the |
74 | + upgrade will not include image number 304. Note that this capping happens |
75 | + *after* the winning upgrade path is selected. |
76 | + |
77 | -i, --info |
78 | Show some information about the current device, including the current |
79 | build number, device name, and channel, then exit. |
80 | @@ -77,8 +84,7 @@ |
81 | -g, --no-apply |
82 | Downloads all files and prepares for, but does not actually apply the |
83 | update. On devices which require a reboot to apply the update, no reboot |
84 | - is performed. *New in system-image 3.0: --no-reboot is renamed to |
85 | - --no-apply* |
86 | + is performed. |
87 | |
88 | -v, --verbose |
89 | Increase the logging verbosity. With one ``-v``, logging goes to the |
90 | @@ -120,6 +126,12 @@ |
91 | Deletes the given key from the settings database. If the key does not |
92 | exist, this is a no-op. May be given multiple times. |
93 | |
94 | +--override-gsm |
95 | + Allows an update to proceed while the device is on GSM and currently set |
96 | + to only use wifi. This is only effective when using |
97 | + ``ubuntu-download-manager``. |
98 | + **New in system-image 3.1.** |
99 | + |
100 | |
101 | FILES |
102 | ===== |
103 | |
104 | === modified file 'coverage-curl.ini' |
105 | --- coverage-curl.ini 2015-02-09 18:46:24 +0000 |
106 | +++ coverage-curl.ini 2016-03-03 20:25:53 +0000 |
107 | @@ -9,13 +9,18 @@ |
108 | systemimage/tests/* |
109 | systemimage/udm.py |
110 | /usr/lib/* |
111 | - .tox/coverage-curl/lib/python3.4/distutils/* |
112 | - .tox/coverage-curl/lib/python3.4/site-packages/pkg_resources* |
113 | - .tox/coverage-udm/lib/python3.4/distutils/* |
114 | - .tox/coverage-udm/lib/python3.4/site-packages/pkg_resources* |
115 | + .tox/coverage-curl/lib/python*/distutils/* |
116 | + .tox/coverage-curl/lib/python*/site-packages/pkg_resources* |
117 | + .tox/coverage-udm/lib/python*/distutils/* |
118 | + .tox/coverage-udm/lib/python*/site-packages/pkg_resources* |
119 | |
120 | [paths] |
121 | source = |
122 | systemimage |
123 | .tox/coverage-curl/lib/python*/site-packages/systemimage |
124 | .tox/coverage-udm/lib/python*/site-packages/systemimage |
125 | + |
126 | +[report] |
127 | +exclude_lines = |
128 | + pragma: no cover |
129 | + pragma: no curl |
130 | |
131 | === modified file 'coverage-udm.ini' |
132 | --- coverage-udm.ini 2015-02-09 18:46:24 +0000 |
133 | +++ coverage-udm.ini 2016-03-03 20:25:53 +0000 |
134 | @@ -9,13 +9,15 @@ |
135 | systemimage/tests/* |
136 | systemimage/curl.py |
137 | /usr/lib/* |
138 | - .tox/coverage-curl/lib/python3.4/distutils/* |
139 | - .tox/coverage-curl/lib/python3.4/site-packages/pkg_resources* |
140 | - .tox/coverage-udm/lib/python3.4/distutils/* |
141 | - .tox/coverage-udm/lib/python3.4/site-packages/pkg_resources* |
142 | + .tox/coverage-*/lib/python*/distutils/* |
143 | + .tox/coverage-*/lib/python*/site-packages/pkg_resources* |
144 | |
145 | [paths] |
146 | source = |
147 | systemimage |
148 | - .tox/coverage-curl/lib/python*/site-packages/systemimage |
149 | - .tox/coverage-udm/lib/python*/site-packages/systemimage |
150 | + .tox/coverage-*/lib/python*/site-packages/systemimage |
151 | + |
152 | +[report] |
153 | +exclude_lines = |
154 | + pragma: no cover |
155 | + pragma: no udm |
156 | |
157 | === modified file 'dbus-manpage.rst' |
158 | --- dbus-manpage.rst 2015-05-08 21:41:15 +0000 |
159 | +++ dbus-manpage.rst 2016-03-03 20:25:53 +0000 |
160 | @@ -7,9 +7,9 @@ |
161 | ----------------------------------------- |
162 | |
163 | :Author: Barry Warsaw <barry@ubuntu.com> |
164 | -:Date: 2015-01-15 |
165 | -:Copyright: 2013-2015 Canonical Ltd. |
166 | -:Version: 3.0 |
167 | +:Date: 2016-02-25 |
168 | +:Copyright: 2013-2016 Canonical Ltd. |
169 | +:Version: 3.1 |
170 | :Manual section: 8 |
171 | |
172 | |
173 | @@ -114,20 +114,6 @@ |
174 | string is returned unless an error occurred, in which case the error |
175 | message is returned. |
176 | |
177 | -``Info()`` |
178 | - **Deprecated** (see ``Information()``). This is a **synchronous** call |
179 | - which returns some information about the current state of the device. The |
180 | - following pieces of information are returned, as a tuple: |
181 | - |
182 | - * *current build number* - the current build number as an integer. |
183 | - * *device name* - the name of the device type. |
184 | - * *channel name* - the channel the device is currently on. |
185 | - * *last update date* - the last time this device was updated as a |
186 | - datetime, e.g. "YYYY-MM-DDTHH:MM:SS" |
187 | - * *version detail* - a mapping of strings to strings, where the keys are |
188 | - component names and the values are the version numbers for that |
189 | - component. |
190 | - |
191 | ``Information()`` |
192 | This is a **synchronous** call which returns an extensible mapping of |
193 | UTF-8 keys to UTF-8 values. The following keys are currently defined: |
194 | @@ -195,6 +181,12 @@ |
195 | has not been previously set, the empty string is returned. Note that |
196 | some of the pre-defined keys have default settings. |
197 | |
198 | +``ForceAllowGSMDownload()`` |
199 | + This is a **synchronous** call to force the use of the GSM network for an |
200 | + in-progress wifi-only update stalled while the device is on GSM. This is |
201 | + only effective when using ``ubuntu-download-manager``. |
202 | + **New in system-image 3.1.** |
203 | + |
204 | ``Exit()`` |
205 | This is a **synchronous** call which causes the D-Bus service process to |
206 | exit immediately. There is no return value. If ``Exit()`` is never |
207 | @@ -250,6 +242,10 @@ |
208 | There is no update available. The ISO 8601 date of the last applied |
209 | update is given, but all other arguments should be ignored. |
210 | |
211 | +``DownloadStarted()`` |
212 | + Sent when the download of the update files has started. |
213 | + **New in system-image 3.1.** |
214 | + |
215 | ``UpdateProgress(percentage, eta)`` |
216 | Sent periodically, while a download is in progress. This signal is not |
217 | sent when an upgrade is paused. |
218 | |
219 | === modified file 'debian/changelog' |
220 | --- debian/changelog 2015-09-28 21:37:45 +0000 |
221 | +++ debian/changelog 2016-03-03 20:25:53 +0000 |
222 | @@ -1,3 +1,27 @@ |
223 | +system-image (3.1-0ubuntu1) xenial; urgency=medium |
224 | + |
225 | + * New upstream release. |
226 | + - LP: #1386302 - In ``system-image-cli``, add a ``-m``/``--maximage`` |
227 | + flag which can be used to cap a winning upgrade path to a maximum |
228 | + image number. |
229 | + - LP: #1380678 - Remove the previously deprecated ``Info()`` D-Bus method. |
230 | + - Remove the previously deprecated ``--no-reboot`` command line option. |
231 | + - LP: #1508081 - Add support for temporarily overriding the wifi-only |
232 | + setting when using ubuntu-download-manager: |
233 | + + Added ``ForceAllowGSMDownload()`` method to the D-Bus API. |
234 | + + Added ``DownloadStarted`` D-Bus signal, which gets sent when the |
235 | + download for an update has begun. |
236 | + + Added ``--override-gsm`` flag to ``system-image-cli``. |
237 | + * d/control: |
238 | + - Add python3-dbusmock to Build-Depends. |
239 | + - Remove the X-Auto-Uploader header; let the train mangle the version |
240 | + numbers so that we can do dual landings. |
241 | + * d/tests/control: ADd python3-dbusmock as a dependency for dryrun. |
242 | + * .bzr-builddeb/default.conf: Added for split building. |
243 | + * d/watch: Dropped. |
244 | + |
245 | + -- Barry Warsaw <barry@ubuntu.com> Wed, 02 Mar 2016 15:33:13 -0500 |
246 | + |
247 | system-image (3.0.2-0ubuntu1) wily; urgency=medium |
248 | |
249 | [ CI Train Bot ] |
250 | |
251 | === modified file 'debian/control' |
252 | --- debian/control 2015-06-18 13:46:00 +0000 |
253 | +++ debian/control 2016-03-03 20:25:53 +0000 |
254 | @@ -11,6 +11,7 @@ |
255 | python-docutils, |
256 | python3-all (>= 3.3), |
257 | python3-dbus, |
258 | + python3-dbusmock, |
259 | python3-gi, |
260 | python3-gnupg, |
261 | python3-nose2, |
262 | @@ -24,7 +25,6 @@ |
263 | Testsuite: autopkgtest |
264 | Vcs-Bzr: https://code.launchpad.net/~ubuntu-managed-branches/ubuntu-system-image/system-image |
265 | Vcs-Browser: http://bazaar.launchpad.net/~ubuntu-managed-branches/ubuntu-system-image/system-image/files |
266 | -X-Auto-Uploader: no-rewrite-version |
267 | |
268 | Package: system-image-cli |
269 | Architecture: all |
270 | |
271 | === modified file 'debian/tests/control' |
272 | --- debian/tests/control 2015-06-17 17:41:56 +0000 |
273 | +++ debian/tests/control 2016-03-03 20:25:53 +0000 |
274 | @@ -8,4 +8,4 @@ |
275 | |
276 | Tests: dryrun |
277 | Restrictions: allow-stderr |
278 | -Depends: @, ubuntu-download-manager, dbus, dbus-x11, python3-psutil, python3-xdg, python3-setuptools, python3-nose2, python3-pycurl |
279 | +Depends: @, ubuntu-download-manager, dbus, dbus-x11, python3-psutil, python3-xdg, python3-setuptools, python3-nose2, python3-pycurl, python3-dbusmock |
280 | |
281 | === removed file 'debian/watch' |
282 | --- debian/watch 2014-07-16 22:16:48 +0000 |
283 | +++ debian/watch 1970-01-01 00:00:00 +0000 |
284 | @@ -1,2 +0,0 @@ |
285 | -version=3 |
286 | -opts="pgpsigurlmangle=s/$/.asc/" https://launchpad.net/ubuntu-system-image/+download https://launchpad.net/ubuntu-system-image/.*/system-image-(.*)\.tar\.gz |
287 | |
288 | === modified file 'ini-manpage.rst' |
289 | --- ini-manpage.rst 2015-05-08 21:41:15 +0000 |
290 | +++ ini-manpage.rst 2016-03-03 20:25:53 +0000 |
291 | @@ -8,8 +8,8 @@ |
292 | ------------------------------------------------ |
293 | |
294 | :Author: Barry Warsaw <barry@ubuntu.com> |
295 | -:Date: 2015-01-15 |
296 | -:Copyright: 2013-2015 Canonical Ltd. |
297 | +:Date: 2016-01-15 |
298 | +:Copyright: 2013-2016 Canonical Ltd. |
299 | :Version: 3.0 |
300 | :Manual section: 5 |
301 | |
302 | |
303 | === modified file 'setup.cfg' |
304 | --- setup.cfg 2015-09-25 20:34:10 +0000 |
305 | +++ setup.cfg 2016-03-03 20:25:53 +0000 |
306 | @@ -4,7 +4,7 @@ |
307 | logging-filter = systemimage |
308 | |
309 | [egg_info] |
310 | +tag_build = |
311 | tag_svn_revision = 0 |
312 | -tag_build = |
313 | tag_date = 0 |
314 | |
315 | |
316 | === modified file 'setup.py' |
317 | --- setup.py 2015-05-08 21:41:15 +0000 |
318 | +++ setup.py 2016-03-03 20:25:53 +0000 |
319 | @@ -1,4 +1,4 @@ |
320 | -# Copyright (C) 2013-2015 Canonical Ltd. |
321 | +# Copyright (C) 2013-2016 Canonical Ltd. |
322 | # Author: Barry Warsaw <barry@ubuntu.com> |
323 | |
324 | # This program is free software: you can redistribute it and/or modify |
325 | |
326 | === modified file 'system_image.egg-info/PKG-INFO' |
327 | --- system_image.egg-info/PKG-INFO 2015-09-25 20:34:10 +0000 |
328 | +++ system_image.egg-info/PKG-INFO 2016-03-03 20:25:53 +0000 |
329 | @@ -1,6 +1,6 @@ |
330 | Metadata-Version: 1.0 |
331 | Name: system-image |
332 | -Version: 3.0.2 |
333 | +Version: 3.1 |
334 | Summary: Ubuntu System Image Based Upgrades |
335 | Home-page: UNKNOWN |
336 | Author: Barry Warsaw |
337 | |
338 | === modified file 'system_image.egg-info/SOURCES.txt' |
339 | --- system_image.egg-info/SOURCES.txt 2015-09-25 20:34:10 +0000 |
340 | +++ system_image.egg-info/SOURCES.txt 2016-03-03 20:25:53 +0000 |
341 | @@ -14,7 +14,6 @@ |
342 | system_image.egg-info/SOURCES.txt |
343 | system_image.egg-info/dependency_links.txt |
344 | system_image.egg-info/entry_points.txt |
345 | -system_image.egg-info/pbr.json |
346 | system_image.egg-info/requires.txt |
347 | system_image.egg-info/top_level.txt |
348 | systemimage/__init__.py |
349 | |
350 | === removed file 'system_image.egg-info/pbr.json' |
351 | --- system_image.egg-info/pbr.json 2015-09-25 19:28:34 +0000 |
352 | +++ system_image.egg-info/pbr.json 1970-01-01 00:00:00 +0000 |
353 | @@ -1,1 +0,0 @@ |
354 | -{"is_release": true, "git_version": "4ecc87c"} |
355 | \ No newline at end of file |
356 | |
357 | === modified file 'systemimage/api.py' |
358 | --- systemimage/api.py 2015-05-08 21:41:15 +0000 |
359 | +++ systemimage/api.py 2016-03-03 20:25:53 +0000 |
360 | @@ -1,4 +1,4 @@ |
361 | -# Copyright (C) 2013-2015 Canonical Ltd. |
362 | +# Copyright (C) 2013-2016 Canonical Ltd. |
363 | # Author: Barry Warsaw <barry@ubuntu.com> |
364 | |
365 | # This program is free software: you can redistribute it and/or modify |
366 | @@ -84,8 +84,12 @@ |
367 | self._callback = callback |
368 | |
369 | def __repr__(self): # pragma: no cover |
370 | - return '<Mediator at 0x{:x} | State at 0x{:x}>'.format( |
371 | - id(self), id(self._state)) |
372 | + fmt = '<Mediator at 0x{:x} | State at 0x{:x} | Downloader at {}>' |
373 | + args = [id(self), id(self._state), |
374 | + 'None' if self._state.downloader is None |
375 | + else '0x{:x}'.format(id(self._state.downloader)) |
376 | + ] |
377 | + return fmt.format(*args) |
378 | |
379 | def cancel(self): |
380 | self._state.downloader.cancel() |
381 | @@ -135,3 +139,6 @@ |
382 | |
383 | def production_reset(self): |
384 | production_reset() |
385 | + |
386 | + def allow_gsm(self): |
387 | + self._state.downloader.allow_gsm() # pragma: no curl |
388 | |
389 | === modified file 'systemimage/apply.py' |
390 | --- systemimage/apply.py 2015-05-08 21:41:15 +0000 |
391 | +++ systemimage/apply.py 2016-03-03 20:25:53 +0000 |
392 | @@ -1,4 +1,4 @@ |
393 | -# Copyright (C) 2013-2015 Canonical Ltd. |
394 | +# Copyright (C) 2013-2016 Canonical Ltd. |
395 | # Author: Barry Warsaw <barry@ubuntu.com> |
396 | |
397 | # This program is free software: you can redistribute it and/or modify |
398 | |
399 | === modified file 'systemimage/bag.py' |
400 | --- systemimage/bag.py 2015-05-08 21:41:15 +0000 |
401 | +++ systemimage/bag.py 2016-03-03 20:25:53 +0000 |
402 | @@ -1,4 +1,4 @@ |
403 | -# Copyright (C) 2013-2015 Canonical Ltd. |
404 | +# Copyright (C) 2013-2016 Canonical Ltd. |
405 | # Author: Barry Warsaw <barry@ubuntu.com> |
406 | |
407 | # This program is free software: you can redistribute it and/or modify |
408 | |
409 | === modified file 'systemimage/candidates.py' |
410 | --- systemimage/candidates.py 2015-05-08 21:41:15 +0000 |
411 | +++ systemimage/candidates.py 2016-03-03 20:25:53 +0000 |
412 | @@ -1,4 +1,4 @@ |
413 | -# Copyright (C) 2013-2015 Canonical Ltd. |
414 | +# Copyright (C) 2013-2016 Canonical Ltd. |
415 | # Author: Barry Warsaw <barry@ubuntu.com> |
416 | |
417 | # This program is free software: you can redistribute it and/or modify |
418 | @@ -20,6 +20,7 @@ |
419 | 'full_filter', |
420 | 'get_candidates', |
421 | 'iter_path', |
422 | + 'version_filter', |
423 | ] |
424 | |
425 | |
426 | @@ -166,3 +167,12 @@ |
427 | if len(new_path) != 0: |
428 | filtered.append(new_path) |
429 | return filtered |
430 | + |
431 | + |
432 | +class version_filter: |
433 | + def __init__(self, maximum_version): |
434 | + self.maximum_version = maximum_version |
435 | + |
436 | + def __call__(self, winner): |
437 | + return [image for image in winner |
438 | + if image.version <= self.maximum_version] |
439 | |
440 | === modified file 'systemimage/channel.py' |
441 | --- systemimage/channel.py 2015-05-08 21:41:15 +0000 |
442 | +++ systemimage/channel.py 2016-03-03 20:25:53 +0000 |
443 | @@ -1,4 +1,4 @@ |
444 | -# Copyright (C) 2013-2015 Canonical Ltd. |
445 | +# Copyright (C) 2013-2016 Canonical Ltd. |
446 | # Author: Barry Warsaw <barry@ubuntu.com> |
447 | |
448 | # This program is free software: you can redistribute it and/or modify |
449 | |
450 | === modified file 'systemimage/config.py' |
451 | --- systemimage/config.py 2015-05-08 21:41:15 +0000 |
452 | +++ systemimage/config.py 2016-03-03 20:25:53 +0000 |
453 | @@ -1,4 +1,4 @@ |
454 | -# Copyright (C) 2013-2015 Canonical Ltd. |
455 | +# Copyright (C) 2013-2016 Canonical Ltd. |
456 | # Author: Barry Warsaw <barry@ubuntu.com> |
457 | |
458 | # This program is free software: you can redistribute it and/or modify |
459 | @@ -69,9 +69,10 @@ |
460 | # plumbing to work otherwise. This seems like the least horrible place |
461 | # to stash this global. |
462 | self.dbus_service = None |
463 | - # This is used to plumb command line arguments from the main() to |
464 | + # These are used to plumb command line arguments from the main() to |
465 | # other parts of the system. |
466 | self.skip_gpg_verification = False |
467 | + self.override_gsm = False |
468 | # Cache. |
469 | self._device = None |
470 | self._build_number = None |
471 | |
472 | === modified file 'systemimage/curl.py' |
473 | --- systemimage/curl.py 2015-03-03 16:14:21 +0000 |
474 | +++ systemimage/curl.py 2016-03-03 20:25:53 +0000 |
475 | @@ -1,4 +1,4 @@ |
476 | -# Copyright (C) 2014-2015 Canonical Ltd. |
477 | +# Copyright (C) 2014-2016 Canonical Ltd. |
478 | # Author: Barry Warsaw <barry@ubuntu.com> |
479 | |
480 | # This program is free software: you can redistribute it and/or modify |
481 | @@ -50,7 +50,7 @@ |
482 | def make_testable(c): |
483 | # The test suite needs to make the PyCURL object accept the testing |
484 | # server's self signed certificate. It will mock this function. |
485 | - pass |
486 | + pass # pragma: no cover |
487 | |
488 | |
489 | class SingleDownload: |
490 | @@ -136,7 +136,7 @@ |
491 | self._pausables = [] |
492 | self._paused = False |
493 | |
494 | - def _get_files(self, records, pausable): |
495 | + def _get_files(self, records, pausable, signal_started): |
496 | # Start by doing a HEAD on all the URLs so that we can get the total |
497 | # target download size in bytes, at least as best as is possible. |
498 | with ExitStack() as resources: |
499 | @@ -160,6 +160,8 @@ |
500 | for handle in handles) |
501 | # Now do a GET on all the URLs. This will write the data to the |
502 | # destination file and collect the checksums. |
503 | + if signal_started and config.dbus_service is not None: |
504 | + config.dbus_service.DownloadStarted() |
505 | with ExitStack() as resources: |
506 | resources.callback(setattr, self, '_handles', None) |
507 | downloads = [] |
508 | |
509 | === modified file 'systemimage/dbus.py' |
510 | --- systemimage/dbus.py 2015-05-08 21:41:15 +0000 |
511 | +++ systemimage/dbus.py 2016-03-03 20:25:53 +0000 |
512 | @@ -1,4 +1,4 @@ |
513 | -# Copyright (C) 2013-2015 Canonical Ltd. |
514 | +# Copyright (C) 2013-2016 Canonical Ltd. |
515 | # Author: Barry Warsaw <barry@ubuntu.com> |
516 | |
517 | # This program is free software: you can redistribute it and/or modify |
518 | @@ -32,7 +32,7 @@ |
519 | from gi.repository import GLib |
520 | from systemimage.api import Mediator |
521 | from systemimage.config import config |
522 | -from systemimage.helpers import last_update_date, version_detail |
523 | +from systemimage.helpers import last_update_date |
524 | from systemimage.settings import Settings |
525 | from threading import Lock |
526 | |
527 | @@ -113,7 +113,7 @@ |
528 | log.info('_check_for_update(): checking lock releasing') |
529 | try: |
530 | self._checking.release() |
531 | - except RuntimeError: |
532 | + except RuntimeError: # pragma: no udm |
533 | log.info('_check_for_update(): checking lock already released') |
534 | else: |
535 | log.info('_check_for_update(): checking lock released') |
536 | @@ -312,14 +312,12 @@ |
537 | return '' |
538 | |
539 | @log_and_exit |
540 | - @method('com.canonical.SystemImage', out_signature='isssa{ss}') |
541 | - def Info(self): |
542 | - self.loop.keepalive() |
543 | - return (config.build_number, |
544 | - config.device, |
545 | - config.channel, |
546 | - last_update_date(), |
547 | - version_detail()) |
548 | + @method('com.canonical.SystemImage') |
549 | + def ForceAllowGSMDownload(self): # pragma: no curl |
550 | + """Force an existing group download to proceed over GSM.""" |
551 | + log.info('Mediator {}', self._api) |
552 | + self._api.allow_gsm() |
553 | + return '' |
554 | |
555 | @log_and_exit |
556 | @method('com.canonical.SystemImage', out_signature='a{ss}') |
557 | @@ -416,6 +414,13 @@ |
558 | last_update_date, repr(error_reason)) |
559 | self.loop.keepalive() |
560 | |
561 | + @log_and_exit |
562 | + @signal('com.canonical.SystemImage') |
563 | + def DownloadStarted(self): |
564 | + """The download has started.""" |
565 | + log.debug('EMIT DownloadStarted()') |
566 | + self.loop.keepalive() |
567 | + |
568 | #@log_and_exit |
569 | @signal('com.canonical.SystemImage', signature='id') |
570 | def UpdateProgress(self, percentage, eta): |
571 | |
572 | === modified file 'systemimage/device.py' |
573 | --- systemimage/device.py 2015-05-08 21:41:15 +0000 |
574 | +++ systemimage/device.py 2016-03-03 20:25:53 +0000 |
575 | @@ -1,4 +1,4 @@ |
576 | -# Copyright (C) 2013-2015 Canonical Ltd. |
577 | +# Copyright (C) 2013-2016 Canonical Ltd. |
578 | # Author: Barry Warsaw <barry@ubuntu.com> |
579 | |
580 | # This program is free software: you can redistribute it and/or modify |
581 | |
582 | === modified file 'systemimage/docs/conf.py' |
583 | --- systemimage/docs/conf.py 2015-05-08 21:41:15 +0000 |
584 | +++ systemimage/docs/conf.py 2016-03-03 20:25:53 +0000 |
585 | @@ -40,17 +40,17 @@ |
586 | master_doc = 'readme' |
587 | |
588 | # General information about the project. |
589 | -project = u'Image Update Resolver' |
590 | -copyright = u'2013-2015, Canonical Ltd.' |
591 | +project = u'System Image Update Client' |
592 | +copyright = u'2013-2016, Canonical Ltd.' |
593 | |
594 | # The version info for the project you're documenting, acts as replacement for |
595 | # |version| and |release|, also used in various other places throughout the |
596 | # built documents. |
597 | # |
598 | # The short X.Y version. |
599 | -version = '0.1' |
600 | +version = '3.1' |
601 | # The full version, including alpha/beta/rc tags. |
602 | -release = '0.1' |
603 | +release = '3.1' |
604 | |
605 | # The language for content autogenerated by Sphinx. Refer to documentation |
606 | # for a list of supported languages. |
607 | @@ -183,7 +183,8 @@ |
608 | # Grouping the document tree into LaTeX files. List of tuples |
609 | # (source start file, target name, title, author, documentclass [howto/manual]). |
610 | latex_documents = [ |
611 | - ('readme', 'ImageUpdateResolver.tex', u'Image Update Resolver Documentation', |
612 | + ('readme', 'ImageUpdateResolver.tex', |
613 | + u'System Image Update Client Documentation', |
614 | u'Barry Warsaw', 'manual'), |
615 | ] |
616 | |
617 | @@ -213,7 +214,8 @@ |
618 | # One entry per manual page. List of tuples |
619 | # (source start file, name, description, authors, manual section). |
620 | man_pages = [ |
621 | - ('readme', 'systemimage', u'Ubuntu System Image Updater Documentation', |
622 | + ('readme', 'systemimage', |
623 | + u'Ubuntu System Image Update Client Documentation', |
624 | [u'Barry Warsaw'], 1) |
625 | ] |
626 | |
627 | @@ -227,7 +229,8 @@ |
628 | # (source start file, target name, title, author, |
629 | # dir menu entry, description, category) |
630 | texinfo_documents = [ |
631 | - ('readme', 'ImageUpdateResolver', u'Image Update Resolver Documentation', |
632 | + ('readme', 'ImageUpdateResolver', |
633 | + u'System Image Update Client Documentation', |
634 | u'Barry Warsaw', 'ImageUpdateResolver', 'One line description of project.', |
635 | 'Miscellaneous'), |
636 | ] |
637 | |
638 | === modified file 'systemimage/download.py' |
639 | --- systemimage/download.py 2015-05-08 21:41:15 +0000 |
640 | +++ systemimage/download.py 2016-03-03 20:25:53 +0000 |
641 | @@ -1,4 +1,4 @@ |
642 | -# Copyright (C) 2013-2015 Canonical Ltd. |
643 | +# Copyright (C) 2013-2016 Canonical Ltd. |
644 | # Author: Barry Warsaw <barry@ubuntu.com> |
645 | |
646 | # This program is free software: you can redistribute it and/or modify |
647 | @@ -150,10 +150,10 @@ |
648 | """Resume the download, but only if one is in progress.""" |
649 | pass # pragma: no cover |
650 | |
651 | - def _get_files(self, records, pausable): |
652 | + def _get_files(self, records, pausable, signal_started): |
653 | raise NotImplementedError # pragma: no cover |
654 | |
655 | - def get_files(self, downloads, *, pausable=False): |
656 | + def get_files(self, downloads, *, pausable=False, signal_started=False): |
657 | """Download a bunch of files concurrently. |
658 | |
659 | Occasionally, the callback is called to report on progress. |
660 | @@ -177,6 +177,12 @@ |
661 | or not. In general, data file downloads are pausable, but |
662 | preliminary downloads are not. |
663 | :type pausable: bool |
664 | + :param signal_started: A flag indicating whether the D-Bus |
665 | + DownloadStarted signal should be sent once the download has |
666 | + started. Normally this is False, but it should be set to True |
667 | + when the update files are being downloaded (i.e. not for the |
668 | + metadata files). |
669 | + :type signal_started: bool |
670 | :raises: FileNotFoundError if any download error occurred. In |
671 | this case, all download files are deleted. |
672 | :raises: DuplicateDestinationError if more than one source url is |
673 | @@ -200,7 +206,19 @@ |
674 | else: |
675 | print('\t{} [{}] -> {}'.format(*record), file=fp) |
676 | log.info('{}'.format(fp.getvalue())) |
677 | - self._get_files(records, pausable) |
678 | + self._get_files(records, pausable, signal_started) |
679 | + |
680 | + @staticmethod |
681 | + def allow_gsm(): |
682 | + """Allow downloads on GSM. |
683 | + |
684 | + This is a temporary override for the `auto_download` setting. |
685 | + If a download was attempted on wifi-only and not started because |
686 | + the device is on GSM, calling this issues a temporary override |
687 | + to allow downloads while on GSM, for download managers that |
688 | + support this (currently only UDM). |
689 | + """ |
690 | + pass # pragma: no cover |
691 | |
692 | |
693 | def get_download_manager(*args): |
694 | |
695 | === modified file 'systemimage/gpg.py' |
696 | --- systemimage/gpg.py 2015-05-08 21:41:15 +0000 |
697 | +++ systemimage/gpg.py 2016-03-03 20:25:53 +0000 |
698 | @@ -1,4 +1,4 @@ |
699 | -# Copyright (C) 2013-2015 Canonical Ltd. |
700 | +# Copyright (C) 2013-2016 Canonical Ltd. |
701 | # Author: Barry Warsaw <barry@ubuntu.com> |
702 | |
703 | # This program is free software: you can redistribute it and/or modify |
704 | |
705 | === modified file 'systemimage/helpers.py' |
706 | --- systemimage/helpers.py 2015-09-25 20:34:10 +0000 |
707 | +++ systemimage/helpers.py 2016-03-03 20:25:53 +0000 |
708 | @@ -1,4 +1,4 @@ |
709 | -# Copyright (C) 2013-2015 Canonical Ltd. |
710 | +# Copyright (C) 2013-2016 Canonical Ltd. |
711 | # Author: Barry Warsaw <barry@ubuntu.com> |
712 | |
713 | # This program is free software: you can redistribute it and/or modify |
714 | |
715 | === modified file 'systemimage/image.py' |
716 | --- systemimage/image.py 2015-05-08 21:41:15 +0000 |
717 | +++ systemimage/image.py 2016-03-03 20:25:53 +0000 |
718 | @@ -1,4 +1,4 @@ |
719 | -# Copyright (C) 2013-2015 Canonical Ltd. |
720 | +# Copyright (C) 2013-2016 Canonical Ltd. |
721 | # Author: Barry Warsaw <barry@ubuntu.com> |
722 | |
723 | # This program is free software: you can redistribute it and/or modify |
724 | |
725 | === modified file 'systemimage/index.py' |
726 | --- systemimage/index.py 2015-05-08 21:41:15 +0000 |
727 | +++ systemimage/index.py 2016-03-03 20:25:53 +0000 |
728 | @@ -1,4 +1,4 @@ |
729 | -# Copyright (C) 2013-2015 Canonical Ltd. |
730 | +# Copyright (C) 2013-2016 Canonical Ltd. |
731 | # Author: Barry Warsaw <barry@ubuntu.com> |
732 | |
733 | # This program is free software: you can redistribute it and/or modify |
734 | |
735 | === modified file 'systemimage/keyring.py' |
736 | --- systemimage/keyring.py 2015-05-08 21:41:15 +0000 |
737 | +++ systemimage/keyring.py 2016-03-03 20:25:53 +0000 |
738 | @@ -1,4 +1,4 @@ |
739 | -# Copyright (C) 2013-2015 Canonical Ltd. |
740 | +# Copyright (C) 2013-2016 Canonical Ltd. |
741 | # Author: Barry Warsaw <barry@ubuntu.com> |
742 | |
743 | # This program is free software: you can redistribute it and/or modify |
744 | |
745 | === modified file 'systemimage/logging.py' |
746 | --- systemimage/logging.py 2015-09-25 20:34:10 +0000 |
747 | +++ systemimage/logging.py 2016-03-03 20:25:53 +0000 |
748 | @@ -1,4 +1,4 @@ |
749 | -# Copyright (C) 2013-2015 Canonical Ltd. |
750 | +# Copyright (C) 2013-2016 Canonical Ltd. |
751 | # Author: Barry Warsaw <barry@ubuntu.com> |
752 | |
753 | # This program is free software: you can redistribute it and/or modify |
754 | |
755 | === modified file 'systemimage/main.py' |
756 | --- systemimage/main.py 2015-06-17 15:18:22 +0000 |
757 | +++ systemimage/main.py 2016-03-03 20:25:53 +0000 |
758 | @@ -1,4 +1,4 @@ |
759 | -# Copyright (C) 2013-2015 Canonical Ltd. |
760 | +# Copyright (C) 2013-2016 Canonical Ltd. |
761 | # Author: Barry Warsaw <barry@ubuntu.com> |
762 | |
763 | # This program is free software: you can redistribute it and/or modify |
764 | @@ -29,7 +29,7 @@ |
765 | from dbus.mainloop.glib import DBusGMainLoop |
766 | from pkg_resources import resource_string as resource_bytes |
767 | from systemimage.apply import factory_reset, production_reset |
768 | -from systemimage.candidates import delta_filter, full_filter |
769 | +from systemimage.candidates import delta_filter, full_filter, version_filter |
770 | from systemimage.config import config |
771 | from systemimage.helpers import ( |
772 | last_update_date, makedirs, phased_percentage, version_detail) |
773 | @@ -112,16 +112,19 @@ |
774 | full updates or only delta updates. The |
775 | argument to this option must be either `full` |
776 | or `delta`""") |
777 | + parser.add_argument('-m', '--maximage', |
778 | + default=None, type=int, |
779 | + help="""After the winning upgrade path is selected, |
780 | + remove all images with version numbers greater |
781 | + than the given one. If no images remain in |
782 | + the winning path, the device is considered |
783 | + up-to-date.""") |
784 | parser.add_argument('-g', '--no-apply', |
785 | default=False, action='store_true', |
786 | help="""Download (i.e. "get") all the data files and |
787 | prepare for updating, but don't actually |
788 | reboot the device into recovery to apply the |
789 | update""") |
790 | - # Deprecated since si 3.0. |
791 | - parser.add_argument('--no-reboot', |
792 | - default=False, action='store_true', |
793 | - help="""Deprecated; use -g/--no-apply""") |
794 | parser.add_argument('-i', '--info', |
795 | default=False, action='store_true', |
796 | help="""Show some information about the current |
797 | @@ -183,6 +186,13 @@ |
798 | help="""Delete the key and its value. It is a no-op |
799 | if the key does not exist. Multiple |
800 | --del arguments can be given.""") |
801 | + parser.add_argument('--override-gsm', |
802 | + default=False, action='store_true', |
803 | + help="""When the device is set to only download over |
804 | + WiFi, but is currently on GSM, use this switch |
805 | + to temporarily override the update restriction. |
806 | + This switch has no effect when using the cURL |
807 | + based downloader.""") |
808 | # Hidden system-image-cli only feature for testing purposes. LP: #1333414 |
809 | parser.add_argument('--skip-gpg-verification', |
810 | default=False, action='store_true', |
811 | @@ -202,6 +212,8 @@ |
812 | Your upgrades are INSECURE.""", file=sys.stderr) |
813 | config.skip_gpg_verification = True |
814 | |
815 | + config.override_gsm = args.override_gsm |
816 | + |
817 | # Perform factory and production resets. |
818 | if args.factory_reset: |
819 | factory_reset() |
820 | @@ -332,7 +344,10 @@ |
821 | print(' {} (alias for: {})'.format(key, alias)) |
822 | return 0 |
823 | |
824 | - state = State(candidate_filter=candidate_filter) |
825 | + state = State() |
826 | + state.candidate_filter = candidate_filter |
827 | + if args.maximage is not None: |
828 | + state.winner_filter = version_filter(args.maximage) |
829 | |
830 | for meter in args.progress: |
831 | if meter == 'dots': |
832 | @@ -381,7 +396,7 @@ |
833 | log.info('running state machine [{}/{}]', |
834 | config.channel, config.device) |
835 | try: |
836 | - if args.no_apply or args.no_reboot: |
837 | + if args.no_apply: |
838 | state.run_until('apply') |
839 | else: |
840 | list(state) |
841 | |
842 | === modified file 'systemimage/reactor.py' |
843 | --- systemimage/reactor.py 2015-05-08 21:41:15 +0000 |
844 | +++ systemimage/reactor.py 2016-03-03 20:25:53 +0000 |
845 | @@ -1,4 +1,4 @@ |
846 | -# Copyright (C) 2013-2015 Canonical Ltd. |
847 | +# Copyright (C) 2013-2016 Canonical Ltd. |
848 | # Author: Barry Warsaw <barry@ubuntu.com> |
849 | |
850 | # This program is free software: you can redistribute it and/or modify |
851 | |
852 | === modified file 'systemimage/scores.py' |
853 | --- systemimage/scores.py 2015-05-08 21:41:15 +0000 |
854 | +++ systemimage/scores.py 2016-03-03 20:25:53 +0000 |
855 | @@ -1,4 +1,4 @@ |
856 | -# Copyright (C) 2013-2015 Canonical Ltd. |
857 | +# Copyright (C) 2013-2016 Canonical Ltd. |
858 | # Author: Barry Warsaw <barry@ubuntu.com> |
859 | |
860 | # This program is free software: you can redistribute it and/or modify |
861 | |
862 | === modified file 'systemimage/service.py' |
863 | --- systemimage/service.py 2015-05-08 21:41:15 +0000 |
864 | +++ systemimage/service.py 2016-03-03 20:25:53 +0000 |
865 | @@ -1,4 +1,4 @@ |
866 | -# Copyright (C) 2013-2015 Canonical Ltd. |
867 | +# Copyright (C) 2013-2016 Canonical Ltd. |
868 | # Author: Barry Warsaw <barry@ubuntu.com> |
869 | |
870 | # This program is free software: you can redistribute it and/or modify |
871 | @@ -50,7 +50,6 @@ |
872 | |
873 | |
874 | def main(): |
875 | - global config |
876 | # If enabled, start code coverage collection as early as possible. |
877 | # Parse arguments. |
878 | parser = argparse.ArgumentParser( |
879 | @@ -62,7 +61,7 @@ |
880 | parser.add_argument('-C', '--config', |
881 | default=DEFAULT_CONFIG_D, action='store', |
882 | metavar='DIRECTORY', |
883 | - help="""Use the given configuration directory instead |
884 | + help="""Use the given configuration directory instead |
885 | of the default""") |
886 | parser.add_argument('-v', '--verbose', |
887 | default=0, action='count', |
888 | |
889 | === modified file 'systemimage/settings.py' |
890 | --- systemimage/settings.py 2015-05-08 21:41:15 +0000 |
891 | +++ systemimage/settings.py 2016-03-03 20:25:53 +0000 |
892 | @@ -1,4 +1,4 @@ |
893 | -# Copyright (C) 2013-2015 Canonical Ltd. |
894 | +# Copyright (C) 2013-2016 Canonical Ltd. |
895 | # Author: Barry Warsaw <barry@ubuntu.com> |
896 | |
897 | # This program is free software: you can redistribute it and/or modify |
898 | |
899 | === modified file 'systemimage/state.py' |
900 | --- systemimage/state.py 2015-05-08 21:41:15 +0000 |
901 | +++ systemimage/state.py 2016-03-03 20:25:53 +0000 |
902 | @@ -1,4 +1,4 @@ |
903 | -# Copyright (C) 2013-2015 Canonical Ltd. |
904 | +# Copyright (C) 2013-2016 Canonical Ltd. |
905 | # Author: Barry Warsaw <barry@ubuntu.com> |
906 | |
907 | # This program is free software: you can redistribute it and/or modify |
908 | @@ -100,11 +100,12 @@ |
909 | |
910 | |
911 | class State: |
912 | - def __init__(self, candidate_filter=None): |
913 | + def __init__(self): |
914 | # Variables which manage state transitions. |
915 | self._next = deque() |
916 | self._debug_step = 1 |
917 | - self._filter = candidate_filter |
918 | + self.candidate_filter = None |
919 | + self.winner_filter = None |
920 | # Variables which represent things we've learned. |
921 | self.blacklist = None |
922 | self.channels = None |
923 | @@ -432,21 +433,27 @@ |
924 | candidates = get_candidates(self.index, build_number) |
925 | log.debug('Candidates from build# {}: {}'.format( |
926 | build_number, len(candidates))) |
927 | - if self._filter is not None: |
928 | - candidates = self._filter(candidates) |
929 | + if self.candidate_filter is not None: |
930 | + candidates = self.candidate_filter(candidates) |
931 | self.winner = config.hooks.scorer().choose( |
932 | candidates, (channel_target |
933 | if channel_alias is None |
934 | else channel_alias)) |
935 | - # If there is no winning upgrade candidate, then there's nothing more |
936 | - # to do. We can skip everything between downloading the files and |
937 | - # doing the reboot. |
938 | - if len(self.winner) > 0: |
939 | - winning_path = [str(image.version) for image in self.winner] |
940 | - log.info('Upgrade path is {}'.format(COLON.join(winning_path))) |
941 | - self._next.append(self._download_files) |
942 | - else: |
943 | + if len(self.winner) == 0: |
944 | log.info('Already up-to-date') |
945 | + return |
946 | + winning_path = [str(image.version) for image in self.winner] |
947 | + log.info('Upgrade path is {}'.format(COLON.join(winning_path))) |
948 | + # Now filter the winning path to cap the maximum version number. |
949 | + if (self.winner_filter is not None and |
950 | + self.winner_filter.maximum_version is not None): |
951 | + log.info('Upgrade path capped at version {}'.format( |
952 | + self.winner_filter.maximum_version)) |
953 | + self.winner = self.winner_filter(self.winner) |
954 | + if len(self.winner) == 0: |
955 | + log.info('Capped upgrade leaves device up-to-date') |
956 | + return |
957 | + self._next.append(self._download_files) |
958 | |
959 | def _download_files(self): |
960 | """Download and verify all the winning upgrade path's files.""" |
961 | @@ -502,8 +509,10 @@ |
962 | if path not in preserve: |
963 | safe_remove(os.path.join(cache_dir, filename)) |
964 | # Now, download all missing or ill-signed files, providing logging |
965 | - # feedback on progress. This download can be paused. |
966 | - self.downloader.get_files(downloads, pausable=True) |
967 | + # feedback on progress. This download can be paused. The downloader |
968 | + # should also signal when the file downloads have started. |
969 | + self.downloader.get_files( |
970 | + downloads, pausable=True, signal_started=True) |
971 | with ExitStack() as stack: |
972 | # Set things up to remove the files if a SignatureError gets |
973 | # raised or if the checksums don't match. If everything's okay, |
974 | |
975 | === modified file 'systemimage/testing/controller.py' |
976 | --- systemimage/testing/controller.py 2015-05-08 21:41:15 +0000 |
977 | +++ systemimage/testing/controller.py 2016-03-03 20:25:53 +0000 |
978 | @@ -1,4 +1,4 @@ |
979 | -# Copyright (C) 2013-2015 Canonical Ltd. |
980 | +# Copyright (C) 2013-2016 Canonical Ltd. |
981 | # Author: Barry Warsaw <barry@ubuntu.com> |
982 | |
983 | # This program is free software: you can redistribute it and/or modify |
984 | |
985 | === modified file 'systemimage/testing/dbus.py' |
986 | --- systemimage/testing/dbus.py 2015-09-25 20:34:10 +0000 |
987 | +++ systemimage/testing/dbus.py 2016-03-03 20:25:53 +0000 |
988 | @@ -1,4 +1,4 @@ |
989 | -# Copyright (C) 2013-2015 Canonical Ltd. |
990 | +# Copyright (C) 2013-2016 Canonical Ltd. |
991 | # Author: Barry Warsaw <barry@ubuntu.com> |
992 | |
993 | # This program is free software: you can redistribute it and/or modify |
994 | |
995 | === modified file 'systemimage/testing/demo.py' |
996 | --- systemimage/testing/demo.py 2015-05-08 21:41:15 +0000 |
997 | +++ systemimage/testing/demo.py 2016-03-03 20:25:53 +0000 |
998 | @@ -1,4 +1,4 @@ |
999 | -# Copyright (C) 2013-2015 Canonical Ltd. |
1000 | +# Copyright (C) 2013-2016 Canonical Ltd. |
1001 | # Author: Barry Warsaw <barry@ubuntu.com> |
1002 | |
1003 | # This program is free software: you can redistribute it and/or modify |
1004 | |
1005 | === modified file 'systemimage/testing/helpers.py' |
1006 | --- systemimage/testing/helpers.py 2015-06-17 15:18:22 +0000 |
1007 | +++ systemimage/testing/helpers.py 2016-03-03 20:25:53 +0000 |
1008 | @@ -1,4 +1,4 @@ |
1009 | -# Copyright (C) 2013-2015 Canonical Ltd. |
1010 | +# Copyright (C) 2013-2016 Canonical Ltd. |
1011 | # Author: Barry Warsaw <barry@ubuntu.com> |
1012 | |
1013 | # This program is free software: you can redistribute it and/or modify |
1014 | @@ -506,10 +506,9 @@ |
1015 | else: |
1016 | path = Path(os.devnull) |
1017 | with path.open('a', encoding='utf-8') as fp: |
1018 | - function = partial(print, file=fp, end=end) |
1019 | + function = partial(print, file=fp, end=end, flush=True) |
1020 | function.fp = fp |
1021 | yield function |
1022 | - fp.flush() |
1023 | |
1024 | |
1025 | def find_dbus_process(ini_path): |
1026 | |
1027 | === modified file 'systemimage/testing/nose.py' |
1028 | --- systemimage/testing/nose.py 2015-05-08 21:41:15 +0000 |
1029 | +++ systemimage/testing/nose.py 2016-03-03 20:25:53 +0000 |
1030 | @@ -1,4 +1,4 @@ |
1031 | -# Copyright (C) 2013-2015 Canonical Ltd. |
1032 | +# Copyright (C) 2013-2016 Canonical Ltd. |
1033 | # Author: Barry Warsaw <barry@ubuntu.com> |
1034 | |
1035 | # This program is free software: you can redistribute it and/or modify |
1036 | @@ -92,7 +92,7 @@ |
1037 | def set_dbus_loglevel(level): |
1038 | self.log_level = level[0] |
1039 | self.addOption(set_dbus_loglevel, 'M', 'loglevel', |
1040 | - 'Set the systemimage.dbus log level', |
1041 | + 'Set the systemimage[:systemimage.dbus] log level', |
1042 | nargs=1) |
1043 | |
1044 | @configuration |
1045 | |
1046 | === modified file 'systemimage/testing/service.py' |
1047 | --- systemimage/testing/service.py 2015-01-16 23:10:30 +0000 |
1048 | +++ systemimage/testing/service.py 2016-03-03 20:25:53 +0000 |
1049 | @@ -1,4 +1,4 @@ |
1050 | -# Copyright (C) 2014-2015 Canonical Ltd. |
1051 | +# Copyright (C) 2014-2016 Canonical Ltd. |
1052 | # Author: Barry Warsaw <barry@ubuntu.com> |
1053 | |
1054 | # This program is free software: you can redistribute it and/or modify |
1055 | @@ -19,13 +19,6 @@ |
1056 | collection as early as possible in the private bus D-Bus activated processes. |
1057 | """ |
1058 | |
1059 | -# Uncomment this if the controller won't start. There's no other good way to |
1060 | -# get debugging information about the D-Bus activated process, since their |
1061 | -# stderr just seems to get lost. |
1062 | -## import sys |
1063 | -## sys.stderr = open('/tmp/debug.log', 'a', encoding='utf-8') |
1064 | - |
1065 | - |
1066 | import os |
1067 | |
1068 | # Set this environment variable if the controller won't start. There's no |
1069 | |
1070 | === modified file 'systemimage/tests/test_api.py' |
1071 | --- systemimage/tests/test_api.py 2015-05-08 21:41:15 +0000 |
1072 | +++ systemimage/tests/test_api.py 2016-03-03 20:25:53 +0000 |
1073 | @@ -1,4 +1,4 @@ |
1074 | -# Copyright (C) 2013-2015 Canonical Ltd. |
1075 | +# Copyright (C) 2013-2016 Canonical Ltd. |
1076 | # Author: Barry Warsaw <barry@ubuntu.com> |
1077 | |
1078 | # This program is free software: you can redistribute it and/or modify |
1079 | |
1080 | === modified file 'systemimage/tests/test_bag.py' |
1081 | --- systemimage/tests/test_bag.py 2015-05-08 21:41:15 +0000 |
1082 | +++ systemimage/tests/test_bag.py 2016-03-03 20:25:53 +0000 |
1083 | @@ -1,4 +1,4 @@ |
1084 | -# Copyright (C) 2013-2015 Canonical Ltd. |
1085 | +# Copyright (C) 2013-2016 Canonical Ltd. |
1086 | # Author: Barry Warsaw <barry@ubuntu.com> |
1087 | |
1088 | # This program is free software: you can redistribute it and/or modify |
1089 | |
1090 | === modified file 'systemimage/tests/test_candidates.py' |
1091 | --- systemimage/tests/test_candidates.py 2015-05-08 21:41:15 +0000 |
1092 | +++ systemimage/tests/test_candidates.py 2016-03-03 20:25:53 +0000 |
1093 | @@ -1,4 +1,4 @@ |
1094 | -# Copyright (C) 2013-2015 Canonical Ltd. |
1095 | +# Copyright (C) 2013-2016 Canonical Ltd. |
1096 | # Author: Barry Warsaw <barry@ubuntu.com> |
1097 | |
1098 | # This program is free software: you can redistribute it and/or modify |
1099 | |
1100 | === modified file 'systemimage/tests/test_channel.py' |
1101 | --- systemimage/tests/test_channel.py 2015-05-08 21:41:15 +0000 |
1102 | +++ systemimage/tests/test_channel.py 2016-03-03 20:25:53 +0000 |
1103 | @@ -1,4 +1,4 @@ |
1104 | -# Copyright (C) 2013-2015 Canonical Ltd. |
1105 | +# Copyright (C) 2013-2016 Canonical Ltd. |
1106 | # Author: Barry Warsaw <barry@ubuntu.com> |
1107 | |
1108 | # This program is free software: you can redistribute it and/or modify |
1109 | |
1110 | === modified file 'systemimage/tests/test_config.py' |
1111 | --- systemimage/tests/test_config.py 2015-05-08 21:41:15 +0000 |
1112 | +++ systemimage/tests/test_config.py 2016-03-03 20:25:53 +0000 |
1113 | @@ -1,4 +1,4 @@ |
1114 | -# Copyright (C) 2013-2015 Canonical Ltd. |
1115 | +# Copyright (C) 2013-2016 Canonical Ltd. |
1116 | # Author: Barry Warsaw <barry@ubuntu.com> |
1117 | |
1118 | # This program is free software: you can redistribute it and/or modify |
1119 | |
1120 | === modified file 'systemimage/tests/test_dbus.py' |
1121 | --- systemimage/tests/test_dbus.py 2015-09-25 20:34:10 +0000 |
1122 | +++ systemimage/tests/test_dbus.py 2016-03-03 20:25:53 +0000 |
1123 | @@ -1,4 +1,4 @@ |
1124 | -# Copyright (C) 2013-2015 Canonical Ltd. |
1125 | +# Copyright (C) 2013-2016 Canonical Ltd. |
1126 | # Author: Barry Warsaw <barry@ubuntu.com> |
1127 | |
1128 | # This program is free software: you can redistribute it and/or modify |
1129 | @@ -23,7 +23,8 @@ |
1130 | 'TestDBusDownload', |
1131 | 'TestDBusDownloadBigFiles', |
1132 | 'TestDBusFactoryReset', |
1133 | - 'TestDBusProductionReset', |
1134 | + 'TestDBusGSMDownloads', |
1135 | + 'TestDBusGSMNoDownloads', |
1136 | 'TestDBusGetSet', |
1137 | 'TestDBusInfo', |
1138 | 'TestDBusMiscellaneous', |
1139 | @@ -36,6 +37,7 @@ |
1140 | 'TestDBusMockUpdateManualSuccess', |
1141 | 'TestDBusMultipleChecksInFlight', |
1142 | 'TestDBusPauseResume', |
1143 | + 'TestDBusProductionReset', |
1144 | 'TestDBusProgress', |
1145 | 'TestDBusRegressions', |
1146 | 'TestDBusUseCache', |
1147 | @@ -49,12 +51,14 @@ |
1148 | import json |
1149 | import time |
1150 | import shutil |
1151 | +import dbusmock |
1152 | import tempfile |
1153 | import unittest |
1154 | +import subprocess |
1155 | |
1156 | from contextlib import ExitStack, suppress |
1157 | from collections import namedtuple |
1158 | -from datetime import datetime |
1159 | +from datetime import datetime, timedelta |
1160 | from dbus.exceptions import DBusException |
1161 | from functools import partial |
1162 | from pathlib import Path |
1163 | @@ -63,7 +67,7 @@ |
1164 | from systemimage.helpers import MiB, safe_remove |
1165 | from systemimage.reactor import Reactor |
1166 | from systemimage.settings import Settings |
1167 | -from systemimage.testing.controller import USING_PYCURL |
1168 | +from systemimage.testing.controller import USING_PYCURL, stop_downloader |
1169 | from systemimage.testing.helpers import ( |
1170 | copy, data_path, find_dbus_process, make_http_server, setup_index, |
1171 | setup_keyring_txz, setup_keyrings, sign, terminate_service, touch_build, |
1172 | @@ -303,6 +307,20 @@ |
1173 | self.rebooting = (True, args[0]) |
1174 | |
1175 | |
1176 | +class StartedReactor(Reactor): |
1177 | + def __init__(self): |
1178 | + super().__init__(dbus.SystemBus()) |
1179 | + self.got_started = 0 |
1180 | + self.react_to('DownloadStarted') |
1181 | + self.react_to('UpdateDownloaded') |
1182 | + |
1183 | + def _do_DownloadStarted(self, *args, **kws): |
1184 | + self.got_started += 1 |
1185 | + |
1186 | + def _do_UpdateDownloaded(self, *args, **kws): |
1187 | + self.quit() |
1188 | + |
1189 | + |
1190 | class _TestBase(unittest.TestCase): |
1191 | """Base class for all DBus testing.""" |
1192 | |
1193 | @@ -677,6 +695,17 @@ |
1194 | # Don't count on a specific error message. |
1195 | self.assertEqual(last_reason[:25], 'DuplicateDestinationError') |
1196 | |
1197 | + def test_started(self): |
1198 | + # A DownloadStarted signal is sent when the download has started. |
1199 | + # This either comes proxied through UDM or gets sent by the built-in |
1200 | + # cURL downloader. |
1201 | + self.download_always() |
1202 | + reactor = StartedReactor() |
1203 | + reactor.schedule(self.iface.CheckForUpdate) |
1204 | + reactor.run(timeout=60) |
1205 | + # Exactly one DownloadStarted signal was received. |
1206 | + self.assertEqual(reactor.got_started, 1) |
1207 | + |
1208 | |
1209 | class TestDBusDownloadBigFiles(_LiveTesting): |
1210 | # If the update contains several very large files, ensure that they can be |
1211 | @@ -1451,15 +1480,6 @@ |
1212 | class TestDBusInfo(_TestBase): |
1213 | mode = 'more-info' |
1214 | |
1215 | - def test_info(self): |
1216 | - # .Info() with some version details. |
1217 | - buildno, device, channel, last_update, details = self.iface.Info() |
1218 | - self.assertEqual(buildno, 45) |
1219 | - self.assertEqual(device, 'nexus11') |
1220 | - self.assertEqual(channel, 'daily-proposed') |
1221 | - self.assertEqual(last_update, '2099-08-01 04:45:45') |
1222 | - self.assertEqual(details, dict(ubuntu='123', mako='456', custom='789')) |
1223 | - |
1224 | def test_information(self): |
1225 | # .Information() with some version details. |
1226 | response = self.iface.Information() |
1227 | @@ -1484,18 +1504,6 @@ |
1228 | |
1229 | |
1230 | class TestLiveDBusInfo(_LiveTesting): |
1231 | - def test_info_no_version_detail(self): |
1232 | - # .Info() where there are no version details. |
1233 | - timestamp = int(datetime(2022, 8, 1, 4, 45, 45).timestamp()) |
1234 | - touch_build(45, timestamp, self.config) |
1235 | - self.iface.Reset() |
1236 | - buildno, device, channel, last_update, details = self.iface.Info() |
1237 | - self.assertEqual(buildno, 45) |
1238 | - self.assertEqual(device, 'nexus7') |
1239 | - self.assertEqual(channel, 'stable') |
1240 | - self.assertEqual(last_update, '2022-08-01 04:45:45') |
1241 | - self.assertEqual(details, {}) |
1242 | - |
1243 | def test_information_before_check_no_details(self): |
1244 | # .Information() where there are no version details, and no previous |
1245 | # CheckForUpdate() call was made. |
1246 | @@ -1741,7 +1749,7 @@ |
1247 | write_bytes(full_path, 750) |
1248 | tweak_checksums('') |
1249 | |
1250 | - @capture_dbus_calls |
1251 | + #@capture_dbus_calls |
1252 | def test_pause(self): |
1253 | # Set up some extra D-Bus debugging. |
1254 | self.download_manually() |
1255 | @@ -1765,13 +1773,13 @@ |
1256 | # size to be big enough to trigger the expected behavior. There's no |
1257 | # other way to control the live u-d-m process. |
1258 | self.assertGreater(reactor.percentage, 0) |
1259 | - self.assertLess(reactor.percentage, 100) |
1260 | + self.assertLessEqual(reactor.percentage, 100) |
1261 | self.assertGreaterEqual(reactor.percentage, reactor.pause_progress) |
1262 | # Now let's resume the download. Because we intentionally corrupted |
1263 | # the downloaded files, we'll get an UpdateFailed signal instead of |
1264 | # the successful UpdateDownloaded signal. |
1265 | reactor = SignalCapturingReactor('UpdateFailed') |
1266 | - reactor.run(self.iface.DownloadUpdate, timeout=60) |
1267 | + reactor.run(self.iface.DownloadUpdate, timeout=300) |
1268 | self.assertEqual(len(reactor.signals), 1) |
1269 | # The error message will include lots of details on the SignatureError |
1270 | # that results. The key thing is that it's 5.txt that is the first |
1271 | @@ -2131,3 +2139,146 @@ |
1272 | # Failure count. |
1273 | self.assertEqual(failure[0], 1) |
1274 | self.assertEqual(failure[1], 'Canceled') |
1275 | + |
1276 | + |
1277 | +@unittest.skipIf(USING_PYCURL, 'UDM-only tests') |
1278 | +class TestDBusGSMDownloads(_LiveTesting): |
1279 | + def _mock_udm(self): |
1280 | + # Stop the actual UDM downloader, create our mock, and let it |
1281 | + # "perform" the download of the update. |
1282 | + stop_downloader(SystemImagePlugin.controller) |
1283 | + # Remove UDM's .service file from the temporary directory so that it |
1284 | + # won't get D-Bus activated. This should let the mock service win. |
1285 | + os.remove(os.path.join( |
1286 | + SystemImagePlugin.controller.tmpdir, |
1287 | + 'com.canonical.applications.Downloader.service')) |
1288 | + # And restart dbus-daemon. |
1289 | + wait_for_service() |
1290 | + # Create the mock UDM. |
1291 | + argv = [sys.executable, '-m', 'dbusmock', '--system', |
1292 | + 'com.canonical.applications.Downloader', |
1293 | + '/', |
1294 | + 'com.canonical.applications.DownloadManager'] |
1295 | + self.server = subprocess.Popen( |
1296 | + argv, stdout=subprocess.PIPE, env=os.environ) |
1297 | + bus = dbus.SystemBus() |
1298 | + until = datetime.now() + timedelta(seconds=60) |
1299 | + while datetime.now() < until: |
1300 | + try: |
1301 | + p = dbus.Interface( |
1302 | + bus.get_object( |
1303 | + 'com.canonical.applications.Downloader', '/'), |
1304 | + dbus_interface=dbus.INTROSPECTABLE_IFACE) |
1305 | + p.Introspect() |
1306 | + break |
1307 | + except dbus.exceptions.DBusException as e: |
1308 | + if '.UnknownInterface' in str(e): |
1309 | + break |
1310 | + time.sleep(0.1) |
1311 | + # Shut down the mock UDM when this test completes. |
1312 | + def terminate(): |
1313 | + self.server.terminate() |
1314 | + self.server.wait() |
1315 | + self.addCleanup(terminate) |
1316 | + # Start filling out the UDM mock, but only enough to complete the |
1317 | + # test. Remember, all we care about is that we can flip the GSM flag |
1318 | + # while a download is paused. We have to assume that the real UDM |
1319 | + # does the right thing in this case, so we're just ensuring that |
1320 | + # system-image handles the situation correctly. |
1321 | + # |
1322 | + # See https://wiki.ubuntu.com/DownloadService |
1323 | + self.udm = dbus.Interface( |
1324 | + bus.get_object('com.canonical.applications.Downloader', '/'), |
1325 | + dbusmock.MOCK_IFACE) |
1326 | + # On the primary entry point, we only care about being able to create |
1327 | + # a group download. This is the object that UDM's |
1328 | + # createDownloadGroup() method will return. We hard code its object |
1329 | + # path because we really don't care about the details. |
1330 | + self.group = self.udm.AddObject( |
1331 | + '/group1', 'com.canonical.applications.GroupDownload', |
1332 | + # No properties. |
1333 | + {}, [ |
1334 | + # We only care about a few methods on the group download object. |
1335 | + # First up is the method that si calls to flip the GSM flag in |
1336 | + # UDM. We'll simulate the resuming of a paused (due to being on |
1337 | + # wifi-only) UDM by emitting the `started` and `finished` signals |
1338 | + # that si expects. This will actually cause si to fail an |
1339 | + # internal assertion because the files it requested to be |
1340 | + # downloaded won't be present. But we don't really care about |
1341 | + # that since we're only making sure that the resumption of the |
1342 | + # paused download works. |
1343 | + ('allowGSMDownload', 'b', '', |
1344 | + 'self.EmitSignal("", "started", "b", (False,)); ' |
1345 | + 'self.EmitSignal("", "finished", "ao", ([objects["/group1"]],))' |
1346 | + ), |
1347 | + # UDM's start() gets called by si's DownloadUpdate(), but it |
1348 | + # doesn't take any arguments, return any values, or have any |
1349 | + # side-effects. |
1350 | + ('start', '', '', ''), |
1351 | + # Similarly, UDM's cancel() gets called by the si test framework |
1352 | + # when the test completes. |
1353 | + ('cancel', '', '', ''), |
1354 | + ]) |
1355 | + # Here's the mock of UDM's createDownloadGroup() method. The only |
1356 | + # thing we care about is that the object created above is returned. |
1357 | + self.udm.AddMethod( |
1358 | + '', 'createDownloadGroup', |
1359 | + # https://wiki.ubuntu.com/DownloadService/DownloadManager |
1360 | + 'a(sss)sba{sv}a{ss}', 'o', |
1361 | + 'ret = objects["/group1"]') |
1362 | + # Because of the way the UDMDownloadManager works, we have to mock |
1363 | + # UDM's getAllDownloads() method to also return the object created |
1364 | + # above. See UDMDownloadManager.allow_gsm() for details. |
1365 | + self.udm.AddMethod( |
1366 | + '', 'getAllDownloads', |
1367 | + '', 'ao', |
1368 | + 'ret = [objects["/group1"]]') |
1369 | + |
1370 | + def test_allow_gsm_download(self): |
1371 | + self.download_manually() |
1372 | + # Check for update available. Use the real UDM so that we get all the |
1373 | + # keyrings and JSON files that define the update. Because we're |
1374 | + # downloading manually, the data files won't be downloaded yet. |
1375 | + reactor = SignalCapturingReactor('UpdateAvailableStatus') |
1376 | + reactor.run(self.iface.CheckForUpdate) |
1377 | + self.assertEqual(len(reactor.signals), 1) |
1378 | + signal = reactor.signals[0] |
1379 | + self.assertTrue(signal.is_available, msg=signal.error_reason) |
1380 | + # Set up the mock and attempt to start the download. This will fail |
1381 | + # to get a 'started' signal because we're not on wifi. |
1382 | + self.download_on_wifi() |
1383 | + self._mock_udm() |
1384 | + reactor = StartedReactor() |
1385 | + reactor.schedule(self.iface.DownloadUpdate) |
1386 | + reactor.run(timeout=10) |
1387 | + self.assertEqual(reactor.got_started, 0) |
1388 | + # Now tell UDM that GSM downloads are okay and watch again for the |
1389 | + # simulated started signal. We do however need a new reactor since |
1390 | + # the old one won't respond to the subsequent signals. |
1391 | + reactor = StartedReactor() |
1392 | + reactor.schedule(self.iface.ForceAllowGSMDownload) |
1393 | + reactor.run(timeout=10) |
1394 | + self.assertEqual(reactor.got_started, 1) |
1395 | + |
1396 | + |
1397 | +@unittest.skipIf(USING_PYCURL, 'UDM-only tests') |
1398 | +class TestDBusGSMNoDownloads(_LiveTesting): |
1399 | + def test_force_gsm_noops_when_no_download_is_in_progress(self): |
1400 | + self.download_on_wifi() |
1401 | + reactor = StartedReactor() |
1402 | + reactor.schedule(self.iface.ForceAllowGSMDownload) |
1403 | + reactor.run(timeout=10) |
1404 | + self.assertEqual(reactor.got_started, 0) |
1405 | + |
1406 | + def test_force_gsm_noops_when_download_is_manual(self): |
1407 | + self.download_manually() |
1408 | + reactor = SignalCapturingReactor('UpdateAvailableStatus') |
1409 | + reactor.run(self.iface.CheckForUpdate) |
1410 | + self.assertEqual(len(reactor.signals), 1) |
1411 | + signal = reactor.signals[0] |
1412 | + self.assertTrue(signal.is_available, msg=signal.error_reason) |
1413 | + # Don't start the download. |
1414 | + reactor = StartedReactor() |
1415 | + reactor.schedule(self.iface.ForceAllowGSMDownload) |
1416 | + reactor.run(timeout=10) |
1417 | + self.assertEqual(reactor.got_started, 0) |
1418 | |
1419 | === modified file 'systemimage/tests/test_download.py' |
1420 | --- systemimage/tests/test_download.py 2015-05-08 21:41:15 +0000 |
1421 | +++ systemimage/tests/test_download.py 2016-03-03 20:25:53 +0000 |
1422 | @@ -1,4 +1,4 @@ |
1423 | -# Copyright (C) 2013-2015 Canonical Ltd. |
1424 | +# Copyright (C) 2013-2016 Canonical Ltd. |
1425 | # Author: Barry Warsaw <barry@ubuntu.com> |
1426 | |
1427 | # This program is free software: you can redistribute it and/or modify |
1428 | |
1429 | === modified file 'systemimage/tests/test_gpg.py' |
1430 | --- systemimage/tests/test_gpg.py 2015-05-08 21:41:15 +0000 |
1431 | +++ systemimage/tests/test_gpg.py 2016-03-03 20:25:53 +0000 |
1432 | @@ -1,4 +1,4 @@ |
1433 | -# Copyright (C) 2013-2015 Canonical Ltd. |
1434 | +# Copyright (C) 2013-2016 Canonical Ltd. |
1435 | # Author: Barry Warsaw <barry@ubuntu.com> |
1436 | |
1437 | # This program is free software: you can redistribute it and/or modify |
1438 | |
1439 | === modified file 'systemimage/tests/test_helpers.py' |
1440 | --- systemimage/tests/test_helpers.py 2015-09-25 20:34:10 +0000 |
1441 | +++ systemimage/tests/test_helpers.py 2016-03-03 20:25:53 +0000 |
1442 | @@ -1,4 +1,4 @@ |
1443 | -# Copyright (C) 2013-2015 Canonical Ltd. |
1444 | +# Copyright (C) 2013-2016 Canonical Ltd. |
1445 | # Author: Barry Warsaw <barry@ubuntu.com> |
1446 | |
1447 | # This program is free software: you can redistribute it and/or modify |
1448 | |
1449 | === modified file 'systemimage/tests/test_image.py' |
1450 | --- systemimage/tests/test_image.py 2015-05-08 21:41:15 +0000 |
1451 | +++ systemimage/tests/test_image.py 2016-03-03 20:25:53 +0000 |
1452 | @@ -1,4 +1,4 @@ |
1453 | -# Copyright (C) 2013-2015 Canonical Ltd. |
1454 | +# Copyright (C) 2013-2016 Canonical Ltd. |
1455 | # Author: Barry Warsaw <barry@ubuntu.com> |
1456 | |
1457 | # This program is free software: you can redistribute it and/or modify |
1458 | |
1459 | === modified file 'systemimage/tests/test_index.py' |
1460 | --- systemimage/tests/test_index.py 2015-05-08 21:41:15 +0000 |
1461 | +++ systemimage/tests/test_index.py 2016-03-03 20:25:53 +0000 |
1462 | @@ -1,4 +1,4 @@ |
1463 | -# Copyright (C) 2013-2015 Canonical Ltd. |
1464 | +# Copyright (C) 2013-2016 Canonical Ltd. |
1465 | # Author: Barry Warsaw <barry@ubuntu.com> |
1466 | |
1467 | # This program is free software: you can redistribute it and/or modify |
1468 | |
1469 | === modified file 'systemimage/tests/test_keyring.py' |
1470 | --- systemimage/tests/test_keyring.py 2015-05-08 21:41:15 +0000 |
1471 | +++ systemimage/tests/test_keyring.py 2016-03-03 20:25:53 +0000 |
1472 | @@ -1,4 +1,4 @@ |
1473 | -# Copyright (C) 2013-2015 Canonical Ltd. |
1474 | +# Copyright (C) 2013-2016 Canonical Ltd. |
1475 | # Author: Barry Warsaw <barry@ubuntu.com> |
1476 | |
1477 | # This program is free software: you can redistribute it and/or modify |
1478 | |
1479 | === modified file 'systemimage/tests/test_main.py' |
1480 | --- systemimage/tests/test_main.py 2015-06-17 15:18:22 +0000 |
1481 | +++ systemimage/tests/test_main.py 2016-03-03 20:25:53 +0000 |
1482 | @@ -1,4 +1,4 @@ |
1483 | -# Copyright (C) 2013-2015 Canonical Ltd. |
1484 | +# Copyright (C) 2013-2016 Canonical Ltd. |
1485 | # Author: Barry Warsaw <barry@ubuntu.com> |
1486 | |
1487 | # This program is free software: you can redistribute it and/or modify |
1488 | @@ -23,6 +23,7 @@ |
1489 | 'TestCLIMain', |
1490 | 'TestCLIMainDryRun', |
1491 | 'TestCLIMainDryRunAliases', |
1492 | + 'TestCLIMaximumImage', |
1493 | 'TestCLINoReboot', |
1494 | 'TestCLIProductionReset', |
1495 | 'TestCLIProgress', |
1496 | @@ -52,6 +53,7 @@ |
1497 | from systemimage.helpers import safe_remove |
1498 | from systemimage.main import main as cli_main |
1499 | from systemimage.settings import Settings |
1500 | +from systemimage.testing.controller import USING_PYCURL |
1501 | from systemimage.testing.helpers import ( |
1502 | ServerTestBase, chmod, configuration, copy, data_path, find_dbus_process, |
1503 | sign, temporary_directory, terminate_service, touch_build, |
1504 | @@ -379,7 +381,7 @@ |
1505 | # Test that the system log file gets created and written. |
1506 | self.assertFalse(os.path.exists(config.system.logfile)) |
1507 | class FakeState: |
1508 | - def __init__(self, candidate_filter): |
1509 | + def __init__(self): |
1510 | self.downloader = MagicMock() |
1511 | def __iter__(self): |
1512 | return self |
1513 | @@ -763,6 +765,177 @@ |
1514 | """) |
1515 | |
1516 | |
1517 | +class TestCLIMaximumImage(ServerTestBase): |
1518 | + INDEX_FILE = 'main.index_02.json' |
1519 | + CHANNEL_FILE = 'main.channels_03.json' |
1520 | + CHANNEL = 'stable' |
1521 | + DEVICE = 'nexus7' |
1522 | + |
1523 | + maxDiff = None |
1524 | + |
1525 | + @configuration |
1526 | + def test_no_maximage(self, config_d): |
1527 | + # With no --maximage we get the full upgrade path. |
1528 | + self._setup_server_keyrings() |
1529 | + # We patch builtin print() rather than sys.stdout because the |
1530 | + # latter can mess with pdb output should we need to trace through |
1531 | + # the code. |
1532 | + capture = StringIO() |
1533 | + # Set up the build number. |
1534 | + touch_build(100) |
1535 | + with ExitStack() as resources: |
1536 | + resources.enter_context(capture_print(capture)) |
1537 | + resources.enter_context(argv('-C', config_d, '--dry-run')) |
1538 | + resources.push(machine_id('0000000000000000aaaaaaaaaaaaaaaa')) |
1539 | + cli_main() |
1540 | + self.assertMultiLineEqual(capture.getvalue(), """\ |
1541 | +Upgrade path is 200:201:304 |
1542 | +Target phase: 44% |
1543 | +""") |
1544 | + |
1545 | + @configuration |
1546 | + def test_maximage_inexact(self, config_d): |
1547 | + # With --maximage the winning path is capped. |
1548 | + self._setup_server_keyrings() |
1549 | + # We patch builtin print() rather than sys.stdout because the |
1550 | + # latter can mess with pdb output should we need to trace through |
1551 | + # the code. |
1552 | + capture = StringIO() |
1553 | + # Set up the build number. |
1554 | + touch_build(100) |
1555 | + with ExitStack() as resources: |
1556 | + resources.enter_context(capture_print(capture)) |
1557 | + resources.enter_context( |
1558 | + argv('-C', config_d, '--dry-run', '--maximage', '205')) |
1559 | + resources.push(machine_id('0000000000000000aaaaaaaaaaaaaaaa')) |
1560 | + cli_main() |
1561 | + self.assertMultiLineEqual(capture.getvalue(), """\ |
1562 | +Upgrade path is 200:201 |
1563 | +Target phase: 18% |
1564 | +""") |
1565 | + |
1566 | + @configuration |
1567 | + def test_maximage_exact(self, config_d): |
1568 | + # With --maximage the winning path is capped. |
1569 | + self._setup_server_keyrings() |
1570 | + # We patch builtin print() rather than sys.stdout because the |
1571 | + # latter can mess with pdb output should we need to trace through |
1572 | + # the code. |
1573 | + capture = StringIO() |
1574 | + # Set up the build number. |
1575 | + touch_build(100) |
1576 | + with ExitStack() as resources: |
1577 | + resources.enter_context(capture_print(capture)) |
1578 | + resources.enter_context( |
1579 | + argv('-C', config_d, '--dry-run', '--maximage', '201')) |
1580 | + resources.push(machine_id('0000000000000000aaaaaaaaaaaaaaaa')) |
1581 | + cli_main() |
1582 | + self.assertMultiLineEqual(capture.getvalue(), """\ |
1583 | +Upgrade path is 200:201 |
1584 | +Target phase: 18% |
1585 | +""") |
1586 | + |
1587 | + @configuration |
1588 | + def test_maximage_too_high(self, config_d): |
1589 | + # With --maximage set above the highest winning image, there is no |
1590 | + # effective cap. |
1591 | + self._setup_server_keyrings() |
1592 | + # We patch builtin print() rather than sys.stdout because the |
1593 | + # latter can mess with pdb output should we need to trace through |
1594 | + # the code. |
1595 | + capture = StringIO() |
1596 | + # Set up the build number. |
1597 | + touch_build(100) |
1598 | + with ExitStack() as resources: |
1599 | + resources.enter_context(capture_print(capture)) |
1600 | + resources.enter_context( |
1601 | + argv('-C', config_d, '--dry-run', '--maximage', '500')) |
1602 | + resources.push(machine_id('0000000000000000aaaaaaaaaaaaaaaa')) |
1603 | + cli_main() |
1604 | + self.assertMultiLineEqual(capture.getvalue(), """\ |
1605 | +Upgrade path is 200:201:304 |
1606 | +Target phase: 44% |
1607 | +""") |
1608 | + |
1609 | + @configuration |
1610 | + def test_maximage_lower_bound(self, config_d): |
1611 | + # With --maximage set at the lower bound, we still get an upgrade. |
1612 | + self._setup_server_keyrings() |
1613 | + # We patch builtin print() rather than sys.stdout because the |
1614 | + # latter can mess with pdb output should we need to trace through |
1615 | + # the code. |
1616 | + capture = StringIO() |
1617 | + # Set up the build number. |
1618 | + touch_build(100) |
1619 | + with ExitStack() as resources: |
1620 | + resources.enter_context(capture_print(capture)) |
1621 | + resources.enter_context( |
1622 | + argv('-C', config_d, '--dry-run', '--maximage', '200')) |
1623 | + resources.push(machine_id('0000000000000000aaaaaaaaaaaaaaaa')) |
1624 | + cli_main() |
1625 | + self.assertMultiLineEqual(capture.getvalue(), """\ |
1626 | +Upgrade path is 200 |
1627 | +Target phase: 1% |
1628 | +""") |
1629 | + |
1630 | + @configuration |
1631 | + def test_maximage_0(self, config_d): |
1632 | + # With --maximage set at zero, we get no upgrade path. |
1633 | + self._setup_server_keyrings() |
1634 | + # We patch builtin print() rather than sys.stdout because the |
1635 | + # latter can mess with pdb output should we need to trace through |
1636 | + # the code. |
1637 | + capture = StringIO() |
1638 | + # Set up the build number. |
1639 | + touch_build(100) |
1640 | + with ExitStack() as resources: |
1641 | + resources.enter_context(capture_print(capture)) |
1642 | + resources.enter_context( |
1643 | + argv('-C', config_d, '--dry-run', '--maximage', '0')) |
1644 | + resources.push(machine_id('0000000000000000aaaaaaaaaaaaaaaa')) |
1645 | + cli_main() |
1646 | + self.assertMultiLineEqual(capture.getvalue(), 'Already up-to-date\n') |
1647 | + |
1648 | + @configuration |
1649 | + def test_maximage_negative(self, config_d): |
1650 | + # With --maximage negative, we also get no upgrade path. |
1651 | + self._setup_server_keyrings() |
1652 | + # We patch builtin print() rather than sys.stdout because the |
1653 | + # latter can mess with pdb output should we need to trace through |
1654 | + # the code. |
1655 | + capture = StringIO() |
1656 | + # Set up the build number. |
1657 | + touch_build(100) |
1658 | + with ExitStack() as resources: |
1659 | + resources.enter_context(capture_print(capture)) |
1660 | + resources.enter_context( |
1661 | + argv('-C', config_d, '--dry-run', '--maximage', '-100')) |
1662 | + resources.push(machine_id('0000000000000000aaaaaaaaaaaaaaaa')) |
1663 | + cli_main() |
1664 | + self.assertMultiLineEqual(capture.getvalue(), 'Already up-to-date\n') |
1665 | + |
1666 | + @configuration |
1667 | + def test_maximage_m(self, config_d): |
1668 | + # With -m is a shortcut for --maximage. |
1669 | + self._setup_server_keyrings() |
1670 | + # We patch builtin print() rather than sys.stdout because the |
1671 | + # latter can mess with pdb output should we need to trace through |
1672 | + # the code. |
1673 | + capture = StringIO() |
1674 | + # Set up the build number. |
1675 | + touch_build(100) |
1676 | + with ExitStack() as resources: |
1677 | + resources.enter_context(capture_print(capture)) |
1678 | + resources.enter_context( |
1679 | + argv('-C', config_d, '--dry-run', '-m', '204')) |
1680 | + resources.push(machine_id('0000000000000000aaaaaaaaaaaaaaaa')) |
1681 | + cli_main() |
1682 | + self.assertMultiLineEqual(capture.getvalue(), """\ |
1683 | +Upgrade path is 200:201 |
1684 | +Target phase: 18% |
1685 | +""") |
1686 | + |
1687 | + |
1688 | class TestCLIDuplicateDestinations(ServerTestBase): |
1689 | INDEX_FILE = 'main.index_04.json' |
1690 | CHANNEL_FILE = 'main.channels_03.json' |
1691 | @@ -852,60 +1025,6 @@ |
1692 | """) |
1693 | |
1694 | @configuration |
1695 | - def test_no_reboot(self, config_d): |
1696 | - # `system-image-cli --no-reboot` downloads everything but does not |
1697 | - # apply the update. THIS IS DEPRECATED IN SI 3.0. |
1698 | - self._setup_server_keyrings() |
1699 | - capture = StringIO() |
1700 | - self._resources.enter_context(capture_print(capture)) |
1701 | - self._resources.enter_context( |
1702 | - argv('-C', config_d, '--no-reboot', '-b', 0, '-c', 'daily')) |
1703 | - mock = self._resources.enter_context( |
1704 | - patch('systemimage.apply.Reboot.apply')) |
1705 | - # Do not use self._resources to manage the check_output mock. Because |
1706 | - # of the nesting order of the @configuration decorator and the base |
1707 | - # class's tearDown(), using self._resources causes the mocks to be |
1708 | - # unwound in the wrong order, affecting future tests. |
1709 | - with patch('systemimage.device.check_output', return_value='manta'): |
1710 | - cli_main() |
1711 | - # The reboot method was never called. |
1712 | - self.assertFalse(mock.called) |
1713 | - # All the expected files should be downloaded. |
1714 | - self.assertEqual(set(os.listdir(config.updater.data_partition)), set([ |
1715 | - 'blacklist.tar.xz', |
1716 | - 'blacklist.tar.xz.asc', |
1717 | - ])) |
1718 | - self.assertEqual(set(os.listdir(config.updater.cache_partition)), set([ |
1719 | - '5.txt', |
1720 | - '5.txt.asc', |
1721 | - '6.txt', |
1722 | - '6.txt.asc', |
1723 | - '7.txt', |
1724 | - '7.txt.asc', |
1725 | - 'device-signing.tar.xz', |
1726 | - 'device-signing.tar.xz.asc', |
1727 | - 'image-master.tar.xz', |
1728 | - 'image-master.tar.xz.asc', |
1729 | - 'image-signing.tar.xz', |
1730 | - 'image-signing.tar.xz.asc', |
1731 | - 'ubuntu_command', |
1732 | - ])) |
1733 | - path = os.path.join(config.updater.cache_partition, 'ubuntu_command') |
1734 | - with open(path, 'r', encoding='utf-8') as fp: |
1735 | - command = fp.read() |
1736 | - self.assertMultiLineEqual(command, """\ |
1737 | -load_keyring image-master.tar.xz image-master.tar.xz.asc |
1738 | -load_keyring image-signing.tar.xz image-signing.tar.xz.asc |
1739 | -load_keyring device-signing.tar.xz device-signing.tar.xz.asc |
1740 | -format system |
1741 | -mount system |
1742 | -update 6.txt 6.txt.asc |
1743 | -update 7.txt 7.txt.asc |
1744 | -update 5.txt 5.txt.asc |
1745 | -unmount system |
1746 | -""") |
1747 | - |
1748 | - @configuration |
1749 | def test_g(self, config_d): |
1750 | # `system-image-cli -g` downloads everything but does not reboot into |
1751 | # recovery. |
1752 | @@ -1287,8 +1406,7 @@ |
1753 | # before calling .Exit(). However, due to timing issues, it's |
1754 | # possible we get here before the process was ever started, and thus |
1755 | # the daemon won't be killed. Conditionally deleting it now will |
1756 | - # allow the .Info() call below to re-active the process and thus |
1757 | - # re-create the directory. |
1758 | + # allow re-activation to re-create the directory. |
1759 | try: |
1760 | shutil.rmtree(config.system.tempdir) |
1761 | except FileNotFoundError: |
1762 | @@ -1502,7 +1620,7 @@ |
1763 | resources.enter_context( |
1764 | patch('systemimage.main.LINE_LENGTH', 10)) |
1765 | resources.enter_context( |
1766 | - argv('-C', config_d, '-b', '0', '--no-reboot', |
1767 | + argv('-C', config_d, '-b', '0', '--no-apply', |
1768 | '--progress', 'dots')) |
1769 | cli_main() |
1770 | # There should be some dots in the stderr. |
1771 | @@ -1512,7 +1630,7 @@ |
1772 | def test_json_progress(self, config_d): |
1773 | # --progress=json prints some JSON to stdout. |
1774 | self._setup_server_keyrings() |
1775 | - with argv('-C', config_d, '-b', '0', '--no-reboot', |
1776 | + with argv('-C', config_d, '-b', '0', '--no-apply', |
1777 | '--progress', 'json'): |
1778 | cli_main() |
1779 | # stdout is now filled with JSON goodness. We can't assert too much |
1780 | @@ -1540,7 +1658,7 @@ |
1781 | resources.enter_context( |
1782 | patch('systemimage.main._LogfileProgress', Testable)) |
1783 | resources.enter_context( |
1784 | - argv('-C', config_d, '-b', '0', '--no-reboot', |
1785 | + argv('-C', config_d, '-b', '0', '--no-apply', |
1786 | '--progress', 'logfile')) |
1787 | cli_main() |
1788 | self.assertGreater(log_mock.debug.call_count, 4) |
1789 | @@ -1563,7 +1681,7 @@ |
1790 | resources.enter_context( |
1791 | patch('systemimage.main._LogfileProgress', Testable)) |
1792 | resources.enter_context( |
1793 | - argv('-C', config_d, '-b', '0', '--no-reboot', |
1794 | + argv('-C', config_d, '-b', '0', '--no-apply', |
1795 | '--progress', 'dots', |
1796 | '--progress', 'json', |
1797 | '--progress', 'logfile')) |
1798 | @@ -1586,7 +1704,7 @@ |
1799 | # An unknown progress type results in an error. |
1800 | with ExitStack() as resources: |
1801 | resources.enter_context( |
1802 | - argv('-C', config_d, '-b', '0', '--no-reboot', |
1803 | + argv('-C', config_d, '-b', '0', '--no-apply', |
1804 | '--progress', 'not-a-meter')) |
1805 | cm = resources.enter_context(self.assertRaises(SystemExit)) |
1806 | cli_main() |
1807 | @@ -1602,7 +1720,7 @@ |
1808 | self._setup_server_keyrings() |
1809 | with ExitStack() as resources: |
1810 | resources.enter_context( |
1811 | - argv('-C', config.config_d, '-b', '0', '--no-reboot', |
1812 | + argv('-C', config.config_d, '-b', '0', '--no-apply', |
1813 | '--progress', 'json')) |
1814 | # It's maybe not the best thing to hook into a private |
1815 | # implementation function in order to cause the state machine to |
1816 | @@ -1625,7 +1743,7 @@ |
1817 | self._setup_server_keyrings() |
1818 | with ExitStack() as resources: |
1819 | resources.enter_context( |
1820 | - argv('-C', config.config_d, '-b', '0', '--no-reboot')) |
1821 | + argv('-C', config.config_d, '-b', '0', '--no-apply')) |
1822 | # It's maybe not the best thing to hook into a private |
1823 | # implementation function in order to cause the state machine to |
1824 | # fail, but it's expedient and works with both downloaders. |
1825 | @@ -1638,3 +1756,50 @@ |
1826 | # the error record. |
1827 | lines = self._stdout.getvalue().splitlines() |
1828 | self.assertEqual(len(lines), 0) |
1829 | + |
1830 | + |
1831 | +@unittest.skipIf(USING_PYCURL, 'UDM-only tests') |
1832 | +class TestCLIGSMOverride(ServerTestBase): |
1833 | + INDEX_FILE = 'main.index_05.json' |
1834 | + CHANNEL_FILE = 'main.channels_02.json' |
1835 | + CHANNEL = 'daily' |
1836 | + DEVICE = 'manta' |
1837 | + |
1838 | + @configuration |
1839 | + def test_no_gsm_override(self, config_d): |
1840 | + # Without --override-gsm, the normal auto_download setting rules. |
1841 | + self._setup_server_keyrings() |
1842 | + Settings().set('auto_download', '1') |
1843 | + with ExitStack() as resources: |
1844 | + resources.enter_context(argv('-C', config_d, '-b', '0')) |
1845 | + mock = resources.enter_context( |
1846 | + patch('systemimage.udm.UDMDownloadManager._set_gsm')) |
1847 | + exit_code = cli_main() |
1848 | + self.assertEqual(exit_code, 0) |
1849 | + # The last time the mock was called, was for the downloads of the data |
1850 | + # files. Here, the first argument that the method was called with is |
1851 | + # the interface, but the second argument is the flag we care about. |
1852 | + # It's called as a keyword argument, so dig this out of the mock's |
1853 | + # call args. |
1854 | + args, kws = mock.call_args |
1855 | + self.assertFalse(kws['allow_gsm']) |
1856 | + |
1857 | + @configuration |
1858 | + def test_gsm_override(self, config_d): |
1859 | + # --override-gsm overrides any local setting for auto_download. |
1860 | + self._setup_server_keyrings() |
1861 | + Settings().set('auto_download', '1') |
1862 | + with ExitStack() as resources: |
1863 | + resources.enter_context( |
1864 | + argv('-C', config_d, '-b', '0', '--override-gsm')) |
1865 | + mock = resources.enter_context( |
1866 | + patch('systemimage.udm.UDMDownloadManager._set_gsm')) |
1867 | + exit_code = cli_main() |
1868 | + self.assertEqual(exit_code, 0) |
1869 | + # The last time the mock was called, was for the downloads of the data |
1870 | + # files. Here, the first argument that the method was called with is |
1871 | + # the interface, but the second argument is the flag we care about. |
1872 | + # It's called as a keyword argument, so dig this out of the mock's |
1873 | + # call args. |
1874 | + args, kws = mock.call_args |
1875 | + self.assertTrue(kws['allow_gsm']) |
1876 | |
1877 | === modified file 'systemimage/tests/test_scores.py' |
1878 | --- systemimage/tests/test_scores.py 2015-05-08 21:41:15 +0000 |
1879 | +++ systemimage/tests/test_scores.py 2016-03-03 20:25:53 +0000 |
1880 | @@ -1,4 +1,4 @@ |
1881 | -# Copyright (C) 2013-2015 Canonical Ltd. |
1882 | +# Copyright (C) 2013-2016 Canonical Ltd. |
1883 | # Author: Barry Warsaw <barry@ubuntu.com> |
1884 | |
1885 | # This program is free software: you can redistribute it and/or modify |
1886 | |
1887 | === modified file 'systemimage/tests/test_settings.py' |
1888 | --- systemimage/tests/test_settings.py 2015-05-08 21:41:15 +0000 |
1889 | +++ systemimage/tests/test_settings.py 2016-03-03 20:25:53 +0000 |
1890 | @@ -1,4 +1,4 @@ |
1891 | -# Copyright (C) 2013-2015 Canonical Ltd. |
1892 | +# Copyright (C) 2013-2016 Canonical Ltd. |
1893 | # Author: Barry Warsaw <barry@ubuntu.com> |
1894 | |
1895 | # This program is free software: you can redistribute it and/or modify |
1896 | |
1897 | === modified file 'systemimage/tests/test_state.py' |
1898 | --- systemimage/tests/test_state.py 2015-05-08 21:41:15 +0000 |
1899 | +++ systemimage/tests/test_state.py 2016-03-03 20:25:53 +0000 |
1900 | @@ -1,4 +1,4 @@ |
1901 | -# Copyright (C) 2013-2015 Canonical Ltd. |
1902 | +# Copyright (C) 2013-2016 Canonical Ltd. |
1903 | # Author: Barry Warsaw <barry@ubuntu.com> |
1904 | |
1905 | # This program is free software: you can redistribute it and/or modify |
1906 | @@ -23,6 +23,7 @@ |
1907 | 'TestDailyProposed', |
1908 | 'TestFileOrder', |
1909 | 'TestKeyringDoubleChecks', |
1910 | + 'TestMaximumImage', |
1911 | 'TestMiscellaneous', |
1912 | 'TestPhasedUpdates', |
1913 | 'TestState', |
1914 | @@ -41,6 +42,7 @@ |
1915 | from datetime import datetime, timedelta, timezone |
1916 | from functools import partial |
1917 | from subprocess import CalledProcessError |
1918 | +from systemimage.candidates import version_filter |
1919 | from systemimage.config import config |
1920 | from systemimage.download import DuplicateDestinationError |
1921 | from systemimage.gpg import Context, SignatureError |
1922 | @@ -770,9 +772,35 @@ |
1923 | touch_build(100) |
1924 | def filter_out_everything(candidates): |
1925 | return [] |
1926 | - state = State(candidate_filter=filter_out_everything) |
1927 | - state.run_thru('calculate_winner') |
1928 | - self.assertEqual(len(state.winner), 0) |
1929 | + state = State() |
1930 | + state.candidate_filter=filter_out_everything |
1931 | + state.run_thru('calculate_winner') |
1932 | + self.assertEqual(state.winner, []) |
1933 | + |
1934 | + |
1935 | +class TestMaximumImage(ServerTestBase): |
1936 | + INDEX_FILE = 'state.index_01.json' |
1937 | + CHANNEL_FILE = 'state.channels_02.json' |
1938 | + CHANNEL = 'stable' |
1939 | + DEVICE = 'nexus7' |
1940 | + |
1941 | + @configuration |
1942 | + def test_maximum_image(self, config): |
1943 | + # Given a winning upgrade path, we can ceiling the maximum image |
1944 | + # number from that path to be applied. This is useful for image |
1945 | + # testing purposes. |
1946 | + self._setup_server_keyrings() |
1947 | + touch_build(100) |
1948 | + state = State() |
1949 | + state.run_thru('calculate_winner') |
1950 | + self.assertEqual([image.version for image in state.winner], |
1951 | + [200, 201, 304]) |
1952 | + # Now we'll try again, but this time, put a cap on the upper |
1953 | + # bound of the images. |
1954 | + state = State() |
1955 | + state.winner_filter = version_filter(200) |
1956 | + state.run_thru('calculate_winner') |
1957 | + self.assertEqual([image.version for image in state.winner], [200]) |
1958 | |
1959 | |
1960 | class TestStateNewChannelsFormat(ServerTestBase): |
1961 | |
1962 | === modified file 'systemimage/tests/test_winner.py' |
1963 | --- systemimage/tests/test_winner.py 2015-05-08 21:41:15 +0000 |
1964 | +++ systemimage/tests/test_winner.py 2016-03-03 20:25:53 +0000 |
1965 | @@ -1,4 +1,4 @@ |
1966 | -# Copyright (C) 2013-2015 Canonical Ltd. |
1967 | +# Copyright (C) 2013-2016 Canonical Ltd. |
1968 | # Author: Barry Warsaw <barry@ubuntu.com> |
1969 | |
1970 | # This program is free software: you can redistribute it and/or modify |
1971 | |
1972 | === modified file 'systemimage/udm.py' |
1973 | --- systemimage/udm.py 2015-03-03 16:14:21 +0000 |
1974 | +++ systemimage/udm.py 2016-03-03 20:25:53 +0000 |
1975 | @@ -1,4 +1,4 @@ |
1976 | -# Copyright (C) 2014-2015 Canonical Ltd. |
1977 | +# Copyright (C) 2014-2016 Canonical Ltd. |
1978 | # Author: Barry Warsaw <barry@ubuntu.com> |
1979 | |
1980 | # This program is free software: you can redistribute it and/or modify |
1981 | @@ -51,10 +51,12 @@ |
1982 | |
1983 | |
1984 | class DownloadReactor(Reactor): |
1985 | - def __init__(self, bus, object_path, callback=None, pausable=False): |
1986 | + def __init__(self, bus, object_path, callback=None, |
1987 | + pausable=False, signal_started=False): |
1988 | super().__init__(bus) |
1989 | self._callback = callback |
1990 | self._pausable = pausable |
1991 | + self._signal_started = signal_started |
1992 | # For _do_pause() percentage calculation. |
1993 | self._received = 0 |
1994 | self._total = 0 |
1995 | @@ -71,6 +73,8 @@ |
1996 | |
1997 | def _do_started(self, signal, path, started): |
1998 | _print('STARTED:', started) |
1999 | + if self._signal_started and config.dbus_service is not None: |
2000 | + config.dbus_service.DownloadStarted() |
2001 | |
2002 | def _do_finished(self, signal, path, local_paths): |
2003 | _print('FINISHED:', local_paths) |
2004 | @@ -127,7 +131,7 @@ |
2005 | self.callbacks.append(callback) |
2006 | self._iface = None |
2007 | |
2008 | - def _get_files(self, records, pausable): |
2009 | + def _get_files(self, records, pausable, signal_started): |
2010 | assert self._iface is None |
2011 | bus = dbus.SystemBus() |
2012 | service = bus.get_object(DOWNLOADER_INTERFACE, '/') |
2013 | @@ -144,12 +148,18 @@ |
2014 | # Are GSM downloads allowed? Yes, except if auto_download is set to 1 |
2015 | # (i.e. wifi-only). |
2016 | allow_gsm = Settings().get('auto_download') != '1' |
2017 | + # See if the CLI was called with --override-gsm. |
2018 | + if not allow_gsm and config.override_gsm: |
2019 | + log.info('GSM-only overridden') |
2020 | + allow_gsm = True |
2021 | + log.info('Allow GSM? {}', ('Yes' if allow_gsm else 'No')) |
2022 | UDMDownloadManager._set_gsm(self._iface, allow_gsm=allow_gsm) |
2023 | # Start the download. |
2024 | reactor = DownloadReactor( |
2025 | - bus, object_path, self._reactor_callback, pausable) |
2026 | + bus, object_path, self._reactor_callback, pausable, signal_started) |
2027 | reactor.schedule(self._iface.start) |
2028 | log.info('[{}] Running group download reactor', object_path) |
2029 | + log.info('self: {}, self._iface: {}', self, self._iface) |
2030 | reactor.run() |
2031 | # This download is complete so the object path is no longer |
2032 | # applicable. Setting this to None will cause subsequent cancels to |
2033 | @@ -188,6 +198,27 @@ |
2034 | # This is a separate method for easier testing via mocks. |
2035 | iface.allowGSMDownload(allow_gsm) |
2036 | |
2037 | + @staticmethod |
2038 | + def allow_gsm(): |
2039 | + """See `DownloadManagerBase`.""" |
2040 | + # We can't rely on self._iface being the interface of the group |
2041 | + # download object. Use getAllDownloads() on UDM to get the group |
2042 | + # download object path, assert that there is only one group download |
2043 | + # in progress, then call allowGSMDownload() on that. |
2044 | + bus = dbus.SystemBus() |
2045 | + service = bus.get_object(DOWNLOADER_INTERFACE, '/') |
2046 | + iface = dbus.Interface(service, MANAGER_INTERFACE) |
2047 | + try: |
2048 | + object_paths = iface.getAllDownloads() |
2049 | + except TypeError: |
2050 | + # If there is no download in progress, udm will cause this |
2051 | + # exception to occur. Allow this to no-op. |
2052 | + log.info('Ignoring GSM force when no download is in progress.') |
2053 | + return |
2054 | + assert len(object_paths) == 1, object_paths |
2055 | + download = bus.get_object(OBJECT_NAME, object_paths[0]) |
2056 | + dbus.Interface(download, OBJECT_INTERFACE).allowGSMDownload(True) |
2057 | + |
2058 | def cancel(self): |
2059 | """Cancel any current downloads.""" |
2060 | if self._iface is None: |
2061 | |
2062 | === modified file 'systemimage/version.txt' |
2063 | --- systemimage/version.txt 2015-09-25 20:34:10 +0000 |
2064 | +++ systemimage/version.txt 2016-03-03 20:25:53 +0000 |
2065 | @@ -1,1 +1,1 @@ |
2066 | -3.0.2 |
2067 | +3.1 |
2068 | |
2069 | === modified file 'tox.ini' |
2070 | --- tox.ini 2015-05-08 21:41:15 +0000 |
2071 | +++ tox.ini 2016-03-03 20:25:53 +0000 |
2072 | @@ -1,6 +1,7 @@ |
2073 | [tox] |
2074 | -envlist = {py34,coverage}-{udm,curl} |
2075 | +envlist = {py34,py35,coverage}-{udm,curl} |
2076 | recreate = True |
2077 | +skip_missing_interpreters = True |
2078 | |
2079 | [coverage] |
2080 | rcfile = {toxinidir}/{envname}.ini |
2081 | @@ -10,14 +11,17 @@ |
2082 | [testenv] |
2083 | commands = |
2084 | py34: python -m nose2 -v |
2085 | - coverage: python /usr/bin/python3-coverage run {[coverage]rc} -m nose2 -v |
2086 | - coverage: python3-coverage combine {[coverage]rc} |
2087 | - coverage: python3-coverage html {[coverage]rc} {[coverage]dir} |
2088 | + py35: python -m nose2 -v |
2089 | + coverage: python -m coverage run {[coverage]rc} -m nose2 -v |
2090 | + coverage: python -m coverage combine {[coverage]rc} |
2091 | + coverage: python -m coverage html {[coverage]rc} {[coverage]dir} |
2092 | + coverage: python -m coverage report -m {[coverage]rc} |
2093 | sitepackages = True |
2094 | +deps = |
2095 | + coverage: coverage |
2096 | indexserver = |
2097 | default = http://missing.example.com |
2098 | usedevelop = True |
2099 | -whitelist_externals = python3-coverage |
2100 | setenv = |
2101 | SYSTEMIMAGE_REACTOR_TIMEOUT=60 |
2102 | coverage: COVERAGE_PROCESS_START={[coverage]rcfile} |