Merge ~rjschwei/cloud-init:btrfsResize into cloud-init:master

Proposed by Robert Schweikert
Status: Merged
Approved by: Scott Moser
Approved revision: cd9dab43dad8121d3526ee84ac64665fddbfccf3
Merged at revision: b28ab78089d362c5c6cab985feee0f5f84c9db44
Proposed branch: ~rjschwei/cloud-init:btrfsResize
Merge into: cloud-init:master
Diff against target: 176 lines (+95/-8)
4 files modified
cloudinit/config/cc_resizefs.py (+11/-1)
cloudinit/tests/test_util.py (+46/-0)
cloudinit/util.py (+17/-6)
tests/unittests/test_handler/test_handler_resizefs.py (+21/-1)
Reviewer Review Type Date Requested Status
Scott Moser Approve
Server Team CI bot continuous-integration Approve
Review via email: mp+334338@code.launchpad.net

Commit message

btrfs: support resizing if root is mounted ro.

Resize of btrfs fails if the mount point for the file system we are trying
to resize, i.e. the root of the filesystem is read only. With this change
we use a known (currently snapper specific) rw location to work around a
flaw that blocks resizing of the ro filesystem.

LP: #1734787

Description of the change

Resize of btrfs fails if "/" is mounted ro. However, it should be allowed to resize the file system. Use a subvolume that is rw as the mountpoint to trick the tools into letting the filesystem be resized as desired

To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:6763d7eb536b9f07e29e4e032589cee693be9d0d
https://jenkins.ubuntu.com/server/job/cloud-init-ci/552/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/552/rebuild

review: Approve (continuous-integration)
Revision history for this message
Scott Moser (smoser) wrote :

A unit test on this would be nice.
I'm fine if you just test _resize_btrfs that calling it returns the right thing
just make a good comment on it with reference to the bug. Heres an example, placed
in cloudinit/config/tests/test_resizefs.py
  http://paste.ubuntu.com/26065846/

Also,
 a.) it seems like we should check if / is in-fact read-only before taking the alternate path.
 b.) knowledge of 'ro' might be useful to pass to *all* the handlers, as if a fs is read-only
     taking a different path seems reasonable.

Lastly,
please fix your commit message
  Subject
  <blank line>
  More info.
  <blank line>
  LP: #1734787

review: Needs Fixing
Revision history for this message
Ryan Harper (raharper) :
~rjschwei/cloud-init:btrfsResize updated
fbc04d2... by Robert Schweikert

- Refactor and implement code to detect rw status of mount
- ADd unit tests

022503d... by Robert Schweikert

- Add docstring to test to reference bug#

Revision history for this message
Robert Schweikert (rjschwei) wrote :

@smoser

Did some refactoring and added some tests. Hopefully this is more agreeable now.

W.r.t. the commit message, sorry I am at a loss about what you are after.

Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:66e933e1fe2f489a894230013d01f3eb9c36ed50
https://jenkins.ubuntu.com/server/job/cloud-init-ci/559/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/559/rebuild

