1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
|
# Copyright 2017 Lars Wirzenius
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# =*= License: GPL-3+ =*=
import itertools
import logging
import os
import stat
import time
import vmdb
class MkpartPlugin(vmdb.Plugin):
def enable(self):
self.app.step_runners.add(MkpartStepRunner())
class MkpartStepRunner(vmdb.StepRunnerInterface):
def get_key_spec(self):
return {
"mkpart": str,
"device": str,
"start": str,
"end": str,
"tag": "",
"part-tag": "",
"fs-type": "ext2",
}
def run(self, values, settings, state):
part_type = values["mkpart"]
device = values["device"]
start = values["start"]
end = values["end"]
tag = values["tag"] or values["part-tag"] or None
fs_type = values["fs-type"]
# Enabling this breaks installation to an LVM2 logical volume.
# device = os.path.realpath(device)
orig = self.list_partitions(device)
vmdb.runcmd(
["parted", "-s", device, "--", "mkpart", part_type, fs_type, start, end]
)
new = self.list_partitions(device)
diff = self.diff_partitions(orig, new)
if len(diff) == 0:
raise ExpectedNewPartition()
if len(diff) > 1:
raise UnexpectedNewPartitions(diff)
state.tags.append(tag)
# If device is a real block device (e.g, /dev/sdb), the
# parition we have in diff are also real devices (e.g.,
# /dev/sdb1), and we should remember those in tags.
#
# If, however, device is a disk image file (e.g, foo.img), the
# partition in diff is not a device file but something like
# foo.img1. We don't need to remember that in tags. The user
# will use the kpartx step later to add those partitions into
# tags.
if self.is_block_dev(device):
self.wait_for_file_to_exist(diff[0])
vmdb.progress("remembering partition {} as {}".format(diff[0], tag))
state.tags.set_dev(tag, diff[0])
def is_block_dev(self, filename):
st = os.lstat(filename)
return stat.S_ISBLK(st.st_mode)
def list_partitions(self, device):
output = vmdb.runcmd(["parted", "-m", device, "print"])
output = output.decode("UTF-8")
partitions = [line.split(":")[0] for line in output.splitlines() if ":" in line]
return [
word if word.startswith("/") else "{}{}{}".format(
device,
# If the device name ends in a number,
# a 'p' is added between device and part number
'p' if device[-1].isnumeric() else '',
word
)
for word in partitions
]
def diff_partitions(self, old, new):
return [line for line in new if line not in old]
def wait_for_file_to_exist(self, filename, timeout=60):
logging.debug("Starting to wait for file %s to come to life", filename)
if timeout is not None:
logging.debug("Waiting %s second(s) before timeout", timeout)
for count in itertools.count():
if os.path.exists(filename):
return
if timeout is None or count < timeout:
time.sleep(1)
else:
raise TimeoutOnWaitingForFile(filename)
class MkpartError(Exception):
pass
class ExpectedNewPartition(MkpartError):
def __init__(self):
super().__init__("Expected a new partition to exist after mkpart")
class UnexpectedNewPartitions(MkpartError):
def __init__(self, diff):
super().__init__(
"Expected only one new partition to exist after mkpart, "
"but found {}".format(" ".join(diff))
)
class TimeoutOnWaitingForFile(MkpartError):
def __init__(self, part):
super().__init__(
"Waited for file {} to come to life, but timeout was "
"reached while waiting. ".format(part)
)
|