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