Merge ~raharper/curtin:fix/clear-holders-swap-partition into curtin:master

Proposed by Ryan Harper
Status: Merged
Approved by: Scott Moser
Approved revision: a79452b863516727eeae12ff6e41b77044accbf5
Merged at revision: 358fca4b96f0c6cea300c6a65d4350166fc8ff48
Proposed branch: ~raharper/curtin:fix/clear-holders-swap-partition
Merge into: curtin:master
Diff against target: 213 lines (+127/-0)
6 files modified
curtin/block/clear_holders.py (+16/-0)
curtin/swap.py (+15/-0)
examples/tests/basic.yaml (+9/-0)
examples/tests/dirty_disks_config.yaml (+26/-0)
tests/unittests/test_clear_holders.py (+22/-0)
tests/unittests/test_swap.py (+39/-0)
Reviewer Review Type Date Requested Status
Scott Moser (community) Approve
Server Team CI bot continuous-integration Approve
Review via email: mp+336335@code.launchpad.net

Description of the change

clear-holders: detect and remove devices from kernel swap as needed

During testing of subiquity the ephemeral environment may activate
existing swap partitions which then prevent curtin from using a device during
storage configuration. This branch introduces a change to TestBasic to add
a swap partition and a command to the dirty-disk mode to forcibly enable
the swap device to reproduce the error. The fix involves having
clear_holders detect and remove the device (disk or partition) from
the kernel swap via use of swapoff.

LP: #1743643

To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Chad Smith (chad.smith) :
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Scott Moser (smoser) :
review: Needs Fixing
Revision history for this message
Ryan Harper (raharper) wrote :

On Thu, Jan 25, 2018 at 7:17 PM, Scott Moser <email address hidden>
wrote:

> Review: Needs Fixing
>
>
>
> Diff comments:
>
> > diff --git a/curtin/swap.py b/curtin/swap.py
> > index 049c528..c77117c 100644
> > --- a/curtin/swap.py
> > +++ b/curtin/swap.py
> > @@ -93,4 +94,20 @@ def setup_swapfile(target, fstab=None, swapfile=None,
> size=None, maxsize=None):
> > os.unlink(fpath)
> > raise
> >
> > +<<<<<<< curtin/swap.py
> > # vi: ts=4 expandtab syntax=python
> > +=======
> > +
> > +def is_swap_device(path):
> > + """
> > + Determine if specified device is a swap device. Linux swap devices
> write
> > + a magic header value on kernel PAGESIZE - 10.
> > +
> > + https://github.com/torvalds/linux/blob/master/include/
> linux/swap.h#L111
> > + """
> > + LOG.debug('Checking if %s is a swap device', path)
> > + swap_magic_offset = resource.getpagesize() - 10
> > + magic = util.load_file(path, read_len=10, offset=swap_magic_offset)
>
> You're basically opening a device and reading from it.
> It very likely has non-utf-8 data on it, so you need to pass decode=False
> or it could raise a unicodedecode error.
>

OK.

>
> > + LOG.debug('Found swap magic: %s' % magic)
> > + return magic in ["SWAPSPACE2", "SWAP-SPACE"]
> > +>>>>>>> curtin/swap.py
> > diff --git a/examples/tests/dirty_disks_config.yaml
> b/examples/tests/dirty_disks_config.yaml
> > index 58d7713..3f22c24 100644
> > --- a/examples/tests/dirty_disks_config.yaml
> > +++ b/examples/tests/dirty_disks_config.yaml
> > @@ -3,6 +3,25 @@ bucket:
> > #!/bin/sh
> > command -v systemctl && systemctl mask media-root\\x2dro.mount
> > exit 0
> > + - &swapon |
> > + #!/bin/sh
> > + # enable any swap device or partition if present
> > + devices=""
> > + for device in /dev/[hsv]d[a-z][0-9]*; do
> > + if ! [ -b "$device" ]; then
> > + continue
> > + fi
> > + /sbin/blkid -o udev -p ${device%%[0-9]*} | grep -q
> "^ID_FS_USAGE=raid" && continue
> > + magic=$(/bin/dd if="$device" bs=4086 skip=1 count=1 2>/dev/null
> | /bin/dd bs=10 count=1 2>/dev/null) || continue
>
> drop /bin/dd just 'dd'.
> this reads bytes 4086 to 4096 ?
> sudo dd if=/swap.img bs=1 count=10 skip=4086
>
> also, can't you just use blkid ?
> $ sudo blkid /swap.img
> /swap.img: UUID="e7498b09-7346-4d64-9371-9f1ba738686d" TYPE="swap"
>

