← Back to team overview

cloud-init-dev team mailing list archive

[Merge] lp:~blair/cloud-init/cloud-init into lp:cloud-init

 

Scott Moser has proposed merging lp:~blair/cloud-init/cloud-init into lp:cloud-init.

Commit message:
Support resizing btrfs filesystems.

The existing code has two issues with btrfs:

1) The command to resize a btrfs filesystem uses a path to the mount
   point, not the underlying device:

   $ btrfs filesystem resize max /dev/vda1
   ERROR: unable to resize '/dev/vda1' - Inappropriate ioctl for device
   Resize '/dev/vda1' of 'max'
   $ btrfs filesystem resize max /
   Resize '/' of 'max'

2) The code that is given a path and finds the ID of the device where
   the path is mounted doesn't work for btrfs:

   Use /proc/$$/mountinfo to find the device where path is mounted.
   This is done because with a btrfs filesystem using os.stat(path)
   does not return the ID of the device.

   Here, / has a device of 18 (decimal).

     $ stat /
       File: '/'
       Size: 234 Blocks: 0 IO Block: 4096 directory
     Device: 12h/18d Inode: 256 Links: 1
     Access: (0755/drwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)
     Access: 2013-01-13 07:31:04.358011255 +0000
     Modify: 2013-01-13 18:48:25.930011255 +0000
     Change: 2013-01-13 18:48:25.930011255 +0000
      Birth: -

   Find where / is mounted:

     $ mount | grep ' / '
     /dev/vda1 on / type btrfs (rw,subvol=@,compress=lzo)

   And the device ID for /dev/vda1 is not 18:

     $ ls -l /dev/vda1
     brw-rw---- 1 root disk 253, 1 Jan 13 08:29 /dev/vda1

   So use /proc/$$/mountinfo to find the device underlying the input
   path.

Requested reviews:
  cloud init development team (cloud-init-dev)

For more details, see:
https://code.launchpad.net/~blair/cloud-init/cloud-init/+merge/143347
-- 
https://code.launchpad.net/~blair/cloud-init/cloud-init/+merge/143347
Your team cloud init development team is requested to review the proposed merge of lp:~blair/cloud-init/cloud-init into lp:cloud-init.
=== modified file 'cloudinit/config/cc_resizefs.py'
--- cloudinit/config/cc_resizefs.py	2012-11-20 06:05:36 +0000
+++ cloudinit/config/cc_resizefs.py	2013-01-15 16:37:28 +0000
@@ -27,9 +27,21 @@
 
 frequency = PER_ALWAYS
 
+def _resize_btrfs(mount_point, devpth, tmp_devpth, log):
+    return ('btrfs', 'filesystem', 'resize', 'max', mount_point)
+
+def _resize_ext(mount_point, devpth, tmp_devpth, log):
+    nodeify_path(tmp_devpth, resize_what, log)
+    return ('resize2fs', tmp_devpth)
+
+def _resize_xfs(mount_point, devpth, tmp_devpth, log):
+    nodeify_path(tmp_devpth, resize_what, log)
+    return ('xfs_growfs', tmp_devpth)
+
 RESIZE_FS_PREFIXES_CMDS = [
-    ('ext', 'resize2fs'),
-    ('xfs', 'xfs_growfs'),
+    ('btrfs', _resize_btrfs),
+    ('ext', _resize_ext),
+    ('xfs', _resize_xfs),
 ]
 
 NOBLOCK = "noblock"
@@ -50,19 +62,85 @@
         raise
 
 
