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

Proposed by Barry Warsaw on 2015-09-25
Status: Merged
Approved by: Barry Warsaw on 2015-09-30
Approved revision: 245
Merged at revision: 244
Proposed branch: lp:~barry/ubuntu-system-image/citrain302
Merge into: lp:~ubuntu-managed-branches/ubuntu-system-image/system-image
Diff against target: 395 lines (+147/-18)
14 files modified
NEWS.rst (+5/-0)
PKG-INFO (+1/-1)
debian/changelog (+11/-0)
debian/rules (+3/-0)
setup.cfg (+1/-1)
system_image.egg-info/PKG-INFO (+1/-1)
system_image.egg-info/SOURCES.txt (+1/-0)
system_image.egg-info/pbr.json (+1/-0)
systemimage/helpers.py (+12/-6)
systemimage/logging.py (+10/-6)
systemimage/testing/dbus.py (+28/-0)
systemimage/tests/test_dbus.py (+26/-2)
systemimage/tests/test_helpers.py (+46/-0)
systemimage/version.txt (+1/-1)
To merge this branch: bzr merge lp:~barry/ubuntu-system-image/citrain302
Reviewer Review Type Date Requested Status
Barry Warsaw Pending
Review via email: mp+272481@code.launchpad.net

Commit message

Don't crash when one of the .ini files is a dangling symlink. (LP: #1495688)

Description of the change