Well, the read is tempting; if I can get kernel PAGE_SIZE easly, then I'll
use that to
find PAGE_SIZE - 10; otherwise blkid will work but heavier.

> > + if [ "$magic" = "SWAPSPACE2" -o "$magic" = "SWAP-SPACE" ]; then
> > + devices="$devices $device"
> > + fi
> > + done
> > + for device in $devices; do
> > + swapon $device || true
> > + done
> > + swapon --show
> > + exit 0
> >
> > early_commands:
> > # systemd sucks; automatic mount and fsck services clash with curtin
>
>
> --
> https://code.launchpad.net/~raharper/curtin/+git/curtin/+merge/336335
> You are the owner of ~raharper/curtin:fix/clear-holders-swap-partition.
>

Revision history for this message
Ryan Harper (raharper) wrote :

Updated to address feedback.

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

these are nit picks.
generally this is fine.

i just like to avoid having code that I dont know why it is there.

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

ack,thanks.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/curtin/block/clear_holders.py b/curtin/block/clear_holders.py
index cd18f14..88907db 100644
--- a/curtin/block/clear_holders.py
+++ b/curtin/block/clear_holders.py
@@ -11,6 +11,7 @@ import os
11import time11import time
1212
13from curtin import (block, udev, util)13from curtin import (block, udev, util)
14from curtin.swap import is_swap_device
14from curtin.block import lvm15from curtin.block import lvm
15from curtin.block import mdadm16from curtin.block import mdadm
16from curtin.block import zfs17from curtin.block import zfs
@@ -251,6 +252,9 @@ def wipe_superblock(device):
251 poolname = zfs.device_to_poolname(blockdev)252 poolname = zfs.device_to_poolname(blockdev)
252 zfs.zpool_export(poolname)253 zfs.zpool_export(poolname)
253254
255 if is_swap_device(blockdev):
256 shutdown_swap(blockdev)
257
254 # some volumes will be claimed by the bcache layer but do not surface258 # some volumes will be claimed by the bcache layer but do not surface
255 # an actual /dev/bcacheN device which owns the parts (backing, cache)259 # an actual /dev/bcacheN device which owns the parts (backing, cache)
256 # The result is that some volumes cannot be wiped while bcache claims260 # The result is that some volumes cannot be wiped while bcache claims
@@ -322,6 +326,18 @@ def identify_partition(device):
322 return os.path.exists(path)326 return os.path.exists(path)
323327
324328
329def shutdown_swap(path):
330 """release swap device from kernel swap pool if present"""
331 procswaps = util.load_file('/proc/swaps')
332 for swapline in procswaps.splitlines():
333 if swapline.startswith(path):
334 msg = ('Removing %s from active use as swap device, '
335 'needed for storage config' % path)
336 LOG.warning(msg)
337 util.subp(['swapoff', path])
338 return
339
340
325def get_holders(device):341def get_holders(device):
326 """342 """
327 Look up any block device holders, return list of knames343 Look up any block device holders, return list of knames
diff --git a/curtin/swap.py b/curtin/swap.py
index 049c528..5f77b03 100644
--- a/curtin/swap.py
+++ b/curtin/swap.py
@@ -1,6 +1,7 @@
1# This file is part of curtin. See LICENSE file for copyright and license info.1# This file is part of curtin. See LICENSE file for copyright and license info.
22
3import os3import os
4import resource
45
5from .log import LOG6from .log import LOG
6from . import util7from . import util
@@ -93,4 +94,18 @@ def setup_swapfile(target, fstab=None, swapfile=None, size=None, maxsize=None):
93 os.unlink(fpath)94 os.unlink(fpath)
94 raise95 raise
9596
97
98def is_swap_device(path):
99 """
100 Determine if specified device is a swap device. Linux swap devices write
101 a magic header value on kernel PAGESIZE - 10.
102
103 https://github.com/torvalds/linux/blob/master/include/linux/swap.h#L111
104 """
105 LOG.debug('Checking if %s is a swap device', path)
106 swap_magic_offset = resource.getpagesize() - 10
107 magic = util.load_file(path, read_len=10, offset=swap_magic_offset,
108 decode=False)
109 LOG.debug('Found swap magic: %s' % magic)
110 return magic in [b'SWAPSPACE2', b'SWAP-SPACE']
96# vi: ts=4 expandtab syntax=python111# vi: ts=4 expandtab syntax=python
diff --git a/examples/tests/basic.yaml b/examples/tests/basic.yaml
index 4780d20..9b6bdd2 100644
--- a/examples/tests/basic.yaml
+++ b/examples/tests/basic.yaml
@@ -21,6 +21,11 @@ storage:
21 number: 221 number: 2
22 size: 1GB22 size: 1GB
23 device: sda23 device: sda
24 - id: sda3
25 type: partition
26 number: 3
27 size: 1GB
28 device: sda
24 - id: sda1_root29 - id: sda1_root
25 type: format30 type: format
26 fstype: ext431 fstype: ext4
@@ -30,6 +35,10 @@ storage:
30 type: format35 type: format
31 fstype: ext436 fstype: ext4
32 volume: sda237 volume: sda2
38 - id: sda3_swap
39 type: format
40 fstype: swap
41 volume: sda3
33 - id: sda1_mount42 - id: sda1_mount
34 type: mount43 type: mount
35 path: /44 path: /
diff --git a/examples/tests/dirty_disks_config.yaml b/examples/tests/dirty_disks_config.yaml
index d8707cf..18d331d 100644
--- a/examples/tests/dirty_disks_config.yaml
+++ b/examples/tests/dirty_disks_config.yaml
@@ -1,3 +1,28 @@
1bucket:
2 - &run_if_systemd |
3 #!/bin/sh
4 command -v systemctl && systemctl mask media-root\\x2dro.mount
5 exit 0
6 - &swapon |
7 #!/bin/sh
8 # enable any swap device or partition if present
9 devices=""
10 for device in /dev/[hsv]d[a-z][0-9]*; do
11 if ! [ -b "$device" ]; then
12 continue
13 fi
14 MOFF=$((`getconf PAGESIZE` - 10))
15 magic=$(dd if="$device" bs=1 skip=$MOFF count=10 2>/dev/null) || continue
16 if [ "$magic" = "SWAPSPACE2" -o "$magic" = "SWAP-SPACE" ]; then
17 devices="$devices $device"
18 fi
19 done
20 for device in $devices; do
21 swapon $device || true
22 done
23 swapon --show
24 exit 0
25
1early_commands:26early_commands:
2 # running block-meta custom from the install environment27 # running block-meta custom from the install environment
3 # inherits the CONFIG environment, so this works to actually prepare28 # inherits the CONFIG environment, so this works to actually prepare
@@ -8,3 +33,4 @@ early_commands:
8 TARGET_MOUNT_POINT=/tmp/my.bdir/target,33 TARGET_MOUNT_POINT=/tmp/my.bdir/target,
9 WORKING_DIR=/tmp/my.bdir/work.d, 34 WORKING_DIR=/tmp/my.bdir/work.d,
10 curtin, --showtrace, -v, block-meta, --umount, custom]35 curtin, --showtrace, -v, block-meta, --umount, custom]
36 enable_swaps: [sh, -c, *swapon]
diff --git a/tests/unittests/test_clear_holders.py b/tests/unittests/test_clear_holders.py
index cc33268..f2acf21 100644
--- a/tests/unittests/test_clear_holders.py
+++ b/tests/unittests/test_clear_holders.py
@@ -700,4 +700,26 @@ class TestClearHolders(CiTestCase):
700 self.assertNotIn(mock.call('zfs'),700 self.assertNotIn(mock.call('zfs'),
701 mock_util.load_kernel_module.call_args_list)701 mock_util.load_kernel_module.call_args_list)
702702
703 @mock.patch('curtin.block.clear_holders.util')
704 def test_shutdown_swap_calls_swapoff(self, mock_util):
705 """clear_holders.shutdown_swap() calls swapoff on active swap device"""
706 blockdev = '/mydev/dummydisk'
707 mock_util.load_file.return_value = textwrap.dedent("""\
708 Filename Type Size Used Priority
709 %s disk 4194300 53508 -1
710 """ % blockdev)
711 clear_holders.shutdown_swap(blockdev)
712 mock_util.subp.assert_called_with(['swapoff', blockdev])
713
714 @mock.patch('curtin.block.clear_holders.util')
715 def test_shutdown_swap_skips_nomatch(self, mock_util):
716 """clear_holders.shutdown_swap() ignores unmatched swapdevices"""
717 blockdev = '/mydev/dummydisk'
718 mock_util.load_file.return_value = textwrap.dedent("""\
719 Filename Type Size Used Priority
720 /foobar/wark disk 4194300 53508 -1
721 """)
722 clear_holders.shutdown_swap(blockdev)
723 self.assertEqual(0, mock_util.subp.call_count)
724
703# vi: ts=4 expandtab syntax=python725# vi: ts=4 expandtab syntax=python
diff --git a/tests/unittests/test_swap.py b/tests/unittests/test_swap.py
704new file mode 100644726new file mode 100644
index 0000000..e12d12e
--- /dev/null
+++ b/tests/unittests/test_swap.py
@@ -0,0 +1,39 @@
1import mock
2
3from curtin import swap
4from .helpers import CiTestCase
5
6
7class TestSwap(CiTestCase):
8 @mock.patch('curtin.swap.resource')
9 @mock.patch('curtin.swap.util')
10 def test_is_swap_device_read_offsets(self, mock_util, mock_resource):
11 """swap.is_swap_device() checks offsets based on system pagesize"""
12 path = '/mydev/dummydisk'
13 # 4k and 64k page size
14 for pagesize in [4096, 65536]:
15 magic_offset = pagesize - 10
16 mock_resource.getpagesize.return_value = pagesize
17 swap.is_swap_device(path)
18 mock_util.load_file.assert_called_with(path, read_len=10,
19 offset=magic_offset,
20 decode=False)
21
22 @mock.patch('curtin.swap.resource')
23 @mock.patch('curtin.swap.util')
24 def test_identify_swap_false(self, mock_util, mock_resource):
25 """swap.is_swap_device() returns false on non swap magic"""
26 mock_util.load_file.return_value = (
27 b'\x00\x00c\x05\x00\x00\x11\x00\x19\x00')
28 is_swap = swap.is_swap_device('ignored')
29 self.assertFalse(is_swap)
30
31 @mock.patch('curtin.swap.resource')
32 @mock.patch('curtin.swap.util')
33 def test_identify_swap_true(self, mock_util, mock_resource):
34 """swap.is_swap_device() returns true on swap magic strings"""
35 path = '/mydev/dummydisk'
36 for magic in [b'SWAPSPACE2', b'SWAP-SPACE']:
37 mock_util.load_file.return_value = magic
38 is_swap = swap.is_swap_device(path)
39 self.assertTrue(is_swap)

Subscribers

People subscribed via source and target branches