← Back to team overview

cloud-init-dev team mailing list archive

[Merge] ~chad.smith/cloud-init:ubuntu/artful into cloud-init:ubuntu/artful

 

Chad Smith has proposed merging ~chad.smith/cloud-init:ubuntu/artful into cloud-init:ubuntu/artful.

Commit message:
Cherry pick fixes for IBMCloud upgrades from 17.1 -> 18.2 (artful-proposed)


Addresses the following bugs:
LP: #1766401
LP: #1767166

Requested reviews:
  cloud-init commiters (cloud-init-dev)
Related bugs:
  Bug #1766401 in cloud-init: "full config file wiped after apt-upgrade issued"
  https://bugs.launchpad.net/cloud-init/+bug/1766401
  Bug #1767166 in cloud-init: "IBMCloud datasource does not recognize provisioning in debug mode."
  https://bugs.launchpad.net/cloud-init/+bug/1767166

For more details, see:
https://code.launchpad.net/~chad.smith/cloud-init/+git/cloud-init/+merge/344901
-- 
Your team cloud-init commiters is requested to review the proposed merge of ~chad.smith/cloud-init:ubuntu/artful into cloud-init:ubuntu/artful.
diff --git a/debian/changelog b/debian/changelog
index 982d0c8..2399d6c 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,12 @@
+cloud-init (18.2-4-g05926e48-0ubuntu1~17.10.2) artful-proposed; urgency=medium
+
+  * cherry-pick 11172924: IBMCloud: Disable config-drive and nocloud
+    only if IBMCloud (LP: #1766401)
+  * cherry-pick 6ef92c98: IBMCloud: recognize provisioning environment
+    during debug (LP: #1767166)
+
+ -- Chad Smith <chad.smith@xxxxxxxxxxxxx>  Tue, 01 May 2018 09:56:23 -0600
+
 cloud-init (18.2-4-g05926e48-0ubuntu1~17.10.1) artful-proposed; urgency=medium
 
   * debian/new-upstream-snapshot: Remove script, now maintained elsewhere.
diff --git a/debian/patches/cpick-11172924-IBMCloud-Disable-config-drive-and-nocloud-only-if b/debian/patches/cpick-11172924-IBMCloud-Disable-config-drive-and-nocloud-only-if
new file mode 100644
index 0000000..18cfcff
--- /dev/null
+++ b/debian/patches/cpick-11172924-IBMCloud-Disable-config-drive-and-nocloud-only-if
@@ -0,0 +1,236 @@
+From 11172924a48a47a7231d19d9cefe628dfddda8bf Mon Sep 17 00:00:00 2001
+From: Scott Moser <smoser@xxxxxxxxxx>
+Date: Mon, 30 Apr 2018 13:21:51 -0600
+Subject: [PATCH] IBMCloud: Disable config-drive and nocloud only if IBMCloud
+ is enabled.
+
+Ubuntu images on IBMCloud for 16.04 have some seed data in
+/var/lib/cloud/data/seed/nocloud-net.  In order to have systems with
+IBMCloud enabled, we modified ds-identify detection to skip that seed
+if the system was on IBMCloud.  That change did not consider the
+fact that IBMCloud might not be in the datasource list.
+
+There was similar logic in the ConfigDrive datasource in ds-identify
+and the datasource itself.
+
+Config drive is now updated to only check and avoid IBMCloud if IBMCloud
+is enabled.  The check in ds-identify for nocloud was dropped.  If a
+user provides a nocloud seed on IBMCloud, then that can be used.
+
+This means that systems running Xenial will continue to get their
+old datasources.
+
+LP: #1766401
+---
+ cloudinit/sources/DataSourceConfigDrive.py | 11 +++--
+ tests/unittests/test_ds_identify.py        | 77 +++++++++++++++++++++++++++---
+ tools/ds-identify                          | 17 +++++--
+ 3 files changed, 91 insertions(+), 14 deletions(-)
+
+Index: cloud-init/cloudinit/sources/DataSourceConfigDrive.py
+===================================================================
+--- cloud-init.orig/cloudinit/sources/DataSourceConfigDrive.py
++++ cloud-init/cloudinit/sources/DataSourceConfigDrive.py
+@@ -69,7 +69,8 @@ class DataSourceConfigDrive(openstack.So
+                 util.logexc(LOG, "Failed reading config drive from %s", sdir)
+ 
+         if not found:
+-            for dev in find_candidate_devs():
++            dslist = self.sys_cfg.get('datasource_list')
++            for dev in find_candidate_devs(dslist=dslist):
+                 try:
+                     # Set mtype if freebsd and turn off sync
+                     if dev.startswith("/dev/cd"):
+@@ -211,7 +212,7 @@ def write_injected_files(files):
+                 util.logexc(LOG, "Failed writing file: %s", filename)
+ 
+ 
+-def find_candidate_devs(probe_optical=True):
++def find_candidate_devs(probe_optical=True, dslist=None):
+     """Return a list of devices that may contain the config drive.
+ 
+     The returned list is sorted by search order where the first item has
+@@ -227,6 +228,9 @@ def find_candidate_devs(probe_optical=Tr
+         * either vfat or iso9660 formated
+         * labeled with 'config-2' or 'CONFIG-2'
+     """
++    if dslist is None:
++        dslist = []
++
+     # query optical drive to get it in blkid cache for 2.6 kernels
+     if probe_optical:
+         for device in OPTICAL_DEVICES:
+@@ -257,7 +261,8 @@ def find_candidate_devs(probe_optical=Tr
+     devices = [d for d in candidates
+                if d in by_label or not util.is_partition(d)]
+ 
+-    if devices:
++    LOG.debug("devices=%s dslist=%s", devices, dslist)
++    if devices and "IBMCloud" in dslist:
+         # IBMCloud uses config-2 label, but limited to a single UUID.
+         ibm_platform, ibm_path = get_ibm_platform()
+         if ibm_path in devices:
+Index: cloud-init/tests/unittests/test_ds_identify.py
+===================================================================
+--- cloud-init.orig/tests/unittests/test_ds_identify.py
++++ cloud-init/tests/unittests/test_ds_identify.py
+@@ -178,17 +178,18 @@ class TestDsIdentify(CiTestCase):
+             data, RC_FOUND, dslist=[data.get('ds'), DS_NONE])
+ 
+     def _check_via_dict(self, data, rc, dslist=None, **kwargs):
+-        found_rc, out, err, cfg, files = self._call_via_dict(data, **kwargs)
++        ret = self._call_via_dict(data, **kwargs)
+         good = False
+         try:
+-            self.assertEqual(rc, found_rc)
++            self.assertEqual(rc, ret.rc)
+             if dslist is not None:
+-                self.assertEqual(dslist, cfg['datasource_list'])
++                self.assertEqual(dslist, ret.cfg['datasource_list'])
+             good = True
+         finally:
+             if not good:
+-                _print_run_output(rc, out, err, cfg, files)
+-        return rc, out, err, cfg, files
++                _print_run_output(ret.rc, ret.stdout, ret.stderr, ret.cfg,
++                                  ret.files)
++        return ret
+ 
+     def test_wb_print_variables(self):
+         """_print_info reports an array of discovered variables to stderr."""
+@@ -237,13 +238,40 @@ class TestDsIdentify(CiTestCase):
+     def test_config_drive(self):
+         """ConfigDrive datasource has a disk with LABEL=config-2."""
+         self._test_ds_found('ConfigDrive')
+-        return
+ 
+     def test_config_drive_upper(self):
+         """ConfigDrive datasource has a disk with LABEL=CONFIG-2."""
+         self._test_ds_found('ConfigDriveUpper')
+         return
+ 
++    def test_config_drive_seed(self):
++        """Config Drive seed directory."""
++        self._test_ds_found('ConfigDrive-seed')
++
++    def test_config_drive_interacts_with_ibmcloud_config_disk(self):
++        """Verify ConfigDrive interaction with IBMCloud.
++
++        If ConfigDrive is enabled and not IBMCloud, then ConfigDrive
++        should claim the ibmcloud 'config-2' disk.
++        If IBMCloud is enabled, then ConfigDrive should skip."""
++        data = copy.deepcopy(VALID_CFG['IBMCloud-config-2'])
++        files = data.get('files', {})
++        if not files:
++            data['files'] = files
++        cfgpath = 'etc/cloud/cloud.cfg.d/99_networklayer_common.cfg'
++
++        # with list including IBMCloud, config drive should be not found.
++        files[cfgpath] = 'datasource_list: [ ConfigDrive, IBMCloud ]\n'
++        ret = self._check_via_dict(data, shell_true)
++        self.assertEqual(
++            ret.cfg.get('datasource_list'), ['IBMCloud', 'None'])
++
++        # But if IBMCloud is not enabled, config drive should claim this.
++        files[cfgpath] = 'datasource_list: [ ConfigDrive, NoCloud ]\n'
++        ret = self._check_via_dict(data, shell_true)
++        self.assertEqual(
++            ret.cfg.get('datasource_list'), ['ConfigDrive', 'None'])
++
+     def test_ibmcloud_template_userdata_in_provisioning(self):
+         """Template provisioned with user-data during provisioning stage.
+ 
+@@ -295,6 +323,37 @@ class TestDsIdentify(CiTestCase):
+         self._check_via_dict(
+             data, rc=RC_FOUND, dslist=['ConfigDrive', DS_NONE])
+ 
++    def test_ibmcloud_with_nocloud_seed(self):
++        """NoCloud seed should be preferred over IBMCloud.
++
++        A nocloud seed should be preferred over IBMCloud even if enabled.
++        Ubuntu 16.04 images have <vlc>/seed/nocloud-net. LP: #1766401."""
++        data = copy.deepcopy(VALID_CFG['IBMCloud-config-2'])
++        files = data.get('files', {})
++        if not files:
++            data['files'] = files
++        files.update(VALID_CFG['NoCloud-seed']['files'])
++        ret = self._check_via_dict(data, shell_true)
++        self.assertEqual(
++            ['NoCloud', 'IBMCloud', 'None'],
++            ret.cfg.get('datasource_list'))
++
++    def test_ibmcloud_with_configdrive_seed(self):
++        """ConfigDrive seed should be preferred over IBMCloud.
++
++        A ConfigDrive seed should be preferred over IBMCloud even if enabled.
++        Ubuntu 16.04 images have a fstab entry that mounts the
++        METADATA disk into <vlc>/seed/config_drive. LP: ##1766401."""
++        data = copy.deepcopy(VALID_CFG['IBMCloud-config-2'])
++        files = data.get('files', {})
++        if not files:
++            data['files'] = files
++        files.update(VALID_CFG['ConfigDrive-seed']['files'])
++        ret = self._check_via_dict(data, shell_true)
++        self.assertEqual(
++            ['ConfigDrive', 'IBMCloud', 'None'],
++            ret.cfg.get('datasource_list'))
++
+     def test_policy_disabled(self):
+         """A Builtin policy of 'disabled' should return not found.
+ 
+@@ -631,6 +690,12 @@ VALID_CFG = {
+              },
+         ],
+     },
++    'ConfigDrive-seed': {
++        'ds': 'ConfigDrive',
++        'files': {
++            os.path.join(P_SEED_DIR, 'config_drive', 'openstack',
++                         'latest', 'meta_data.json'): 'md\n'},
++    },
+     'Hetzner': {
+         'ds': 'Hetzner',
+         'files': {P_SYS_VENDOR: 'Hetzner\n'},
+Index: cloud-init/tools/ds-identify
+===================================================================
+--- cloud-init.orig/tools/ds-identify
++++ cloud-init/tools/ds-identify
+@@ -600,7 +600,6 @@ dscheck_NoCloud() {
+         *\ ds=nocloud*) return ${DS_FOUND};;
+     esac
+ 
+-    is_ibm_cloud && return ${DS_NOT_FOUND}
+     for d in nocloud nocloud-net; do
+         check_seed_dir "$d" meta-data user-data && return ${DS_FOUND}
+         check_writable_seed_dir "$d" meta-data user-data && return ${DS_FOUND}
+@@ -611,11 +610,12 @@ dscheck_NoCloud() {
+     return ${DS_NOT_FOUND}
+ }
+ 
++is_ds_enabled() {
++    local name="$1" pad=" ${DI_DSLIST} "
++    [ "${pad#* $name }" != "${pad}" ]
++}
++
+ check_configdrive_v2() {
+-    is_ibm_cloud && return ${DS_NOT_FOUND}
+-    if has_fs_with_label CONFIG-2 config-2; then
+-        return ${DS_FOUND}
+-    fi
+     # look in /config-drive <vlc>/seed/config_drive for a directory
+     # openstack/YYYY-MM-DD format with a file meta_data.json
+     local d=""
+@@ -630,6 +630,13 @@ check_configdrive_v2() {
+         debug 1 "config drive seeded directory had only 'latest'"
+         return ${DS_FOUND}
+     fi
++
++    is_ds_enabled "IBMCloud"
++    debug 1 "is_ds_enabled returned $?: $DI_DSLIST"
++    is_ds_enabled "IBMCloud" && is_ibm_cloud && return ${DS_NOT_FOUND}
++    if has_fs_with_label CONFIG-2 config-2; then
++        return ${DS_FOUND}
++    fi
+     return ${DS_NOT_FOUND}
+ }
+ 
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 0000000..962fe06
--- /dev/null
+++ b/debian/patches/cpick-6ef92c98-IBMCloud-recognize-provisioning-environment-during
@@ -0,0 +1,399 @@
+From 6ef92c98c3d2b127b05d6708337efc8a81e00071 Mon Sep 17 00:00:00 2001
+From: Scott Moser <smoser@xxxxxxxxxx>
+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 ++++-
+ tests/unittests/test_datasource/test_ibmcloud.py | 50 ++++++++++++++++
+ tests/unittests/test_ds_identify.py              | 72 +++++++++++++++++++++---
+ tools/ds-identify                                | 21 ++++++-
+ 5 files changed, 175 insertions(+), 23 deletions(-)
+
+Index: cloud-init/cloudinit/sources/DataSourceIBMCloud.py
+===================================================================
+--- cloud-init.orig/cloudinit/sources/DataSourceIBMCloud.py
++++ cloud-init/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
+ 
+ 
+Index: cloud-init/cloudinit/tests/helpers.py
+===================================================================
+--- cloud-init.orig/cloudinit/tests/helpers.py
++++ cloud-init/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:
+Index: cloud-init/tests/unittests/test_datasource/test_ibmcloud.py
+===================================================================
+--- cloud-init.orig/tests/unittests/test_datasource/test_ibmcloud.py
++++ cloud-init/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
+Index: cloud-init/tests/unittests/test_ds_identify.py
+===================================================================
+--- cloud-init.orig/tests/unittests/test_ds_identify.py
++++ cloud-init/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]
+@@ -191,6 +197,8 @@ class TestDsIdentify(CiTestCase):
+                                   ret.files)
+         return ret
+ 
++
++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']
+@@ -278,7 +286,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):
+@@ -293,7 +304,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):
+@@ -505,6 +517,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:
+@@ -704,6 +757,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()},
+@@ -717,6 +771,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(),
+@@ -734,6 +789,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()},
+Index: cloud-init/tools/ds-identify
+===================================================================
+--- cloud-init.orig/tools/ds-identify
++++ cloud-init/tools/ds-identify
+@@ -125,6 +125,7 @@ DI_ON_NOTFOUND=""
+ DI_EC2_STRICT_ID_DEFAULT="true"
+ 
+ _IS_IBM_CLOUD=""
++_IS_IBM_PROVISIONING=""
+ 
+ error() {
+     set -- "ERROR:" "$@";
+@@ -1013,7 +1014,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
new file mode 100644
index 0000000..2f90183
--- /dev/null
+++ b/debian/patches/series
@@ -0,0 +1,2 @@
+cpick-11172924-IBMCloud-Disable-config-drive-and-nocloud-only-if
+cpick-6ef92c98-IBMCloud-recognize-provisioning-environment-during