3.0.2 (2015-09-22)
==================
 * Don't crash when one of the .ini files is a dangling symlink.
   (LP: #1495688)

To post a comment you must log in.
245. By Barry Warsaw on 2015-09-28

d/rules: override_dh_auto_clean because otherwise, pybuild will
remove the .egg-info files and that causes the Jenkins job in the CI
train to fail.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'NEWS.rst'
--- NEWS.rst 2015-06-17 15:18:22 +0000
+++ NEWS.rst 2015-09-28 21:36:51 +0000
@@ -2,6 +2,11 @@
2NEWS for system-image updater2NEWS for system-image updater
3=============================3=============================
44
53.0.2 (2015-09-22)
6==================
7 * Don't crash when one of the .ini files is a dangling symlink.
8 (LP: #1495688)
9
53.0.1 (2015-06-16)103.0.1 (2015-06-16)
6==================11==================
7 * When `--progress=json` is used, print an error record to stdout if the12 * When `--progress=json` is used, print an error record to stdout if the
813
=== modified file 'PKG-INFO'
--- PKG-INFO 2015-06-17 15:18:22 +0000
+++ PKG-INFO 2015-09-28 21:36:51 +0000
@@ -1,6 +1,6 @@
1Metadata-Version: 1.01Metadata-Version: 1.0
2Name: system-image2Name: system-image
3Version: 3.0.13Version: 3.0.2
4Summary: Ubuntu System Image Based Upgrades4Summary: Ubuntu System Image Based Upgrades
5Home-page: UNKNOWN5Home-page: UNKNOWN
6Author: Barry Warsaw6Author: Barry Warsaw
77
=== modified file 'debian/changelog'
--- debian/changelog 2015-06-18 16:23:37 +0000
+++ debian/changelog 2015-09-28 21:36:51 +0000
@@ -1,3 +1,14 @@
1system-image (3.0.2-0ubuntu1) wily; urgency=medium
2
3 * New upstream release.
4 - LP: #1495688 - Don't crash when one of the .ini files is a dangling
5 symlink.
6 - d/rules: override_dh_auto_clean because otherwise, pybuild will
7 remove the .egg-info files and that causes the Jenkins job in the CI
8 train to fail.
9
10 -- Barry Warsaw <barry@ubuntu.com> Fri, 25 Sep 2015 15:28:37 -0400
11
1system-image (3.0.1-0ubuntu1) wily; urgency=medium12system-image (3.0.1-0ubuntu1) wily; urgency=medium
213
3 * New rebuild forced.14 * New rebuild forced.
415
=== modified file 'debian/rules'
--- debian/rules 2015-05-09 15:50:39 +0000
+++ debian/rules 2015-09-28 21:36:51 +0000
@@ -56,3 +56,6 @@
56 rst2man dbus-manpage.rst > debian/tmp/system-image-dbus.man56 rst2man dbus-manpage.rst > debian/tmp/system-image-dbus.man
57 rst2man ini-manpage.rst > debian/tmp/client-ini.man57 rst2man ini-manpage.rst > debian/tmp/client-ini.man
58 dh_installman58 dh_installman
59
60# We don't want the buildds to remove the .egg-info files.
61override_dh_auto_clean:
5962
=== modified file 'setup.cfg'
--- setup.cfg 2015-06-17 15:18:22 +0000
+++ setup.cfg 2015-09-28 21:36:51 +0000
@@ -4,7 +4,7 @@
4logging-filter = systemimage4logging-filter = systemimage
55
6[egg_info]6[egg_info]
7tag_date = 0
8tag_svn_revision = 07tag_svn_revision = 0
9tag_build = 8tag_build =
9tag_date = 0
1010
1111
=== modified file 'system_image.egg-info/PKG-INFO'
--- system_image.egg-info/PKG-INFO 2015-06-17 15:18:22 +0000
+++ system_image.egg-info/PKG-INFO 2015-09-28 21:36:51 +0000
@@ -1,6 +1,6 @@
1Metadata-Version: 1.01Metadata-Version: 1.0
2Name: system-image2Name: system-image
3Version: 3.0.13Version: 3.0.2
4Summary: Ubuntu System Image Based Upgrades4Summary: Ubuntu System Image Based Upgrades
5Home-page: UNKNOWN5Home-page: UNKNOWN
6Author: Barry Warsaw6Author: Barry Warsaw
77
=== modified file 'system_image.egg-info/SOURCES.txt'
--- system_image.egg-info/SOURCES.txt 2015-05-08 21:41:15 +0000
+++ system_image.egg-info/SOURCES.txt 2015-09-28 21:36:51 +0000
@@ -14,6 +14,7 @@
14system_image.egg-info/SOURCES.txt14system_image.egg-info/SOURCES.txt
15system_image.egg-info/dependency_links.txt15system_image.egg-info/dependency_links.txt
16system_image.egg-info/entry_points.txt16system_image.egg-info/entry_points.txt
17system_image.egg-info/pbr.json
17system_image.egg-info/requires.txt18system_image.egg-info/requires.txt
18system_image.egg-info/top_level.txt19system_image.egg-info/top_level.txt
19systemimage/__init__.py20systemimage/__init__.py
2021
=== added file 'system_image.egg-info/pbr.json'
--- system_image.egg-info/pbr.json 1970-01-01 00:00:00 +0000
+++ system_image.egg-info/pbr.json 2015-09-28 21:36:51 +0000
@@ -0,0 +1,1 @@
1{"is_release": true, "git_version": "4ecc87c"}
0\ No newline at end of file2\ No newline at end of file
13
=== modified file 'systemimage/helpers.py'
--- systemimage/helpers.py 2015-05-08 21:41:15 +0000
+++ systemimage/helpers.py 2015-09-28 21:36:51 +0000
@@ -41,7 +41,7 @@
41import logging41import logging
42import tempfile42import tempfile
4343
44from contextlib import ExitStack, contextmanager44from contextlib import ExitStack, contextmanager, suppress
45from datetime import datetime, timedelta45from datetime import datetime, timedelta
46from hashlib import sha25646from hashlib import sha256
47from importlib import import_module47from importlib import import_module
@@ -252,13 +252,19 @@
252 try:252 try:
253 timestamp = datetime.fromtimestamp(os.stat(LAST_UPDATE_FILE).st_mtime)253 timestamp = datetime.fromtimestamp(os.stat(LAST_UPDATE_FILE).st_mtime)
254 except (FileNotFoundError, PermissionError):254 except (FileNotFoundError, PermissionError):
255 # We fall back to the latest mtime of the config.d/*.ini files.255 # We fall back to the latest mtime of the config.d/*.ini files. For
256 timestamps = sorted(256 # robustness, watch out for two possibilities: the config file could
257 datetime.fromtimestamp(path.stat().st_mtime)257 # have been deleted after the system started up (thus making
258 for path in config.ini_files)258 # config.ini_files include nonexistent files), and the ini file could
259 # be a dangling symlink. For the latter, use lstat().
260 timestamps = []
261 for path in config.ini_files:
262 with suppress(FileNotFoundError):
263 timestamps.append(
264 datetime.fromtimestamp(path.lstat().st_mtime))
259 if len(timestamps) == 0:265 if len(timestamps) == 0:
260 return 'Unknown'266 return 'Unknown'
261 timestamp = timestamps[-1]267 timestamp = sorted(timestamps)[-1]
262 return str(timestamp.replace(microsecond=0))268 return str(timestamp.replace(microsecond=0))
263269
264270
265271
=== modified file 'systemimage/logging.py'
--- systemimage/logging.py 2015-05-08 21:41:15 +0000
+++ systemimage/logging.py 2015-09-28 21:36:51 +0000
@@ -18,6 +18,7 @@
18__all__ = [18__all__ = [
19 'debug_logging',19 'debug_logging',
20 'initialize',20 'initialize',
21 'make_handler',
21 ]22 ]
2223
2324
@@ -68,13 +69,16 @@
68 return super().getMessage()69 return super().getMessage()
6970
7071
71def _make_handler(path):72def make_handler(path):
72 # issue21539 - mkdir(..., exist_ok=True)73 # issue21539 - mkdir(..., exist_ok=True)
73 with suppress(FileExistsError):74 with suppress(FileExistsError):
74 path.parent.mkdir(DEFAULT_DIRMODE, parents=True)75 path.parent.mkdir(DEFAULT_DIRMODE, parents=True)
75 path.touch(LOGFILE_PERMISSIONS)76 path.touch(LOGFILE_PERMISSIONS)
76 # Our handler will output in UTF-8 using {} style logging.77 # Our handler will output in UTF-8 using {} style logging.
77 return logging.FileHandler(bytes(path), encoding='utf-8')78 formatter = logging.Formatter(style='{', fmt=MSG_FMT, datefmt=DATE_FMT)
79 handler = logging.FileHandler(bytes(path), encoding='utf-8')
80 handler.setFormatter(formatter)
81 return handler
7882
7983
80def initialize(*, verbosity=0):84def initialize(*, verbosity=0):
@@ -95,13 +99,11 @@
95 # Now configure the application level logger based on the ini file.99 # Now configure the application level logger based on the ini file.
96 log = logging.getLogger(name)100 log = logging.getLogger(name)
97 try:101 try:
98 handler = _make_handler(Path(config.system.logfile))102 handler = make_handler(Path(config.system.logfile))
99 except PermissionError:103 except PermissionError:
100 handler = _make_handler(104 handler = make_handler(
101 Path(xdg_cache_home) / 'system-image' / 'client.log')105 Path(xdg_cache_home) / 'system-image' / 'client.log')
102 handler.setLevel(level)106 handler.setLevel(level)
103 formatter = logging.Formatter(style='{', fmt=MSG_FMT, datefmt=DATE_FMT)
104 handler.setFormatter(formatter)
105 log.addHandler(handler)107 log.addHandler(handler)
106 log.propagate = False108 log.propagate = False
107 # If we want more verbosity, add a stream handler.109 # If we want more verbosity, add a stream handler.
@@ -111,6 +113,8 @@
111 else: # pragma: no cover113 else: # pragma: no cover
112 handler = logging.StreamHandler(stream=sys.stderr)114 handler = logging.StreamHandler(stream=sys.stderr)
113 handler.setLevel(level)115 handler.setLevel(level)
116 formatter = logging.Formatter(
117 style='{', fmt=MSG_FMT, datefmt=DATE_FMT)
114 handler.setFormatter(formatter)118 handler.setFormatter(formatter)
115 log.addHandler(handler)119 log.addHandler(handler)
116 # Set the overall level on the log object to the minimum level.120 # Set the overall level on the log object to the minimum level.
117121
=== modified file 'systemimage/testing/dbus.py'
--- systemimage/testing/dbus.py 2015-05-08 21:41:15 +0000
+++ systemimage/testing/dbus.py 2015-09-28 21:36:51 +0000
@@ -22,6 +22,7 @@
2222
2323
24import os24import os
25import logging
2526
26try:27try:
27 import pycurl28 import pycurl
@@ -30,10 +31,12 @@
3031
31from dbus.service import method, signal32from dbus.service import method, signal
32from gi.repository import GLib33from gi.repository import GLib
34from pathlib import Path
33from systemimage.api import Mediator35from systemimage.api import Mediator
34from systemimage.config import config36from systemimage.config import config
35from systemimage.dbus import Service, log_and_exit37from systemimage.dbus import Service, log_and_exit
36from systemimage.helpers import MiB, makedirs, safe_remove, version_detail38from systemimage.helpers import MiB, makedirs, safe_remove, version_detail
39from systemimage.logging import make_handler
37from unittest.mock import patch40from unittest.mock import patch
3841
3942
@@ -74,6 +77,10 @@
74class _LiveTestableService(Service):77class _LiveTestableService(Service):
75 """For testing purposes only."""78 """For testing purposes only."""
7679
80 def __init__(self, bus, object_path, loop):
81 super().__init__(bus, object_path, loop)
82 self._debug_handler = None
83
77 @log_and_exit84 @log_and_exit
78 @method('com.canonical.SystemImage')85 @method('com.canonical.SystemImage')
79 def Reset(self):86 def Reset(self):
@@ -103,6 +110,27 @@
103 def TornDown(self):110 def TornDown(self):
104 pass111 pass
105112
113 @log_and_exit
114 @method('com.canonical.SystemImage',
115 in_signature='ss',
116 out_signature='ss')
117 def DebugDBusTo(self, filename, level_name):
118 # Get the existing logging level and logging file name.
119 dbus_log = logging.getLogger('systemimage.dbus')
120 old_level = logging.getLevelName(dbus_log.getEffectiveLevel())
121 old_filename = config.system.logfile
122 # Remove any previous D-Bus debugging handler.
123 if self._debug_handler is not None:
124 dbus_log.removeHandler(self._debug_handler)
125 self._debug_handler = None
126 new_level = getattr(logging, level_name.upper())
127 dbus_log.setLevel(new_level)
128 if filename != '':
129 self._debug_handler = make_handler(Path(filename))
130 self._debug_handler.setLevel(new_level)
131 dbus_log.addHandler(self._debug_handler)
132 return old_filename, old_level
133
106134
107class _UpdateAutoSuccess(Service):135class _UpdateAutoSuccess(Service):
108 """Normal update in auto-download mode."""136 """Normal update in auto-download mode."""
109137
=== modified file 'systemimage/tests/test_dbus.py'
--- systemimage/tests/test_dbus.py 2015-05-08 21:41:15 +0000
+++ systemimage/tests/test_dbus.py 2015-09-28 21:36:51 +0000
@@ -44,10 +44,12 @@
4444
4545
46import os46import os
47import sys
47import dbus48import dbus
48import json49import json
49import time50import time
50import shutil51import shutil
52import tempfile
51import unittest53import unittest
5254
53from contextlib import ExitStack, suppress55from contextlib import ExitStack, suppress
@@ -61,6 +63,7 @@
61from systemimage.helpers import MiB, safe_remove63from systemimage.helpers import MiB, safe_remove
62from systemimage.reactor import Reactor64from systemimage.reactor import Reactor
63from systemimage.settings import Settings65from systemimage.settings import Settings
66from systemimage.testing.controller import USING_PYCURL
64from systemimage.testing.helpers import (67from systemimage.testing.helpers import (
65 copy, data_path, find_dbus_process, make_http_server, setup_index,68 copy, data_path, find_dbus_process, make_http_server, setup_index,
66 setup_keyring_txz, setup_keyrings, sign, terminate_service, touch_build,69 setup_keyring_txz, setup_keyrings, sign, terminate_service, touch_build,
@@ -78,6 +81,27 @@
78 'last_update_date error_reason')81 'last_update_date error_reason')
7982
8083
84def capture_dbus_calls(function):
85 def inner(self, *args, **kws):
86 with ExitStack() as resources:
87 fd, filename = tempfile.mkstemp('.log')
88 os.close(fd)
89 resources.callback(os.remove, filename)
90 old_filename, old_level = self.iface.DebugDBusTo(filename, 'debug')
91 try:
92 result = function(self, *args, **kws)
93 finally:
94 self.iface.DebugDBusTo('', old_level)
95 with open(filename, 'r', encoding='utf-8') as fp:
96 print('\nvvvvv', function.__name__,
97 'dbus calls vvvvv', file=sys.stderr)
98 sys.stderr.write(fp.read())
99 print('^^^^^', function.__name__,
100 'dbus calls ^^^^^', file=sys.stderr)
101 return result
102 return inner
103
104
81def tweak_checksums(checksum):105def tweak_checksums(checksum):
82 index_path = os.path.join(106 index_path = os.path.join(
83 SystemImagePlugin.controller.serverdir,107 SystemImagePlugin.controller.serverdir,
@@ -1717,7 +1741,9 @@
1717 write_bytes(full_path, 750)1741 write_bytes(full_path, 750)
1718 tweak_checksums('')1742 tweak_checksums('')
17191743
1744 @capture_dbus_calls
1720 def test_pause(self):1745 def test_pause(self):
1746 # Set up some extra D-Bus debugging.
1721 self.download_manually()1747 self.download_manually()
1722 touch_build(0, use_config=self.config)1748 touch_build(0, use_config=self.config)
1723 reactor = SignalCapturingReactor('UpdateAvailableStatus')1749 reactor = SignalCapturingReactor('UpdateAvailableStatus')
@@ -1966,8 +1992,6 @@
1966 self.assertEqual(reactor.uas_signals[0], reactor.uas_signals[1])1992 self.assertEqual(reactor.uas_signals[0], reactor.uas_signals[1])
19671993
19681994
1969from systemimage.testing.controller import USING_PYCURL
1970
1971@unittest.skipIf(os.getuid() == 0, 'Test cannot succeed when run as root')1995@unittest.skipIf(os.getuid() == 0, 'Test cannot succeed when run as root')
1972@unittest.skipUnless(USING_PYCURL, 'LP: #1411866')1996@unittest.skipUnless(USING_PYCURL, 'LP: #1411866')
1973class TestDBusCheckForUpdateToUnwritablePartition(_LiveTesting):1997class TestDBusCheckForUpdateToUnwritablePartition(_LiveTesting):
19741998
=== modified file 'systemimage/tests/test_helpers.py'
--- systemimage/tests/test_helpers.py 2015-05-08 21:41:15 +0000
+++ systemimage/tests/test_helpers.py 2015-09-28 21:36:51 +0000
@@ -255,6 +255,52 @@
255 # The last update date will be the date of the 99_build.ini file.255 # The last update date will be the date of the 99_build.ini file.
256 self.assertEqual(last_update_date(), '2022-01-02 03:04:05')256 self.assertEqual(last_update_date(), '2022-01-02 03:04:05')
257257
258 @configuration
259 def test_dangling_symlink(self, config):
260 # LP: #1495688 reports a problem where /userdata/.last_update doesn't
261 # exist, and the files in the config.d directory are dangling
262 # symlinks. In this case, there's really little that can be done to
263 # find a reliable last update date, but at least we don't crash.
264 #
265 # Start by deleting any existing .ini files in config.d.
266 for path in Path(config.config_d).iterdir():
267 if path.suffix == '.ini':
268 path.unlink()
269 with ExitStack() as stack:
270 tmpdir = stack.enter_context(temporary_directory())
271 userdata_path = Path(tmpdir) / '.last_update'
272 stack.enter_context(patch('systemimage.helpers.LAST_UPDATE_FILE',
273 str(userdata_path)))
274 # Do not create the .last_update file.
275 missing_ini = Path(tmpdir) / 'missing.ini'
276 config.ini_files = [missing_ini]
277 # Do not create the missing.ini file, but do create a symlink from
278 # a config.d file to this missing file.
279 default_ini = Path(config.config_d) / '00_default.ini'
280 default_ini.symlink_to(missing_ini)
281 last_update_date()
282 self.assertEqual(last_update_date(), 'Unknown')
283
284 @configuration
285 def test_post_startup_delete(self, config):
286 # Like test_dangling_symlink() except that an existing ini file gets
287 # deleted after system startup, so some of the files that
288 # last_update_date() looks at will throw an exception.
289 #
290 # Start by deleting any existing .ini files in config.d. This time
291 # however we don't update config.ini_files.
292 for path in Path(config.config_d).iterdir():
293 if path.suffix == '.ini':
294 path.unlink()
295 with ExitStack() as stack:
296 tmpdir = stack.enter_context(temporary_directory())
297 userdata_path = Path(tmpdir) / '.last_update'
298 stack.enter_context(patch('systemimage.helpers.LAST_UPDATE_FILE',
299 str(userdata_path)))
300 # Do not create the .last_update file.
301 last_update_date()
302 self.assertEqual(last_update_date(), 'Unknown')
303
258304
259class TestPhasedPercentage(unittest.TestCase):305class TestPhasedPercentage(unittest.TestCase):
260 def setUp(self):306 def setUp(self):
261307
=== modified file 'systemimage/version.txt'
--- systemimage/version.txt 2015-06-17 15:18:22 +0000
+++ systemimage/version.txt 2015-09-28 21:36:51 +0000
@@ -1,1 +1,1 @@
13.0.113.0.2

Subscribers

People subscribed via source and target branches