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
diff --git a/cloudinit/net/cmdline.py b/cloudinit/net/cmdline.py
index 61e2369..38b27a5 100755
--- a/cloudinit/net/cmdline.py
+++ b/cloudinit/net/cmdline.py
@@ -9,41 +9,12 @@ import base64
9import glob9import glob
10import gzip10import gzip
11import io11import io
12import shlex
13import sys
14
15import six
1612
17from . import get_devicelist13from . import get_devicelist
18from . import read_sys_net_safe14from . import read_sys_net_safe
1915
20from cloudinit import util16from cloudinit import util
2117
22PY26 = sys.version_info[0:2] == (2, 6)
23
24
25def _shlex_split(blob):
26 if PY26 and isinstance(blob, six.text_type):
27 # Older versions don't support unicode input
28 blob = blob.encode("utf8")
29 return shlex.split(blob)
30
31
32def _load_shell_content(content, add_empty=False, empty_val=None):
33 """Given shell like syntax (key=value\nkey2=value2\n) in content
34 return the data in dictionary form. If 'add_empty' is True
35 then add entries in to the returned dictionary for 'VAR='
36 variables. Set their value to empty_val."""
37 data = {}
38 for line in _shlex_split(content):
39 key, value = line.split("=", 1)
40 if not value:
41 value = empty_val
42 if add_empty or value:
43 data[key] = value
44
45 return data
46
4718
48def _klibc_to_config_entry(content, mac_addrs=None):19def _klibc_to_config_entry(content, mac_addrs=None):
49 """Convert a klibc written shell content file to a 'config' entry20 """Convert a klibc written shell content file to a 'config' entry
@@ -63,7 +34,7 @@ def _klibc_to_config_entry(content, mac_addrs=None):
63 if mac_addrs is None:34 if mac_addrs is None:
64 mac_addrs = {}35 mac_addrs = {}
6536
66 data = _load_shell_content(content)37 data = util.load_shell_content(content)
67 try:38 try:
68 name = data['DEVICE'] if 'DEVICE' in data else data['DEVICE6']39 name = data['DEVICE'] if 'DEVICE' in data else data['DEVICE6']
69 except KeyError:40 except KeyError:
diff --git a/cloudinit/util.py b/cloudinit/util.py
index 27a9833..67ff7ba 100644
--- a/cloudinit/util.py
+++ b/cloudinit/util.py
@@ -24,6 +24,7 @@ import platform
24import pwd24import pwd
25import random25import random
26import re26import re
27import shlex
27import shutil28import shutil
28import socket29import socket
29import stat30import stat
@@ -75,6 +76,7 @@ CONTAINER_TESTS = (['systemd-detect-virt', '--quiet', '--container'],
75PROC_CMDLINE = None76PROC_CMDLINE = None
7677
77_LSB_RELEASE = {}78_LSB_RELEASE = {}
79PY26 = sys.version_info[0:2] == (2, 6)
7880
7981
80def get_architecture(target=None):82def get_architecture(target=None):
@@ -2424,6 +2426,18 @@ def system_is_snappy():
2424 # channel.ini is configparser loadable.2426 # channel.ini is configparser loadable.
2425 # snappy will move to using /etc/system-image/config.d/*.ini2427 # snappy will move to using /etc/system-image/config.d/*.ini
2426 # this is certainly not a perfect test, but good enough for now.2428 # this is certainly not a perfect test, but good enough for now.
2429 orpath = "/etc/os-release"
2430 try:
2431 orinfo = load_shell_content(load_file(orpath, quiet=True))
2432 if orinfo.get('ID', '').lower() == "ubuntu-core":
2433 return True
2434 except ValueError as e:
2435 LOG.warning("Unexpected error loading '%s': %s", orpath, e)
2436
2437 cmdline = get_cmdline()
2438 if 'snap_core=' in cmdline:
2439 return True
2440
2427 content = load_file("/etc/system-image/channel.ini", quiet=True)2441 content = load_file("/etc/system-image/channel.ini", quiet=True)
2428 if 'ubuntu-core' in content.lower():2442 if 'ubuntu-core' in content.lower():
2429 return True2443 return True
@@ -2470,4 +2484,27 @@ def rootdev_from_cmdline(cmdline):
2470 return "/dev/" + found2484 return "/dev/" + found
24712485
24722486
2487def load_shell_content(content, add_empty=False, empty_val=None):
2488 """Given shell like syntax (key=value\nkey2=value2\n) in content
2489 return the data in dictionary form. If 'add_empty' is True
2490 then add entries in to the returned dictionary for 'VAR='
2491 variables. Set their value to empty_val."""
2492
2493 def _shlex_split(blob):
2494 if PY26 and isinstance(blob, six.text_type):
2495 # Older versions don't support unicode input
2496 blob = blob.encode("utf8")
2497 return shlex.split(blob)
2498
2499 data = {}
2500 for line in _shlex_split(content):
2501 key, value = line.split("=", 1)
2502 if not value:
2503 value = empty_val
2504 if add_empty or value:
2505 data[key] = value
2506
2507 return data
2508
2509
2473# vi: ts=4 expandtab2510# vi: ts=4 expandtab
diff --git a/tests/unittests/helpers.py b/tests/unittests/helpers.py
index a711404..d24f817 100644
--- a/tests/unittests/helpers.py
+++ b/tests/unittests/helpers.py
@@ -106,7 +106,7 @@ class CiTestCase(TestCase):
106 return os.path.normpath(os.path.abspath(os.path.join(dir, path)))106 return os.path.normpath(os.path.abspath(os.path.join(dir, path)))
107107
108108
109class ResourceUsingTestCase(TestCase):109class ResourceUsingTestCase(CiTestCase):
110 def setUp(self):110 def setUp(self):
111 super(ResourceUsingTestCase, self).setUp()111 super(ResourceUsingTestCase, self).setUp()
112 self.resource_path = None112 self.resource_path = None
@@ -229,8 +229,7 @@ class FilesystemMockingTestCase(ResourceUsingTestCase):
229229
230 def reRoot(self, root=None):230 def reRoot(self, root=None):
231 if root is None:231 if root is None:
232 root = tempfile.mkdtemp()232 root = self.tmp_dir()
233 self.addCleanup(shutil.rmtree, root)
234 self.patchUtils(root)233 self.patchUtils(root)
235 self.patchOS(root)234 self.patchOS(root)
236 return root235 return root
@@ -256,7 +255,7 @@ def populate_dir(path, files):
256 os.makedirs(path)255 os.makedirs(path)
257 ret = []256 ret = []
258 for (name, content) in files.items():257 for (name, content) in files.items():
259 p = os.path.join(path, name)258 p = os.path.sep.join([path, name])
260 util.ensure_dir(os.path.dirname(p))259 util.ensure_dir(os.path.dirname(p))
261 with open(p, "wb") as fp:260 with open(p, "wb") as fp:
262 if isinstance(content, six.binary_type):261 if isinstance(content, six.binary_type):
diff --git a/tests/unittests/test_util.py b/tests/unittests/test_util.py
index 189caca..490760d 100644
--- a/tests/unittests/test_util.py
+++ b/tests/unittests/test_util.py
@@ -712,4 +712,73 @@ class TestProcessExecutionError(helpers.TestCase):
712 )).format(description=self.empty_description,712 )).format(description=self.empty_description,
713 empty_attr=self.empty_attr))713 empty_attr=self.empty_attr))
714714
715
716class TestSystemIsSnappy(helpers.FilesystemMockingTestCase):
717 def test_id_in_os_release_quoted(self):
718 """os-release containing ID="ubuntu-core" is snappy."""
719 orcontent = '\n'.join(['ID="ubuntu-core"', ''])
720 root_d = self.tmp_dir()
721 helpers.populate_dir(root_d, {'etc/os-release': orcontent})
722 self.reRoot(root_d)
723 self.assertTrue(util.system_is_snappy())
724
725 def test_id_in_os_release(self):
726 """os-release containing ID=ubuntu-core is snappy."""
727 orcontent = '\n'.join(['ID=ubuntu-core', ''])
728 root_d = self.tmp_dir()
729 helpers.populate_dir(root_d, {'etc/os-release': orcontent})
730 self.reRoot(root_d)
731 self.assertTrue(util.system_is_snappy())
732
733 @mock.patch('cloudinit.util.get_cmdline')
734 def test_bad_content_in_os_release_no_effect(self, m_cmdline):
735 """malformed os-release should not raise exception."""
736 m_cmdline.return_value = 'root=/dev/sda'
737 orcontent = '\n'.join(['IDubuntu-core', ''])
738 root_d = self.tmp_dir()
739 helpers.populate_dir(root_d, {'etc/os-release': orcontent})
740 self.reRoot()
741 self.assertFalse(util.system_is_snappy())
742
743 @mock.patch('cloudinit.util.get_cmdline')
744 def test_snap_core_in_cmdline_is_snappy(self, m_cmdline):
745 """The string snap_core= in kernel cmdline indicates snappy."""
746 cmdline = (
747 "BOOT_IMAGE=(loop)/kernel.img root=LABEL=writable "
748 "snap_core=core_x1.snap snap_kernel=pc-kernel_x1.snap ro "
749 "net.ifnames=0 init=/lib/systemd/systemd console=tty1 "
750 "console=ttyS0 panic=-1")
751 m_cmdline.return_value = cmdline
752 self.assertTrue(util.system_is_snappy())
753 self.assertTrue(m_cmdline.call_count > 0)
754
755 @mock.patch('cloudinit.util.get_cmdline')
756 def test_nothing_found_is_not_snappy(self, m_cmdline):
757 """If no positive identification, then not snappy."""
758 m_cmdline.return_value = 'root=/dev/sda'
759 self.reRoot()
760 self.assertFalse(util.system_is_snappy())
761 self.assertTrue(m_cmdline.call_count > 0)
762
763 @mock.patch('cloudinit.util.get_cmdline')
764 def test_channel_ini_with_snappy_is_snappy(self, m_cmdline):
765 """A Channel.ini file with 'ubuntu-core' indicates snappy."""
766 m_cmdline.return_value = 'root=/dev/sda'
767 root_d = self.tmp_dir()
768 content = '\n'.join(["[Foo]", "source = 'ubuntu-core'", ""])
769 helpers.populate_dir(
770 root_d, {'etc/system-image/channel.ini': content})
771 self.reRoot(root_d)
772 self.assertTrue(util.system_is_snappy())
773
774 @mock.patch('cloudinit.util.get_cmdline')
775 def test_system_image_config_dir_is_snappy(self, m_cmdline):
776 """Existence of /etc/system-image/config.d indicates snappy."""
777 m_cmdline.return_value = 'root=/dev/sda'
778 root_d = self.tmp_dir()
779 helpers.populate_dir(
780 root_d, {'etc/system-image/config.d/my.file': "_unused"})
781 self.reRoot(root_d)
782 self.assertTrue(util.system_is_snappy())
783
715# vi: ts=4 expandtab784# vi: ts=4 expandtab

Subscribers

People subscribed via source and target branches