From 598e0560d64f949369962ebbce2c53207763f5d2 Mon Sep 17 00:00:00 2001 From: Brett Holman 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