summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorScott Moser <smoser@ubuntu.com>2018-04-30 19:56:55 -0400
committerScott Moser <smoser@ubuntu.com>2018-04-30 19:56:55 -0400
commit44a44ae18e7ee51322110dc66107d2a58f5ff304 (patch)
tree32e6e9109335d57f1fe04cfca2a0fae6ec630d6e
parentd6441b7f42e1d8756d09f47ca4dc3854ad2ad6a9 (diff)
downloadvyos-cloud-init-44a44ae18e7ee51322110dc66107d2a58f5ff304.tar.gz
vyos-cloud-init-44a44ae18e7ee51322110dc66107d2a58f5ff304.zip
cherry pick 6ef92c98
LP: #1767166
-rw-r--r--debian/patches/cpick-6ef92c98-IBMCloud-recognize-provisioning-environment-during389
-rw-r--r--debian/patches/series1
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