← Back to team overview

cloud-init-dev team mailing list archive

[Merge] ~raharper/cloud-init:fix/cc_resizefs_on_zfs_root into cloud-init:master

 

Ryan Harper has proposed merging ~raharper/cloud-init:fix/cc_resizefs_on_zfs_root into cloud-init:master.

Commit message:
cc_resizefs, util: handle no /dev/zfs
    
The zfs/zpool commands will hang for 10 seconds if /dev/zfs is not
present (bug 1760173).  This is a common occurance for containers
using zfs as rootfs.  Additionally handle missing zpool command or
other errors that may occur while executing the zpool command.


Requested reviews:
  Server Team CI bot (server-team-bot): continuous-integration
  cloud-init commiters (cloud-init-dev)

For more details, see:
https://code.launchpad.net/~raharper/cloud-init/+git/cloud-init/+merge/342467

Note, this slipped through ci since the lxd storage backend isn't currently using zfs.
-- 
Your team cloud-init commiters is requested to review the proposed merge of ~raharper/cloud-init:fix/cc_resizefs_on_zfs_root into cloud-init:master.
diff --git a/cloudinit/config/cc_resizefs.py b/cloudinit/config/cc_resizefs.py
index c8e1752..013e69b 100644
--- a/cloudinit/config/cc_resizefs.py
+++ b/cloudinit/config/cc_resizefs.py
@@ -251,6 +251,8 @@ def handle(name, cfg, _cloud, log, args):
     if fs_type == 'zfs':
         zpool = devpth.split('/')[0]
         devpth = util.get_device_info_from_zpool(zpool)
+        if not devpth:
+            return  # could not find device from zpool
         resize_what = zpool
 
     info = "dev=%s mnt_point=%s path=%s" % (devpth, mount_point, resize_what)
diff --git a/cloudinit/util.py b/cloudinit/util.py
index 0ab2c48..acdc0d8 100644
--- a/cloudinit/util.py
+++ b/cloudinit/util.py
@@ -2249,7 +2249,15 @@ def get_mount_info_freebsd(path):
 
 
 def get_device_info_from_zpool(zpool):
-    (zpoolstatus, err) = subp(['zpool', 'status', zpool])
+    # zpool has 10 second timeout waiting for /dev/zfs LP: #1760173
+    if not os.path.exists('/dev/zfs'):
+        LOG.debug('Cannot get zpool info, no /dev/zfs')
+        return None
+    try:
+        (zpoolstatus, err) = subp(['zpool', 'status', zpool])
+    except ProcessExecutionError as err:
+        LOG.warning("Unable to get zpool status of %s: %s", zpool, err)
+        return None
     if len(err):
         return None
     r = r'.*(ONLINE).*'
diff --git a/tests/unittests/test_util.py b/tests/unittests/test_util.py
index 8685b8e..9fadfb3 100644
--- a/tests/unittests/test_util.py
+++ b/tests/unittests/test_util.py
@@ -366,8 +366,11 @@ class TestMountinfoParsing(helpers.ResourceUsingTestCase):
         expected = ('none', 'tmpfs', '/run/lock')
         self.assertEqual(expected, util.parse_mount_info('/run/lock', lines))
 
+    @mock.patch('cloudinit.util.os')
     @mock.patch('cloudinit.util.subp')
-    def test_get_device_info_from_zpool(self, zpool_output):
+    def test_get_device_info_from_zpool(self, zpool_output, m_os):
+        # mock /dev/zfs exists
+        m_os.path.exists.return_value = True
         # mock subp command from util.get_mount_info_fs_on_zpool
         zpool_output.return_value = (
             self.readResource('zpool_status_simple.txt'), ''
@@ -377,8 +380,29 @@ class TestMountinfoParsing(helpers.ResourceUsingTestCase):
         self.assertEqual('gpt/system', ret)
         self.assertIsNotNone(ret)
 
+    @mock.patch('cloudinit.util.os')
+    def test_get_device_info_from_zpool_no_dev_zfs(self, m_os):
+        # mock /dev/zfs missing
+        m_os.path.exists.return_value = False
+        # save function return values and do asserts
+        ret = util.get_device_info_from_zpool('vmzroot')
+        self.assertIsNone(ret)
+
+    @mock.patch('cloudinit.util.os')
+    @mock.patch('cloudinit.util.subp')
+    def test_get_device_info_from_zpool_handles_no_zpool(self, m_sub, m_os):
+        """Handle case where there is no zpool command"""
+        # mock /dev/zfs exists
+        m_os.path.exists.return_value = True
+        m_sub.side_effect = util.ProcessExecutionError("No zpool cmd")
+        ret = util.get_device_info_from_zpool('vmzroot')
+        self.assertIsNone(ret)
+
+    @mock.patch('cloudinit.util.os')
     @mock.patch('cloudinit.util.subp')
-    def test_get_device_info_from_zpool_on_error(self, zpool_output):
+    def test_get_device_info_from_zpool_on_error(self, zpool_output, m_os):
+        # mock /dev/zfs exists
+        m_os.path.exists.return_value = True
         # mock subp command from util.get_mount_info_fs_on_zpool
         zpool_output.return_value = (
             self.readResource('zpool_status_simple.txt'), 'error'