diff options
Diffstat (limited to 'debian')
-rw-r--r-- | debian/patches/cpick-6ef92c98-IBMCloud-recognize-provisioning-environment-during | 389 | ||||
-rw-r--r-- | debian/patches/series | 1 |
2 files changed, 390 insertions, 0 deletions
diff --git a/debian/patches/cpick-6ef92c98-IBMCloud-recognize-provisioning-environment-during b/debian/patches/cpick-6ef92c98-IBMCloud-recognize-provisioning-environment-during new file mode 100644 index 00000000..a693543c --- /dev/null +++ b/debian/patches/cpick-6ef92c98-IBMCloud-recognize-provisioning-environment-during @@ -0,0 +1,389 @@ +From 6ef92c98c3d2b127b05d6708337efc8a81e00071 Mon Sep 17 00:00:00 2001 +From: Scott Moser <smoser@ubuntu.com> +Date: Thu, 26 Apr 2018 16:24:24 -0500 +Subject: [PATCH] IBMCloud: recognize provisioning environment during debug + boots. + +When images are deployed from template in a production environment +the artifacts of the provisioning stage (provisioningConfiguration.cfg) +that cloud-init referenced are cleaned up. However, when provisioned +in "debug" mode (internal to IBM) the artifacts are left. + +This changes the 'is_ibm_provisioning' implementations in both +ds-identify and in the IBM datasource to identify the provisioning +stage more correctly. The change is to consider provisioning only +if the provisioing file existed and there was no log file or +the log file was older than this boot. + +LP: #1767166 +--- + cloudinit/sources/DataSourceIBMCloud.py | 42 +++++++---- + cloudinit/tests/helpers.py | 13 +++- + .../test_datasource/test_ibmcloud.py | 50 +++++++++++++ + tests/unittests/test_ds_identify.py | 72 ++++++++++++++++--- + tools/ds-identify | 21 +++++- + 5 files changed, 175 insertions(+), 23 deletions(-) + +--- a/cloudinit/sources/DataSourceIBMCloud.py ++++ b/cloudinit/sources/DataSourceIBMCloud.py +@@ -8,17 +8,11 @@ There are 2 different api exposed launch + * template: This is the legacy method of launching instances. + When booting from an image template, the system boots first into + a "provisioning" mode. There, host <-> guest mechanisms are utilized +- to execute code in the guest and provision it. ++ to execute code in the guest and configure it. The configuration ++ includes configuring the system network and possibly installing ++ packages and other software stack. + +- Cloud-init will disable itself when it detects that it is in the +- provisioning mode. It detects this by the presence of +- a file '/root/provisioningConfiguration.cfg'. +- +- When provided with user-data, the "first boot" will contain a +- ConfigDrive-like disk labeled with 'METADATA'. If there is no user-data +- provided, then there is no data-source. +- +- Cloud-init never does any network configuration in this mode. ++ After the provisioning is finished, the system reboots. + + * os_code: Essentially "launch by OS Code" (Operating System Code). + This is a more modern approach. There is no specific "provisioning" boot. +@@ -138,8 +132,30 @@ def _is_xen(): + return os.path.exists("/proc/xen") + + +-def _is_ibm_provisioning(): +- return os.path.exists("/root/provisioningConfiguration.cfg") ++def _is_ibm_provisioning( ++ prov_cfg="/root/provisioningConfiguration.cfg", ++ inst_log="/root/swinstall.log", ++ boot_ref="/proc/1/environ"): ++ """Return boolean indicating if this boot is ibm provisioning boot.""" ++ if os.path.exists(prov_cfg): ++ msg = "config '%s' exists." % prov_cfg ++ result = True ++ if os.path.exists(inst_log): ++ if os.path.exists(boot_ref): ++ result = (os.stat(inst_log).st_mtime > ++ os.stat(boot_ref).st_mtime) ++ msg += (" log '%s' from %s boot." % ++ (inst_log, "current" if result else "previous")) ++ else: ++ msg += (" log '%s' existed, but no reference file '%s'." % ++ (inst_log, boot_ref)) ++ result = False ++ else: ++ msg += " log '%s' did not exist." % inst_log ++ else: ++ result, msg = (False, "config '%s' did not exist." % prov_cfg) ++ LOG.debug("ibm_provisioning=%s: %s", result, msg) ++ return result + + + def get_ibm_platform(): +@@ -189,7 +205,7 @@ def get_ibm_platform(): + else: + return (Platforms.TEMPLATE_LIVE_METADATA, metadata_path) + elif _is_ibm_provisioning(): +- return (Platforms.TEMPLATE_PROVISIONING_NODATA, None) ++ return (Platforms.TEMPLATE_PROVISIONING_NODATA, None) + return not_found + + +--- a/cloudinit/tests/helpers.py ++++ b/cloudinit/tests/helpers.py +@@ -8,6 +8,7 @@ import os + import shutil + import sys + import tempfile ++import time + import unittest + + import mock +@@ -285,7 +286,8 @@ class FilesystemMockingTestCase(Resource + os.path: [('isfile', 1), ('exists', 1), + ('islink', 1), ('isdir', 1), ('lexists', 1)], + os: [('listdir', 1), ('mkdir', 1), +- ('lstat', 1), ('symlink', 2)] ++ ('lstat', 1), ('symlink', 2), ++ ('stat', 1)] + } + + if hasattr(os, 'scandir'): +@@ -354,6 +356,15 @@ def populate_dir(path, files): + return ret + + ++def populate_dir_with_ts(path, data): ++ """data is {'file': ('contents', mtime)}. mtime relative to now.""" ++ populate_dir(path, dict((k, v[0]) for k, v in data.items())) ++ btime = time.time() ++ for fpath, (_contents, mtime) in data.items(): ++ ts = btime + mtime if mtime else btime ++ os.utime(os.path.sep.join((path, fpath)), (ts, ts)) ++ ++ + def dir2dict(startdir, prefix=None): + flist = {} + if prefix is None: +--- a/tests/unittests/test_datasource/test_ibmcloud.py ++++ b/tests/unittests/test_datasource/test_ibmcloud.py +@@ -259,4 +259,54 @@ class TestReadMD(test_helpers.CiTestCase + ret['metadata']) + + ++class TestIsIBMProvisioning(test_helpers.FilesystemMockingTestCase): ++ """Test the _is_ibm_provisioning method.""" ++ inst_log = "/root/swinstall.log" ++ prov_cfg = "/root/provisioningConfiguration.cfg" ++ boot_ref = "/proc/1/environ" ++ with_logs = True ++ ++ def _call_with_root(self, rootd): ++ self.reRoot(rootd) ++ return ibm._is_ibm_provisioning() ++ ++ def test_no_config(self): ++ """No provisioning config means not provisioning.""" ++ self.assertFalse(self._call_with_root(self.tmp_dir())) ++ ++ def test_config_only(self): ++ """A provisioning config without a log means provisioning.""" ++ rootd = self.tmp_dir() ++ test_helpers.populate_dir(rootd, {self.prov_cfg: "key=value"}) ++ self.assertTrue(self._call_with_root(rootd)) ++ ++ def test_config_with_old_log(self): ++ """A config with a log from previous boot is not provisioning.""" ++ rootd = self.tmp_dir() ++ data = {self.prov_cfg: ("key=value\nkey2=val2\n", -10), ++ self.inst_log: ("log data\n", -30), ++ self.boot_ref: ("PWD=/", 0)} ++ test_helpers.populate_dir_with_ts(rootd, data) ++ self.assertFalse(self._call_with_root(rootd=rootd)) ++ self.assertIn("from previous boot", self.logs.getvalue()) ++ ++ def test_config_with_new_log(self): ++ """A config with a log from this boot is provisioning.""" ++ rootd = self.tmp_dir() ++ data = {self.prov_cfg: ("key=value\nkey2=val2\n", -10), ++ self.inst_log: ("log data\n", 30), ++ self.boot_ref: ("PWD=/", 0)} ++ test_helpers.populate_dir_with_ts(rootd, data) ++ self.assertTrue(self._call_with_root(rootd=rootd)) ++ self.assertIn("from current boot", self.logs.getvalue()) ++ ++ def test_config_and_log_no_reference(self): ++ """If the config and log existed, but no reference, assume not.""" ++ rootd = self.tmp_dir() ++ test_helpers.populate_dir( ++ rootd, {self.prov_cfg: "key=value", self.inst_log: "log data\n"}) ++ self.assertFalse(self._call_with_root(rootd=rootd)) ++ self.assertIn("no reference file", self.logs.getvalue()) ++ ++ + # vi: ts=4 expandtab +--- a/tests/unittests/test_ds_identify.py ++++ b/tests/unittests/test_ds_identify.py +@@ -1,5 +1,6 @@ + # This file is part of cloud-init. See LICENSE file for license information. + ++from collections import namedtuple + import copy + import os + from uuid import uuid4 +@@ -7,7 +8,7 @@ from uuid import uuid4 + from cloudinit import safeyaml + from cloudinit import util + from cloudinit.tests.helpers import ( +- CiTestCase, dir2dict, populate_dir) ++ CiTestCase, dir2dict, populate_dir, populate_dir_with_ts) + + from cloudinit.sources import DataSourceIBMCloud as dsibm + +@@ -66,7 +67,6 @@ P_SYS_VENDOR = "sys/class/dmi/id/sys_ven + P_SEED_DIR = "var/lib/cloud/seed" + P_DSID_CFG = "etc/cloud/ds-identify.cfg" + +-IBM_PROVISIONING_CHECK_PATH = "/root/provisioningConfiguration.cfg" + IBM_CONFIG_UUID = "9796-932E" + + MOCK_VIRT_IS_KVM = {'name': 'detect_virt', 'RET': 'kvm', 'ret': 0} +@@ -74,11 +74,17 @@ MOCK_VIRT_IS_VMWARE = {'name': 'detect_v + MOCK_VIRT_IS_XEN = {'name': 'detect_virt', 'RET': 'xen', 'ret': 0} + MOCK_UNAME_IS_PPC64 = {'name': 'uname', 'out': UNAME_PPC64EL, 'ret': 0} + ++shell_true = 0 ++shell_false = 1 + +-class TestDsIdentify(CiTestCase): ++CallReturn = namedtuple('CallReturn', ++ ['rc', 'stdout', 'stderr', 'cfg', 'files']) ++ ++ ++class DsIdentifyBase(CiTestCase): + dsid_path = os.path.realpath('tools/ds-identify') + +- def call(self, rootd=None, mocks=None, args=None, files=None, ++ def call(self, rootd=None, mocks=None, func="main", args=None, files=None, + policy_dmi=DI_DEFAULT_POLICY, + policy_no_dmi=DI_DEFAULT_POLICY_NO_DMI, + ec2_strict_id=DI_EC2_STRICT_ID_DEFAULT): +@@ -135,7 +141,7 @@ class TestDsIdentify(CiTestCase): + mocklines.append(write_mock(d)) + + endlines = [ +- 'main %s' % ' '.join(['"%s"' % s for s in args]) ++ func + ' ' + ' '.join(['"%s"' % s for s in args]) + ] + + with open(wrap, "w") as fp: +@@ -159,7 +165,7 @@ class TestDsIdentify(CiTestCase): + cfg = {"_INVALID_YAML": contents, + "_EXCEPTION": str(e)} + +- return rc, out, err, cfg, dir2dict(rootd) ++ return CallReturn(rc, out, err, cfg, dir2dict(rootd)) + + def _call_via_dict(self, data, rootd=None, **kwargs): + # return output of self.call with a dict input like VALID_CFG[item] +@@ -190,6 +196,8 @@ class TestDsIdentify(CiTestCase): + _print_run_output(rc, out, err, cfg, files) + return rc, out, err, cfg, files + ++ ++class TestDsIdentify(DsIdentifyBase): + def test_wb_print_variables(self): + """_print_info reports an array of discovered variables to stderr.""" + data = VALID_CFG['Azure-dmi-detection'] +@@ -250,7 +258,10 @@ class TestDsIdentify(CiTestCase): + Template provisioning with user-data has METADATA disk, + datasource should return not found.""" + data = copy.deepcopy(VALID_CFG['IBMCloud-metadata']) +- data['files'] = {IBM_PROVISIONING_CHECK_PATH: 'xxx'} ++ # change the 'is_ibm_provisioning' mock to return 1 (false) ++ isprov_m = [m for m in data['mocks'] ++ if m["name"] == "is_ibm_provisioning"][0] ++ isprov_m['ret'] = shell_true + return self._check_via_dict(data, RC_NOT_FOUND) + + def test_ibmcloud_template_userdata(self): +@@ -265,7 +276,8 @@ class TestDsIdentify(CiTestCase): + + no disks attached. Datasource should return not found.""" + data = copy.deepcopy(VALID_CFG['IBMCloud-nodisks']) +- data['files'] = {IBM_PROVISIONING_CHECK_PATH: 'xxx'} ++ data['mocks'].append( ++ {'name': 'is_ibm_provisioning', 'ret': shell_true}) + return self._check_via_dict(data, RC_NOT_FOUND) + + def test_ibmcloud_template_no_userdata(self): +@@ -446,6 +458,47 @@ class TestDsIdentify(CiTestCase): + self._test_ds_found('Hetzner') + + ++class TestIsIBMProvisioning(DsIdentifyBase): ++ """Test the is_ibm_provisioning method in ds-identify.""" ++ ++ inst_log = "/root/swinstall.log" ++ prov_cfg = "/root/provisioningConfiguration.cfg" ++ boot_ref = "/proc/1/environ" ++ funcname = "is_ibm_provisioning" ++ ++ def test_no_config(self): ++ """No provisioning config means not provisioning.""" ++ ret = self.call(files={}, func=self.funcname) ++ self.assertEqual(shell_false, ret.rc) ++ ++ def test_config_only(self): ++ """A provisioning config without a log means provisioning.""" ++ ret = self.call(files={self.prov_cfg: "key=value"}, func=self.funcname) ++ self.assertEqual(shell_true, ret.rc) ++ ++ def test_config_with_old_log(self): ++ """A config with a log from previous boot is not provisioning.""" ++ rootd = self.tmp_dir() ++ data = {self.prov_cfg: ("key=value\nkey2=val2\n", -10), ++ self.inst_log: ("log data\n", -30), ++ self.boot_ref: ("PWD=/", 0)} ++ populate_dir_with_ts(rootd, data) ++ ret = self.call(rootd=rootd, func=self.funcname) ++ self.assertEqual(shell_false, ret.rc) ++ self.assertIn("from previous boot", ret.stderr) ++ ++ def test_config_with_new_log(self): ++ """A config with a log from this boot is provisioning.""" ++ rootd = self.tmp_dir() ++ data = {self.prov_cfg: ("key=value\nkey2=val2\n", -10), ++ self.inst_log: ("log data\n", 30), ++ self.boot_ref: ("PWD=/", 0)} ++ populate_dir_with_ts(rootd, data) ++ ret = self.call(rootd=rootd, func=self.funcname) ++ self.assertEqual(shell_true, ret.rc) ++ self.assertIn("from current boot", ret.stderr) ++ ++ + def blkid_out(disks=None): + """Convert a list of disk dictionaries into blkid content.""" + if disks is None: +@@ -639,6 +692,7 @@ VALID_CFG = { + 'ds': 'IBMCloud', + 'mocks': [ + MOCK_VIRT_IS_XEN, ++ {'name': 'is_ibm_provisioning', 'ret': shell_false}, + {'name': 'blkid', 'ret': 0, + 'out': blkid_out( + [{'DEVNAME': 'xvda1', 'TYPE': 'vfat', 'PARTUUID': uuid4()}, +@@ -652,6 +706,7 @@ VALID_CFG = { + 'ds': 'IBMCloud', + 'mocks': [ + MOCK_VIRT_IS_XEN, ++ {'name': 'is_ibm_provisioning', 'ret': shell_false}, + {'name': 'blkid', 'ret': 0, + 'out': blkid_out( + [{'DEVNAME': 'xvda1', 'TYPE': 'ext3', 'PARTUUID': uuid4(), +@@ -669,6 +724,7 @@ VALID_CFG = { + 'ds': 'IBMCloud', + 'mocks': [ + MOCK_VIRT_IS_XEN, ++ {'name': 'is_ibm_provisioning', 'ret': shell_false}, + {'name': 'blkid', 'ret': 0, + 'out': blkid_out( + [{'DEVNAME': 'xvda1', 'TYPE': 'vfat', 'PARTUUID': uuid4()}, +--- a/tools/ds-identify ++++ b/tools/ds-identify +@@ -125,6 +125,7 @@ DI_ON_NOTFOUND="" + DI_EC2_STRICT_ID_DEFAULT="warn" + + _IS_IBM_CLOUD="" ++_IS_IBM_PROVISIONING="" + + error() { + set -- "ERROR:" "$@"; +@@ -1006,7 +1007,25 @@ dscheck_Hetzner() { + } + + is_ibm_provisioning() { +- [ -f "${PATH_ROOT}/root/provisioningConfiguration.cfg" ] ++ local pcfg="${PATH_ROOT}/root/provisioningConfiguration.cfg" ++ local logf="${PATH_ROOT}/root/swinstall.log" ++ local is_prov=false msg="config '$pcfg' did not exist." ++ if [ -f "$pcfg" ]; then ++ msg="config '$pcfg' exists." ++ is_prov=true ++ if [ -f "$logf" ]; then ++ if [ "$logf" -nt "$PATH_PROC_1_ENVIRON" ]; then ++ msg="$msg log '$logf' from current boot." ++ else ++ is_prov=false ++ msg="$msg log '$logf' from previous boot." ++ fi ++ else ++ msg="$msg log '$logf' did not exist." ++ fi ++ fi ++ debug 2 "ibm_provisioning=$is_prov: $msg" ++ [ "$is_prov" = "true" ] + } + + is_ibm_cloud() { diff --git a/debian/patches/series b/debian/patches/series index 7e909afc..b38df2bf 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -1,3 +1,4 @@ azure-use-walinux-agent.patch ds-identify-behavior-xenial.patch stable-release-no-jsonschema-dep.patch +cpick-6ef92c98-IBMCloud-recognize-provisioning-environment-during |