From 598e0560d64f949369962ebbce2c53207763f5d2 Mon Sep 17 00:00:00 2001
From: Brett Holman <brett.holman@canonical.com>
Date: Fri, 5 Jan 2024 13:10:01 -0700
Subject: [PATCH] fix: fix growpart race (#4618)

Fixes GH-4613
---
 cloudinit/config/cc_growpart.py            | 23 +++++++++++++++++-----
 tests/unittests/config/test_cc_growpart.py | 16 +++++++++++++++
 2 files changed, 34 insertions(+), 5 deletions(-)

diff --git a/cloudinit/config/cc_growpart.py b/cloudinit/config/cc_growpart.py
index f2e847e..f00e2e9 100644
--- a/cloudinit/config/cc_growpart.py
+++ b/cloudinit/config/cc_growpart.py
@@ -19,7 +19,7 @@ from abc import ABC, abstractmethod
 from contextlib import suppress
 from pathlib import Path
 from textwrap import dedent
-from typing import Tuple
+from typing import Optional, Tuple
 
 from cloudinit import subp, temp_utils, util
 from cloudinit.cloud import Cloud
@@ -283,12 +283,16 @@ class ResizeGpart(Resizer):
         return (before, get_size(partdev))
 
 
-def get_size(filename):
-    fd = os.open(filename, os.O_RDONLY)
+def get_size(filename) -> Optional[int]:
+    fd = None
     try:
+        fd = os.open(filename, os.O_RDONLY)
         return os.lseek(fd, 0, os.SEEK_END)
+    except FileNotFoundError:
+        return None
     finally:
-        os.close(fd)
+        if fd:
+            os.close(fd)
 
 
 def device_part_info(devpath):
@@ -571,7 +575,7 @@ def resize_devices(resizer, devices):
             continue
 
         try:
-            (old, new) = resizer.resize(disk, ptnum, blockdev)
+            old, new = resizer.resize(disk, ptnum, blockdev)
             if old == new:
                 info.append(
                     (
@@ -580,6 +584,15 @@ def resize_devices(resizer, devices):
                         "no change necessary (%s, %s)" % (disk, ptnum),
                     )
                 )
+            elif new is None or old is None:
+                info.append(
+                    (
+                        devent,
+                        RESIZE.CHANGED,
+                        "changed (%s, %s) size, new size is unknown"
+                        % (disk, ptnum),
+                    )
+                )
             else:
                 info.append(
                     (
diff --git a/tests/unittests/config/test_cc_growpart.py b/tests/unittests/config/test_cc_growpart.py
index 5b97f7b..85a4759 100644
--- a/tests/unittests/config/test_cc_growpart.py
+++ b/tests/unittests/config/test_cc_growpart.py
@@ -389,6 +389,22 @@ class TestResize(unittest.TestCase):
             os.stat = real_stat
 
 
+class TestGetSize:
+    @pytest.mark.parametrize(
+        "file_exists, expected",
+        (
+            (False, None),
+            (True, 1),
+        ),
+    )
+    def test_get_size_behaves(self, file_exists, expected, tmp_path):
+        """Ensure that get_size() doesn't raise exception"""
+        tmp_file = tmp_path / "tmp.txt"
+        if file_exists:
+            tmp_file.write_bytes(b"0")
+        assert expected == cc_growpart.get_size(tmp_file)
+
+
 class TestEncrypted:
     """Attempt end-to-end scenarios using encrypted devices.
 
-- 
2.27.0

