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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/config/cc_resizefs.py b/cloudinit/config/cc_resizefs.py
2index 0d282e6..cec22bb 100644
3--- a/cloudinit/config/cc_resizefs.py
4+++ b/cloudinit/config/cc_resizefs.py
5@@ -59,7 +59,17 @@ __doc__ = get_schema_doc(schema) # Supplement python help()
6
7
8 def _resize_btrfs(mount_point, devpth):
9- return ('btrfs', 'filesystem', 'resize', 'max', mount_point)
10+ # If "/" is ro resize will fail. However it should be allowed since resize
11+ # makes everything bigger and subvolumes that are not ro will benefit.
12+ # Use a subvolume that is not ro to trick the resize operation to do the
13+ # "right" thing. The use of ".snapshot" is specific to "snapper" a generic
14+ # solution would be walk the subvolumes and find a rw mounted subvolume.
15+ if (not util.mount_is_read_write(mount_point) and
16+ os.path.isdir("%s/.snapshots" % mount_point)):
17+ return ('btrfs', 'filesystem', 'resize', 'max',
18+ '%s/.snapshots' % mount_point)
19+ else:
20+ return ('btrfs', 'filesystem', 'resize', 'max', mount_point)
21
22
23 def _resize_ext(mount_point, devpth):
24diff --git a/cloudinit/tests/test_util.py b/cloudinit/tests/test_util.py
25new file mode 100644
26index 0000000..ba6bf69
27--- /dev/null
28+++ b/cloudinit/tests/test_util.py
29@@ -0,0 +1,46 @@
30+# This file is part of cloud-init. See LICENSE file for license information.
31+
32+"""Tests for cloudinit.util"""
33+
34+import logging
35+
36+import cloudinit.util as util
37+
38+from cloudinit.tests.helpers import CiTestCase, mock
39+
40+LOG = logging.getLogger(__name__)
41+
42+MOUNT_INFO = [
43+ '68 0 8:3 / / ro,relatime shared:1 - btrfs /dev/sda1 ro,attr2,inode64',
44+ '153 68 254:0 / /home rw,relatime shared:101 - xfs /dev/sda2 rw,attr2'
45+]
46+
47+
48+class TestUtil(CiTestCase):
49+
50+ def test_parse_mount_info_no_opts_no_arg(self):
51+ result = util.parse_mount_info('/home', MOUNT_INFO, LOG)
52+ self.assertEqual(('/dev/sda2', 'xfs', '/home'), result)
53+
54+ def test_parse_mount_info_no_opts_arg(self):
55+ result = util.parse_mount_info('/home', MOUNT_INFO, LOG, False)
56+ self.assertEqual(('/dev/sda2', 'xfs', '/home'), result)
57+
58+ def test_parse_mount_info_with_opts(self):
59+ result = util.parse_mount_info('/', MOUNT_INFO, LOG, True)
60+ self.assertEqual(
61+ ('/dev/sda1', 'btrfs', '/', 'ro,relatime'),
62+ result
63+ )
64+
65+ @mock.patch('cloudinit.util.get_mount_info')
66+ def test_mount_is_rw(self, m_mount_info):
67+ m_mount_info.return_value = ('/dev/sda1', 'btrfs', '/', 'rw,relatime')
68+ is_rw = util.mount_is_read_write('/')
69+ self.assertEqual(is_rw, True)
70+
71+ @mock.patch('cloudinit.util.get_mount_info')
72+ def test_mount_is_ro(self, m_mount_info):
73+ m_mount_info.return_value = ('/dev/sda1', 'btrfs', '/', 'ro,relatime')
74+ is_rw = util.mount_is_read_write('/')
75+ self.assertEqual(is_rw, False)
76diff --git a/cloudinit/util.py b/cloudinit/util.py
77index 11e96a7..f3eff13 100644
78--- a/cloudinit/util.py
79+++ b/cloudinit/util.py
80@@ -2051,7 +2051,7 @@ def expand_package_list(version_fmt, pkgs):
81 return pkglist
82
83
84-def parse_mount_info(path, mountinfo_lines, log=LOG):
85+def parse_mount_info(path, mountinfo_lines, log=LOG, get_mnt_opts=False):
86 """Return the mount information for PATH given the lines from
87 /proc/$$/mountinfo."""
88
89@@ -2113,11 +2113,16 @@ def parse_mount_info(path, mountinfo_lines, log=LOG):
90
91 match_mount_point = mount_point
92 match_mount_point_elements = mount_point_elements
93+ mount_options = parts[5]
94
95- if devpth and fs_type and match_mount_point:
96- return (devpth, fs_type, match_mount_point)
97+ if get_mnt_opts:
98+ if devpth and fs_type and match_mount_point and mount_options:
99+ return (devpth, fs_type, match_mount_point, mount_options)
100 else:
101- return None
102+ if devpth and fs_type and match_mount_point:
103+ return (devpth, fs_type, match_mount_point)
104+
105+ return None
106
107
108 def parse_mtab(path):
109@@ -2187,7 +2192,7 @@ def parse_mount(path):
110 return None
111
112
113-def get_mount_info(path, log=LOG):
114+def get_mount_info(path, log=LOG, get_mnt_opts=False):
115 # Use /proc/$$/mountinfo to find the device where path is mounted.
116 # This is done because with a btrfs filesystem using os.stat(path)
117 # does not return the ID of the device.
118@@ -2219,7 +2224,7 @@ def get_mount_info(path, log=LOG):
119 mountinfo_path = '/proc/%s/mountinfo' % os.getpid()
120 if os.path.exists(mountinfo_path):
121 lines = load_file(mountinfo_path).splitlines()
122- return parse_mount_info(path, lines, log)
123+ return parse_mount_info(path, lines, log, get_mnt_opts)
124 elif os.path.exists("/etc/mtab"):
125 return parse_mtab(path)
126 else:
127@@ -2604,4 +2609,10 @@ def wait_for_files(flist, maxwait, naplen=.5, log_pre=""):
128 return need
129
130
131+def mount_is_read_write(mount_point):
132+ """Check whether the given mount point is mounted rw"""
133+ result = get_mount_info(mount_point, get_mnt_opts=True)
134+ mount_opts = result[-1].split(',')
135+ return mount_opts[0] == 'rw'
136+
137 # vi: ts=4 expandtab
138diff --git a/tests/unittests/test_handler/test_handler_resizefs.py b/tests/unittests/test_handler/test_handler_resizefs.py
139index 29d5574..5aa3c49 100644
140--- a/tests/unittests/test_handler/test_handler_resizefs.py
141+++ b/tests/unittests/test_handler/test_handler_resizefs.py
142@@ -1,7 +1,7 @@
143 # This file is part of cloud-init. See LICENSE file for license information.
144
145 from cloudinit.config.cc_resizefs import (
146- can_skip_resize, handle, maybe_get_writable_device_path)
147+ can_skip_resize, handle, maybe_get_writable_device_path, _resize_btrfs)
148
149 from collections import namedtuple
150 import logging
151@@ -293,5 +293,25 @@ class TestMaybeGetDevicePathAsWritableBlock(CiTestCase):
152 " per kernel cmdline",
153 self.logs.getvalue())
154
155+ @mock.patch('cloudinit.util.mount_is_read_write')
156+ @mock.patch('cloudinit.config.cc_resizefs.os.path.isdir')
157+ def test_resize_btrfs_mount_is_ro(self, m_is_dir, m_is_rw):
158+ """Do not resize / directly if it is read-only. (LP: #1734787)."""
159+ m_is_rw.return_value = False
160+ m_is_dir.return_value = True
161+ self.assertEqual(
162+ ('btrfs', 'filesystem', 'resize', 'max', '//.snapshots'),
163+ _resize_btrfs("/", "/dev/sda1"))
164+
165+ @mock.patch('cloudinit.util.mount_is_read_write')
166+ @mock.patch('cloudinit.config.cc_resizefs.os.path.isdir')
167+ def test_resize_btrfs_mount_is_rw(self, m_is_dir, m_is_rw):
168+ """Do not resize / directly if it is read-only. (LP: #1734787)."""
169+ m_is_rw.return_value = True
170+ m_is_dir.return_value = True
171+ self.assertEqual(
172+ ('btrfs', 'filesystem', 'resize', 'max', '/'),
173+ _resize_btrfs("/", "/dev/sda1"))
174+
175
176 # vi: ts=4 expandtab

Subscribers

People subscribed via source and target branches