-def get_fs_type(st_dev, path, log):
-    try:
-        dev_entries = util.find_devs_with(tag='TYPE', oformat='value',
-                                         no_cache=True, path=path)
-        if not dev_entries:
-            return None
-        return dev_entries[0].strip()
-    except util.ProcessExecutionError:
-        util.logexc(log, ("Failed to get filesystem type"
-                          " of maj=%s, min=%s for path %s"),
-                    os.major(st_dev), os.minor(st_dev), path)
-        raise
-
+def get_fs_devpth_and_type(path, log):
+    # Use /proc/$$/mountinfo to find the device where path is mounted.
+    # This is done because with a btrfs filesystem using os.stat(path)
+    # does not return the ID of the device.
+    #
+    # Here, / has a device of 18 (decimal).
+    #
+    # $ stat /
+    #   File: '/'
+    #   Size: 234               Blocks: 0          IO Block: 4096   directory
+    # Device: 12h/18d   Inode: 256         Links: 1
+    # Access: (0755/drwxr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root)
+    # Access: 2013-01-13 07:31:04.358011255 +0000
+    # Modify: 2013-01-13 18:48:25.930011255 +0000
+    # Change: 2013-01-13 18:48:25.930011255 +0000
+    #  Birth: -
+    #
+    # Find where / is mounted:
+    #
+    # $ mount | grep ' / '
+    # /dev/vda1 on / type btrfs (rw,subvol=@,compress=lzo)
+    #
+    # And the device ID for /dev/vda1 is not 18:
+    #
+    # $ ls -l /dev/vda1
+    # brw-rw---- 1 root disk 253, 1 Jan 13 08:29 /dev/vda1
+    #
+    # So use /proc/$$/mountinfo to find the device underlying the
+    # input path.
+    fs_type = None
+    devpth = None
+    match_mount_elements = None
+    path_elements = [e for e in path.split('/') if e]
+    mountinfo_path = '/proc/%s/mountinfo' % os.getpid()
+    with open(mountinfo_path) as f:
+        for line in f.readlines():
+            # Look for - and use the field two to the right.
+            parts = line.split()
+
+            mount = parts[4]
+            mount_elements = [e for e in mount.split('/') if e]
+
+            # Ignore mounts deeper than the path in question.
+            if len(mount_elements) > len(path_elements):
+                continue
+
+            # Ignore mounts where the common path is not the same.
+            l = min(len(mount_elements), len(path_elements))
+            if mount_elements[0:l] != path_elements[0:l]:
+                continue
+
+            # Ignore mount points higher than an already seen mount
+            # point.
+            if (match_mount_elements is not None and
+                len(match_mount_elements) > len(mount_elements)):
+                continue
+
+            # Find the '-' which terminates a list of optional columns
+            # to find the path to the device and the mount point.  See
+            # man 5 proc for the format of this file.
+            try:
+                i = parts.index('-')
+            except ValueError:
+                log.debug("Did not find column named '-' in %s",
+                          mountinfo_path)
+                return None
+
+            # Get the path to the device.
+            try:
+                fs_type = parts[i+1]
+                devpth = parts[i+2]
+            except IndexError, e:
+                log.debug("Too few columns in %s after '-' column",
+                          mountinfo_path)
+                return None
+
+            match_mount_elements = mount_elements
+
+    return (devpth, fs_type)
 
 def handle(name, cfg, _cloud, log, args):
     if len(args) != 0:
@@ -82,7 +160,7 @@
     resize_what = "/"
     with util.ExtendedTemporaryFile(prefix="cloudinit.resizefs.",
                                     dir=resize_root_d, delete=True) as tfh:
-        devpth = tfh.name
+        tmp_devpth = tfh.name
 
         # Delete the file so that mknod will work
         # but don't change the file handle to know that its
@@ -91,12 +169,15 @@
         # auto deletion
         tfh.unlink_now()
 
-        st_dev = nodeify_path(devpth, resize_what, log)
-        fs_type = get_fs_type(st_dev, devpth, log)
-        if not fs_type:
+        result = get_fs_devpth_and_type(resize_what, log)
+        if not result:
             log.warn("Could not determine filesystem type of %s", resize_what)
             return
 
+        (devpth, fs_type) = result
+
+        st_dev = os.stat(devpth).st_rdev
+
         resizer = None
         fstype_lc = fs_type.lower()
         for (pfix, root_cmd) in RESIZE_FS_PREFIXES_CMDS:
@@ -109,8 +190,9 @@
                      fs_type, resize_what)
             return
 
-        log.debug("Resizing %s (%s) using %s", resize_what, fs_type, resizer)
-        resize_cmd = [resizer, devpth]
+        resize_cmd = resizer(resize_what, devpth, tmp_devpth, log)
+        log.debug("Resizing %s (%s) using %s", resize_what, fs_type,
+                  ' '.join(resize_cmd))
 
         if resize_root == NOBLOCK:
             # Fork to a child that will run


Follow ups