Merge ~smoser/cloud-init:bug/1689944-improve-system-is-snappy into cloud-init:master

Proposed by Scott Moser
Status: Merged
Merged at revision: 4bcc947301bedc5ebf430cfaf6e4597bfb174aa7
Proposed branch: ~smoser/cloud-init:bug/1689944-improve-system-is-snappy
Merge into: cloud-init:master
Diff against target: 232 lines (+110/-34)
4 files modified
cloudinit/net/cmdline.py (+1/-30)
cloudinit/util.py (+37/-0)
tests/unittests/helpers.py (+3/-4)
tests/unittests/test_util.py (+69/-0)
Reviewer Review Type Date Requested Status
Ryan Harper Approve
Server Team CI bot continuous-integration Approve
Review via email: mp+324126@code.launchpad.net

Commit message

Improve detection of snappy to include os-release and kernel cmdline.

Recent core snap images (edge channel revision 1886) do not contain the
previously known files used to detect that a system is ubuntu core.

The changes here are to look in 2 additional locations to determine
if a system is snappy.

LP: #1689944

To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Ryan Harper (raharper) wrote :

Looks good.

review: Approve

There was an error fetching revisions from git servers. Please try again in a few minutes. If the problem persists, contact Launchpad support.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/net/cmdline.py b/cloudinit/net/cmdline.py
2index 61e2369..38b27a5 100755
3--- a/cloudinit/net/cmdline.py
4+++ b/cloudinit/net/cmdline.py
5@@ -9,41 +9,12 @@ import base64
6 import glob
7 import gzip
8 import io
9-import shlex
10-import sys
11-
12-import six
13
14 from . import get_devicelist
15 from . import read_sys_net_safe
16
17 from cloudinit import util
18
19-PY26 = sys.version_info[0:2] == (2, 6)
20-
21-
22-def _shlex_split(blob):
23- if PY26 and isinstance(blob, six.text_type):
24- # Older versions don't support unicode input
25- blob = blob.encode("utf8")
26- return shlex.split(blob)
27-
28-
29-def _load_shell_content(content, add_empty=False, empty_val=None):
30- """Given shell like syntax (key=value\nkey2=value2\n) in content
31- return the data in dictionary form. If 'add_empty' is True
32- then add entries in to the returned dictionary for 'VAR='
33- variables. Set their value to empty_val."""
34- data = {}
35- for line in _shlex_split(content):
36- key, value = line.split("=", 1)
37- if not value:
38- value = empty_val
39- if add_empty or value:
40- data[key] = value
41-
42- return data
43-
44
45 def _klibc_to_config_entry(content, mac_addrs=None):
46 """Convert a klibc written shell content file to a 'config' entry
47@@ -63,7 +34,7 @@ def _klibc_to_config_entry(content, mac_addrs=None):
48 if mac_addrs is None:
49 mac_addrs = {}
50
51- data = _load_shell_content(content)
52+ data = util.load_shell_content(content)
53 try:
54 name = data['DEVICE'] if 'DEVICE' in data else data['DEVICE6']
55 except KeyError:
56diff --git a/cloudinit/util.py b/cloudinit/util.py
57index 27a9833..67ff7ba 100644
58--- a/cloudinit/util.py
59+++ b/cloudinit/util.py
60@@ -24,6 +24,7 @@ import platform
61 import pwd
62 import random
63 import re
64+import shlex
65 import shutil
66 import socket
67 import stat
68@@ -75,6 +76,7 @@ CONTAINER_TESTS = (['systemd-detect-virt', '--quiet', '--container'],
69 PROC_CMDLINE = None
70
71 _LSB_RELEASE = {}
72+PY26 = sys.version_info[0:2] == (2, 6)
73
74
75 def get_architecture(target=None):
76@@ -2424,6 +2426,18 @@ def system_is_snappy():
77 # channel.ini is configparser loadable.
78 # snappy will move to using /etc/system-image/config.d/*.ini
79 # this is certainly not a perfect test, but good enough for now.
80+ orpath = "/etc/os-release"
81+ try:
82+ orinfo = load_shell_content(load_file(orpath, quiet=True))
83+ if orinfo.get('ID', '').lower() == "ubuntu-core":
84+ return True
85+ except ValueError as e:
86+ LOG.warning("Unexpected error loading '%s': %s", orpath, e)
87+
88+ cmdline = get_cmdline()
89+ if 'snap_core=' in cmdline:
90+ return True
91+
92 content = load_file("/etc/system-image/channel.ini", quiet=True)
93 if 'ubuntu-core' in content.lower():
94 return True
95@@ -2470,4 +2484,27 @@ def rootdev_from_cmdline(cmdline):
96 return "/dev/" + found
97
98
99+def load_shell_content(content, add_empty=False, empty_val=None):
100+ """Given shell like syntax (key=value\nkey2=value2\n) in content
101+ return the data in dictionary form. If 'add_empty' is True
102+ then add entries in to the returned dictionary for 'VAR='
103+ variables. Set their value to empty_val."""
104+
105+ def _shlex_split(blob):
106+ if PY26 and isinstance(blob, six.text_type):
107+ # Older versions don't support unicode input
108+ blob = blob.encode("utf8")
109+ return shlex.split(blob)
110+
111+ data = {}
112+ for line in _shlex_split(content):
113+ key, value = line.split("=", 1)
114+ if not value:
115+ value = empty_val
116+ if add_empty or value:
117+ data[key] = value
118+
119+ return data
120+
121+
122 # vi: ts=4 expandtab
123diff --git a/tests/unittests/helpers.py b/tests/unittests/helpers.py
124index a711404..d24f817 100644
125--- a/tests/unittests/helpers.py
126+++ b/tests/unittests/helpers.py
127@@ -106,7 +106,7 @@ class CiTestCase(TestCase):
128 return os.path.normpath(os.path.abspath(os.path.join(dir, path)))
129
130
131-class ResourceUsingTestCase(TestCase):
132+class ResourceUsingTestCase(CiTestCase):
133 def setUp(self):
134 super(ResourceUsingTestCase, self).setUp()
135 self.resource_path = None
136@@ -229,8 +229,7 @@ class FilesystemMockingTestCase(ResourceUsingTestCase):
137
138 def reRoot(self, root=None):
139 if root is None:
140- root = tempfile.mkdtemp()
141- self.addCleanup(shutil.rmtree, root)
142+ root = self.tmp_dir()
143 self.patchUtils(root)
144 self.patchOS(root)
145 return root
146@@ -256,7 +255,7 @@ def populate_dir(path, files):
147 os.makedirs(path)
148 ret = []
149 for (name, content) in files.items():
150- p = os.path.join(path, name)
151+ p = os.path.sep.join([path, name])
152 util.ensure_dir(os.path.dirname(p))
153 with open(p, "wb") as fp:
154 if isinstance(content, six.binary_type):
155diff --git a/tests/unittests/test_util.py b/tests/unittests/test_util.py
156index 189caca..490760d 100644
157--- a/tests/unittests/test_util.py
158+++ b/tests/unittests/test_util.py
159@@ -712,4 +712,73 @@ class TestProcessExecutionError(helpers.TestCase):
160 )).format(description=self.empty_description,
161 empty_attr=self.empty_attr))
162
163+
164+class TestSystemIsSnappy(helpers.FilesystemMockingTestCase):
165+ def test_id_in_os_release_quoted(self):
166+ """os-release containing ID="ubuntu-core" is snappy."""
167+ orcontent = '\n'.join(['ID="ubuntu-core"', ''])
168+ root_d = self.tmp_dir()
169+ helpers.populate_dir(root_d, {'etc/os-release': orcontent})
170+ self.reRoot(root_d)
171+ self.assertTrue(util.system_is_snappy())
172+
173+ def test_id_in_os_release(self):
174+ """os-release containing ID=ubuntu-core is snappy."""
175+ orcontent = '\n'.join(['ID=ubuntu-core', ''])
176+ root_d = self.tmp_dir()
177+ helpers.populate_dir(root_d, {'etc/os-release': orcontent})
178+ self.reRoot(root_d)
179+ self.assertTrue(util.system_is_snappy())
180+
181+ @mock.patch('cloudinit.util.get_cmdline')
182+ def test_bad_content_in_os_release_no_effect(self, m_cmdline):
183+ """malformed os-release should not raise exception."""
184+ m_cmdline.return_value = 'root=/dev/sda'
185+ orcontent = '\n'.join(['IDubuntu-core', ''])
186+ root_d = self.tmp_dir()
187+ helpers.populate_dir(root_d, {'etc/os-release': orcontent})
188+ self.reRoot()
189+ self.assertFalse(util.system_is_snappy())
190+
191+ @mock.patch('cloudinit.util.get_cmdline')
192+ def test_snap_core_in_cmdline_is_snappy(self, m_cmdline):
193+ """The string snap_core= in kernel cmdline indicates snappy."""
194+ cmdline = (
195+ "BOOT_IMAGE=(loop)/kernel.img root=LABEL=writable "
196+ "snap_core=core_x1.snap snap_kernel=pc-kernel_x1.snap ro "
197+ "net.ifnames=0 init=/lib/systemd/systemd console=tty1 "
198+ "console=ttyS0 panic=-1")
199+ m_cmdline.return_value = cmdline
200+ self.assertTrue(util.system_is_snappy())
201+ self.assertTrue(m_cmdline.call_count > 0)
202+
203+ @mock.patch('cloudinit.util.get_cmdline')
204+ def test_nothing_found_is_not_snappy(self, m_cmdline):
205+ """If no positive identification, then not snappy."""
206+ m_cmdline.return_value = 'root=/dev/sda'
207+ self.reRoot()
208+ self.assertFalse(util.system_is_snappy())
209+ self.assertTrue(m_cmdline.call_count > 0)
210+
211+ @mock.patch('cloudinit.util.get_cmdline')
212+ def test_channel_ini_with_snappy_is_snappy(self, m_cmdline):
213+ """A Channel.ini file with 'ubuntu-core' indicates snappy."""
214+ m_cmdline.return_value = 'root=/dev/sda'
215+ root_d = self.tmp_dir()
216+ content = '\n'.join(["[Foo]", "source = 'ubuntu-core'", ""])
217+ helpers.populate_dir(
218+ root_d, {'etc/system-image/channel.ini': content})
219+ self.reRoot(root_d)
220+ self.assertTrue(util.system_is_snappy())
221+
222+ @mock.patch('cloudinit.util.get_cmdline')
223+ def test_system_image_config_dir_is_snappy(self, m_cmdline):
224+ """Existence of /etc/system-image/config.d indicates snappy."""
225+ m_cmdline.return_value = 'root=/dev/sda'
226+ root_d = self.tmp_dir()
227+ helpers.populate_dir(
228+ root_d, {'etc/system-image/config.d/my.file': "_unused"})
229+ self.reRoot(root_d)
230+ self.assertTrue(util.system_is_snappy())
231+
232 # vi: ts=4 expandtab

Subscribers

People subscribed via source and target branches