6732b3
From b226448134b5182ba685702e7b7a486db772d956 Mon Sep 17 00:00:00 2001
6732b3
From: Emanuele Giuseppe Esposito <eesposit@redhat.com>
6732b3
Date: Fri, 4 Mar 2022 11:21:16 +0100
6732b3
Subject: [PATCH 1/2] - Detect a Python version change and clear the cache
6732b3
 (#857)
6732b3
6732b3
RH-Author: Emanuele Giuseppe Esposito <eesposit@redhat.com>
6732b3
RH-MergeRequest: 54: - Detect a Python version change and clear the cache (#857)
6732b3
RH-Commit: [1/2] c562cd802eabae9dc14079de0b26d471d2229ca8
6732b3
RH-Bugzilla: 1935826
6732b3
RH-Acked-by: Eduardo Otubo <otubo@redhat.com>
6732b3
RH-Acked-by: Vitaly Kuznetsov <vkuznets@redhat.com>
6732b3
RH-Acked-by: Mohamed Gamal Morsy <mmorsy@redhat.com>
6732b3
6732b3
commit 78e89b03ecb29e7df3181b1219a0b5f44b9d7532
6732b3
Author: Robert Schweikert <rjschwei@suse.com>
6732b3
Date:   Thu Jul 1 12:35:40 2021 -0400
6732b3
6732b3
    - Detect a Python version change and clear the cache (#857)
6732b3
6732b3
    summary: Clear cache when a Python version change is detected
6732b3
6732b3
    When a distribution gets updated it is possible that the Python version
6732b3
    changes. Python makes no guarantee that pickle is consistent across
6732b3
    versions as such we need to purge the cache and start over.
6732b3
6732b3
    Co-authored-by: James Falcon <therealfalcon@gmail.com>
6732b3
Conflicts:
6732b3
   tests/integration_tests/util.py: test is not present downstream
6732b3
6732b3
Signed-off-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
6732b3
---
6732b3
 cloudinit/cmd/main.py                         |  30 ++++++++++
6732b3
 cloudinit/cmd/tests/test_main.py              |   2 +
6732b3
 .../assets/test_version_change.pkl            | Bin 0 -> 21 bytes
6732b3
 .../modules/test_ssh_auth_key_fingerprints.py |   2 +-
6732b3
 .../modules/test_version_change.py            |  56 ++++++++++++++++++
6732b3
 5 files changed, 89 insertions(+), 1 deletion(-)
6732b3
 create mode 100644 tests/integration_tests/assets/test_version_change.pkl
6732b3
 create mode 100644 tests/integration_tests/modules/test_version_change.py
6732b3
6732b3
diff --git a/cloudinit/cmd/main.py b/cloudinit/cmd/main.py
6732b3
index baf1381f..21213a4a 100644
6732b3
--- a/cloudinit/cmd/main.py
6732b3
+++ b/cloudinit/cmd/main.py
6732b3
@@ -210,6 +210,35 @@ def attempt_cmdline_url(path, network=True, cmdline=None):
6732b3
             (cmdline_name, url, path))
6732b3
 
6732b3
 
6732b3
+def purge_cache_on_python_version_change(init):
6732b3
+    """Purge the cache if python version changed on us.
6732b3
+
6732b3
+    There could be changes not represented in our cache (obj.pkl) after we
6732b3
+    upgrade to a new version of python, so at that point clear the cache
6732b3
+    """
6732b3
+    current_python_version = '%d.%d' % (
6732b3
+        sys.version_info.major, sys.version_info.minor
6732b3
+    )
6732b3
+    python_version_path = os.path.join(
6732b3
+        init.paths.get_cpath('data'), 'python-version'
6732b3
+    )
6732b3
+    if os.path.exists(python_version_path):
6732b3
+        cached_python_version = open(python_version_path).read()
6732b3
+        # The Python version has changed out from under us, anything that was
6732b3
+        # pickled previously is likely useless due to API changes.
6732b3
+        if cached_python_version != current_python_version:
6732b3
+            LOG.debug('Python version change detected. Purging cache')
6732b3
+            init.purge_cache(True)
6732b3
+            util.write_file(python_version_path, current_python_version)
6732b3
+    else:
6732b3
+        if os.path.exists(init.paths.get_ipath_cur('obj_pkl')):
6732b3
+            LOG.info(
6732b3
+                'Writing python-version file. '
6732b3
+                'Cache compatibility status is currently unknown.'
6732b3
+            )
6732b3
+        util.write_file(python_version_path, current_python_version)
6732b3
+
6732b3
+
6732b3
 def main_init(name, args):
6732b3
     deps = [sources.DEP_FILESYSTEM, sources.DEP_NETWORK]
6732b3
     if args.local:
6732b3
@@ -276,6 +305,7 @@ def main_init(name, args):
6732b3
         util.logexc(LOG, "Failed to initialize, likely bad things to come!")
6732b3
     # Stage 4
6732b3
     path_helper = init.paths
6732b3
+    purge_cache_on_python_version_change(init)
6732b3
     mode = sources.DSMODE_LOCAL if args.local else sources.DSMODE_NETWORK
6732b3
 
6732b3
     if mode == sources.DSMODE_NETWORK:
6732b3
diff --git a/cloudinit/cmd/tests/test_main.py b/cloudinit/cmd/tests/test_main.py
6732b3
index 78b27441..1f5975b0 100644
6732b3
--- a/cloudinit/cmd/tests/test_main.py
6732b3
+++ b/cloudinit/cmd/tests/test_main.py
6732b3
@@ -17,6 +17,8 @@ myargs = namedtuple('MyArgs', 'debug files force local reporter subcommand')
6732b3
 
6732b3
 
6732b3
 class TestMain(FilesystemMockingTestCase):
6732b3
+    with_logs = True
6732b3
+    allowed_subp = False
6732b3
 
6732b3
     def setUp(self):
6732b3
         super(TestMain, self).setUp()
6732b3
diff --git a/tests/integration_tests/modules/test_ssh_auth_key_fingerprints.py b/tests/integration_tests/modules/test_ssh_auth_key_fingerprints.py
6732b3
index b9b0d85e..e1946cb1 100644
6732b3
--- a/tests/integration_tests/modules/test_ssh_auth_key_fingerprints.py
6732b3
+++ b/tests/integration_tests/modules/test_ssh_auth_key_fingerprints.py
6732b3
@@ -18,7 +18,7 @@ USER_DATA_SSH_AUTHKEY_DISABLE = """\
6732b3
 no_ssh_fingerprints: true
6732b3
 """
6732b3
 
6732b3
-USER_DATA_SSH_AUTHKEY_ENABLE="""\
6732b3
+USER_DATA_SSH_AUTHKEY_ENABLE = """\
6732b3
 #cloud-config
6732b3
 ssh_genkeytypes:
6732b3
   - ecdsa
6732b3
diff --git a/tests/integration_tests/modules/test_version_change.py b/tests/integration_tests/modules/test_version_change.py
6732b3
new file mode 100644
6732b3
index 00000000..4e9ab63f
6732b3
--- /dev/null
6732b3
+++ b/tests/integration_tests/modules/test_version_change.py
6732b3
@@ -0,0 +1,56 @@
6732b3
+from pathlib import Path
6732b3
+
6732b3
+from tests.integration_tests.instances import IntegrationInstance
6732b3
+from tests.integration_tests.util import ASSETS_DIR
6732b3
+
6732b3
+
6732b3
+PICKLE_PATH = Path('/var/lib/cloud/instance/obj.pkl')
6732b3
+TEST_PICKLE = ASSETS_DIR / 'test_version_change.pkl'
6732b3
+
6732b3
+
6732b3
+def _assert_no_pickle_problems(log):
6732b3
+    assert 'Failed loading pickled blob' not in log
6732b3
+    assert 'Traceback' not in log
6732b3
+    assert 'WARN' not in log
6732b3
+
6732b3
+
6732b3
+def test_reboot_without_version_change(client: IntegrationInstance):
6732b3
+    log = client.read_from_file('/var/log/cloud-init.log')
6732b3
+    assert 'Python version change detected' not in log
6732b3
+    assert 'Cache compatibility status is currently unknown.' not in log
6732b3
+    _assert_no_pickle_problems(log)
6732b3
+
6732b3
+    client.restart()
6732b3
+    log = client.read_from_file('/var/log/cloud-init.log')
6732b3
+    assert 'Python version change detected' not in log
6732b3
+    assert 'Could not determine Python version used to write cache' not in log
6732b3
+    _assert_no_pickle_problems(log)
6732b3
+
6732b3
+    # Now ensure that loading a bad pickle gives us problems
6732b3
+    client.push_file(TEST_PICKLE, PICKLE_PATH)
6732b3
+    client.restart()
6732b3
+    log = client.read_from_file('/var/log/cloud-init.log')
6732b3
+    assert 'Failed loading pickled blob from {}'.format(PICKLE_PATH) in log
6732b3
+
6732b3
+
6732b3
+def test_cache_purged_on_version_change(client: IntegrationInstance):
6732b3
+    # Start by pushing the invalid pickle so we'll hit an error if the
6732b3
+    # cache didn't actually get purged
6732b3
+    client.push_file(TEST_PICKLE, PICKLE_PATH)
6732b3
+    client.execute("echo '1.0' > /var/lib/cloud/data/python-version")
6732b3
+    client.restart()
6732b3
+    log = client.read_from_file('/var/log/cloud-init.log')
6732b3
+    assert 'Python version change detected. Purging cache' in log
6732b3
+    _assert_no_pickle_problems(log)
6732b3
+
6732b3
+
6732b3
+def test_log_message_on_missing_version_file(client: IntegrationInstance):
6732b3
+    # Start by pushing a pickle so we can see the log message
6732b3
+    client.push_file(TEST_PICKLE, PICKLE_PATH)
6732b3
+    client.execute("rm /var/lib/cloud/data/python-version")
6732b3
+    client.restart()
6732b3
+    log = client.read_from_file('/var/log/cloud-init.log')
6732b3
+    assert (
6732b3
+        'Writing python-version file. '
6732b3
+        'Cache compatibility status is currently unknown.'
6732b3
+    ) in log
6732b3
-- 
6732b3
2.31.1
6732b3