Merge lp:~barry/ubuntu-system-image/citrain31 into lp:~ubuntu-managed-branches/ubuntu-system-image/system-image

Proposed by Barry Warsaw
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
Reviewer Review Type Date Requested Status
Ubuntu CI managed package branches Pending
Review via email: mp+287848@code.launchpad.net

Description of the change

system-image (3.1-0ubuntu1) xenial; urgency=medium

  * New upstream release.
    - LP: #1386302 - In ``system-image-cli``, add a ``-m``/``--maximage``
      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-download-manager:
      + Added ``ForceAllowGSMDownload()`` method to the D-Bus API.
      + Added ``DownloadStarted`` D-Bus signal, which gets sent when the
        download for an update has begun.
      + Added ``--override-gsm`` flag to ``system-image-cli``.
  * 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

To post a comment you must log in.
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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}

Subscribers

People subscribed via source and target branches