review: Approve (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:cfd1ea17887c8f372ae6edad82cfd069c849383e
https://jenkins.ubuntu.com/server/job/cloud-init-ci/560/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/560/rebuild

review: Approve (continuous-integration)
Revision history for this message
Scott Moser (smoser) wrote :

This does look good, thank you.
Fix your 'Set commit message' button above to be the right form as I said earlier.

Then, I am also curious about /.snapshots as rharper was.

Sorry for the noob questions he and I just dont' have much experience with brtfs.

review: Needs Information
Revision history for this message
Robert Schweikert (rjschwei) wrote :

Commit message set.

As far as .snapshot is concerned, this is "snapper" [1] related. I think until proven otherwise this will do. The other solution is to walk the subvolumes and find one that's mounted rw. For now I think the subvolume walking is overkill.

[1] http://snapper.io/

~rjschwei/cloud-init:btrfsResize updated
e1348e3... by Robert Schweikert

- Clarifying comment

Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:8914a05cdad94a1851fb893f21c9088db47239f5
https://jenkins.ubuntu.com/server/job/cloud-init-ci/571/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/571/rebuild

review: Approve (continuous-integration)
Revision history for this message
Scott Moser (smoser) wrote :

please rebase and address conflicts and one bit about verticle space below.
I think i seems fine after that.

~rjschwei/cloud-init:btrfsResize updated
09f8aeb... by Robert Schweikert

- Rebase to resolve merge conlicts

Revision history for this message
Server Team CI bot (server-team-bot) wrote :

FAILED: Continuous integration, rev:e1348e3c6a7e9fdb609258ed7cf498a49d364f4f
https://jenkins.ubuntu.com/server/job/cloud-init-ci/606/
Executed test runs:
    SUCCESS: Checkout
    FAILED: Unit & Style Tests

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/606/rebuild

review: Needs Fixing (continuous-integration)
~rjschwei/cloud-init:btrfsResize updated
0c12fbf... by Robert Schweikert

- code style
  + more compact notation for continuation lines

Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:0c12fbf916e5cde71bd27e1a250bbe15ef6cbcf2
https://jenkins.ubuntu.com/server/job/cloud-init-ci/607/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/607/rebuild

review: Approve (continuous-integration)
Revision history for this message
Scott Moser (smoser) wrote :

I think I'm ok with this if you fix the little comment fixes inline.

review: Needs Fixing
~rjschwei/cloud-init:btrfsResize updated
cd9dab4... by Robert Schweikert

- Fix typos in comment

Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:cd9dab43dad8121d3526ee84ac64665fddbfccf3
https://jenkins.ubuntu.com/server/job/cloud-init-ci/613/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/613/rebuild

review: Approve (continuous-integration)
Revision history for this message
Scott Moser (smoser) wrote :

thanks Robert.

review: Approve

Update scan failed

At least one of the branches involved have failed to scan. You can manually schedule a rescan if required.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/cloudinit/config/cc_resizefs.py b/cloudinit/config/cc_resizefs.py
index 0d282e6..cec22bb 100644
--- a/cloudinit/config/cc_resizefs.py
+++ b/cloudinit/config/cc_resizefs.py
@@ -59,7 +59,17 @@ __doc__ = get_schema_doc(schema) # Supplement python help()
5959
6060
61def _resize_btrfs(mount_point, devpth):61def _resize_btrfs(mount_point, devpth):
62 return ('btrfs', 'filesystem', 'resize', 'max', mount_point)62 # If "/" is ro resize will fail. However it should be allowed since resize
63 # makes everything bigger and subvolumes that are not ro will benefit.
64 # Use a subvolume that is not ro to trick the resize operation to do the
65 # "right" thing. The use of ".snapshot" is specific to "snapper" a generic
66 # solution would be walk the subvolumes and find a rw mounted subvolume.
67 if (not util.mount_is_read_write(mount_point) and
68 os.path.isdir("%s/.snapshots" % mount_point)):
69 return ('btrfs', 'filesystem', 'resize', 'max',
70 '%s/.snapshots' % mount_point)
71 else:
72 return ('btrfs', 'filesystem', 'resize', 'max', mount_point)
6373
6474
65def _resize_ext(mount_point, devpth):75def _resize_ext(mount_point, devpth):
diff --git a/cloudinit/tests/test_util.py b/cloudinit/tests/test_util.py
66new file mode 10064476new file mode 100644
index 0000000..ba6bf69
--- /dev/null
+++ b/cloudinit/tests/test_util.py
@@ -0,0 +1,46 @@
1# This file is part of cloud-init. See LICENSE file for license information.
2
3"""Tests for cloudinit.util"""
4
5import logging
6
7import cloudinit.util as util
8
9from cloudinit.tests.helpers import CiTestCase, mock
10
11LOG = logging.getLogger(__name__)
12
13MOUNT_INFO = [
14 '68 0 8:3 / / ro,relatime shared:1 - btrfs /dev/sda1 ro,attr2,inode64',
15 '153 68 254:0 / /home rw,relatime shared:101 - xfs /dev/sda2 rw,attr2'
16]
17
18
19class TestUtil(CiTestCase):
20
21 def test_parse_mount_info_no_opts_no_arg(self):
22 result = util.parse_mount_info('/home', MOUNT_INFO, LOG)
23 self.assertEqual(('/dev/sda2', 'xfs', '/home'), result)
24
25 def test_parse_mount_info_no_opts_arg(self):
26 result = util.parse_mount_info('/home', MOUNT_INFO, LOG, False)
27 self.assertEqual(('/dev/sda2', 'xfs', '/home'), result)
28
29 def test_parse_mount_info_with_opts(self):
30 result = util.parse_mount_info('/', MOUNT_INFO, LOG, True)
31 self.assertEqual(
32 ('/dev/sda1', 'btrfs', '/', 'ro,relatime'),
33 result
34 )
35
36 @mock.patch('cloudinit.util.get_mount_info')
37 def test_mount_is_rw(self, m_mount_info):
38 m_mount_info.return_value = ('/dev/sda1', 'btrfs', '/', 'rw,relatime')
39 is_rw = util.mount_is_read_write('/')
40 self.assertEqual(is_rw, True)
41
42 @mock.patch('cloudinit.util.get_mount_info')
43 def test_mount_is_ro(self, m_mount_info):
44 m_mount_info.return_value = ('/dev/sda1', 'btrfs', '/', 'ro,relatime')
45 is_rw = util.mount_is_read_write('/')
46 self.assertEqual(is_rw, False)
diff --git a/cloudinit/util.py b/cloudinit/util.py
index 11e96a7..f3eff13 100644
--- a/cloudinit/util.py
+++ b/cloudinit/util.py
@@ -2051,7 +2051,7 @@ def expand_package_list(version_fmt, pkgs):
2051 return pkglist2051 return pkglist
20522052
20532053
2054def parse_mount_info(path, mountinfo_lines, log=LOG):2054def parse_mount_info(path, mountinfo_lines, log=LOG, get_mnt_opts=False):
2055 """Return the mount information for PATH given the lines from2055 """Return the mount information for PATH given the lines from
2056 /proc/$$/mountinfo."""2056 /proc/$$/mountinfo."""
20572057
@@ -2113,11 +2113,16 @@ def parse_mount_info(path, mountinfo_lines, log=LOG):
21132113
2114 match_mount_point = mount_point2114 match_mount_point = mount_point
2115 match_mount_point_elements = mount_point_elements2115 match_mount_point_elements = mount_point_elements
2116 mount_options = parts[5]
21162117
2117 if devpth and fs_type and match_mount_point:2118 if get_mnt_opts:
2118 return (devpth, fs_type, match_mount_point)2119 if devpth and fs_type and match_mount_point and mount_options:
2120 return (devpth, fs_type, match_mount_point, mount_options)
2119 else:2121 else:
2120 return None2122 if devpth and fs_type and match_mount_point:
2123 return (devpth, fs_type, match_mount_point)
2124
2125 return None
21212126
21222127
2123def parse_mtab(path):2128def parse_mtab(path):
@@ -2187,7 +2192,7 @@ def parse_mount(path):
2187 return None2192 return None
21882193
21892194
2190def get_mount_info(path, log=LOG):2195def get_mount_info(path, log=LOG, get_mnt_opts=False):
2191 # Use /proc/$$/mountinfo to find the device where path is mounted.2196 # Use /proc/$$/mountinfo to find the device where path is mounted.
2192 # This is done because with a btrfs filesystem using os.stat(path)2197 # This is done because with a btrfs filesystem using os.stat(path)
2193 # does not return the ID of the device.2198 # does not return the ID of the device.
@@ -2219,7 +2224,7 @@ def get_mount_info(path, log=LOG):
2219 mountinfo_path = '/proc/%s/mountinfo' % os.getpid()2224 mountinfo_path = '/proc/%s/mountinfo' % os.getpid()
2220 if os.path.exists(mountinfo_path):2225 if os.path.exists(mountinfo_path):
2221 lines = load_file(mountinfo_path).splitlines()2226 lines = load_file(mountinfo_path).splitlines()
2222 return parse_mount_info(path, lines, log)2227 return parse_mount_info(path, lines, log, get_mnt_opts)
2223 elif os.path.exists("/etc/mtab"):2228 elif os.path.exists("/etc/mtab"):
2224 return parse_mtab(path)2229 return parse_mtab(path)
2225 else:2230 else:
@@ -2604,4 +2609,10 @@ def wait_for_files(flist, maxwait, naplen=.5, log_pre=""):
2604 return need2609 return need
26052610
26062611
2612def mount_is_read_write(mount_point):
2613 """Check whether the given mount point is mounted rw"""
2614 result = get_mount_info(mount_point, get_mnt_opts=True)
2615 mount_opts = result[-1].split(',')
2616 return mount_opts[0] == 'rw'
2617
2607# vi: ts=4 expandtab2618# vi: ts=4 expandtab
diff --git a/tests/unittests/test_handler/test_handler_resizefs.py b/tests/unittests/test_handler/test_handler_resizefs.py
index 29d5574..5aa3c49 100644
--- a/tests/unittests/test_handler/test_handler_resizefs.py
+++ b/tests/unittests/test_handler/test_handler_resizefs.py
@@ -1,7 +1,7 @@
1# This file is part of cloud-init. See LICENSE file for license information.1# This file is part of cloud-init. See LICENSE file for license information.
22
3from cloudinit.config.cc_resizefs import (3from cloudinit.config.cc_resizefs import (
4 can_skip_resize, handle, maybe_get_writable_device_path)4 can_skip_resize, handle, maybe_get_writable_device_path, _resize_btrfs)
55
6from collections import namedtuple6from collections import namedtuple
7import logging7import logging
@@ -293,5 +293,25 @@ class TestMaybeGetDevicePathAsWritableBlock(CiTestCase):
293 " per kernel cmdline",293 " per kernel cmdline",
294 self.logs.getvalue())294 self.logs.getvalue())
295295
296 @mock.patch('cloudinit.util.mount_is_read_write')
297 @mock.patch('cloudinit.config.cc_resizefs.os.path.isdir')
298 def test_resize_btrfs_mount_is_ro(self, m_is_dir, m_is_rw):
299 """Do not resize / directly if it is read-only. (LP: #1734787)."""
300 m_is_rw.return_value = False
301 m_is_dir.return_value = True
302 self.assertEqual(
303 ('btrfs', 'filesystem', 'resize', 'max', '//.snapshots'),
304 _resize_btrfs("/", "/dev/sda1"))
305
306 @mock.patch('cloudinit.util.mount_is_read_write')
307 @mock.patch('cloudinit.config.cc_resizefs.os.path.isdir')
308 def test_resize_btrfs_mount_is_rw(self, m_is_dir, m_is_rw):
309 """Do not resize / directly if it is read-only. (LP: #1734787)."""
310 m_is_rw.return_value = True
311 m_is_dir.return_value = True
312 self.assertEqual(
313 ('btrfs', 'filesystem', 'resize', 'max', '/'),
314 _resize_btrfs("/", "/dev/sda1"))
315
296316
297# vi: ts=4 expandtab317# vi: ts=4 expandtab

Subscribers

People subscribed via source and target branches