Drop python3-parted use
Using parted required root privileges since it opened the underlying device. Instead create our own Disk model and use sysfs interface for extracting size information. This also clears the way for providing device data via probert input. This means we can feed subiquity a probert dump and have it present that to the installer UI even if we're running on a different system. Signed-off-by: Ryan Harper <ryan.harper@canonical.com>
This commit is contained in:
parent
b10a9ade16
commit
a16c301eed
2
Makefile
2
Makefile
|
@ -27,7 +27,7 @@ $(NAME)_$(VERSION).orig.tar.gz: clean
|
|||
tarball: $(NAME)_$(VERSION).orig.tar.gz
|
||||
|
||||
install_deps:
|
||||
sudo apt-get install python3-urwid python3-pyudev python3-netifaces python3-nose python3-flake8 python3-parted python3-yaml git bzr ubuntu-cloudimage-keyring python3-jinja2 python3-coverage
|
||||
sudo apt-get install python3-urwid python3-pyudev python3-netifaces python3-nose python3-flake8 python3-yaml git bzr ubuntu-cloudimage-keyring python3-jinja2 python3-coverage
|
||||
|
||||
dryrun:
|
||||
$(MAKE) ui-view DRYRUN="--dry-run"
|
||||
|
|
|
@ -35,7 +35,6 @@ SRC_DEPS=(
|
|||
)
|
||||
INSTALLER_DEPS=(
|
||||
"petname"
|
||||
"python3-parted"
|
||||
"python3-urwid"
|
||||
"python3-pyudev"
|
||||
"python3-netifaces"
|
||||
|
|
|
@ -106,6 +106,7 @@ class FilesystemController(ControllerPolicy):
|
|||
# adjust downward the partition size to accommodate
|
||||
# the bios/grub partition
|
||||
spec['bytes'] -= BIOS_GRUB_SIZE_BYTES
|
||||
spec['partnum'] = 2
|
||||
|
||||
if spec["fstype"] in ["swap"]:
|
||||
current_disk.add_partition(partnum=spec["partnum"],
|
||||
|
|
|
@ -17,12 +17,13 @@ import yaml
|
|||
|
||||
|
||||
class DiskAction():
|
||||
def __init__(self, action_id, model, serial, ptable='gpt'):
|
||||
def __init__(self, action_id, model, serial, ptable='gpt', wipe=None):
|
||||
self._action_id = action_id
|
||||
self.parent = None
|
||||
self._ptable = ptable
|
||||
self._model = model
|
||||
self._serial = serial
|
||||
self._wipe = wipe
|
||||
|
||||
def get_parent(self):
|
||||
return self.parent
|
||||
|
@ -32,42 +33,55 @@ class DiskAction():
|
|||
return str(self._action_id)
|
||||
|
||||
def get(self):
|
||||
return {
|
||||
action = {
|
||||
'id': self.action_id,
|
||||
'model': self._model,
|
||||
'ptable': self._ptable,
|
||||
'serial': self._serial,
|
||||
'type': 'disk',
|
||||
}
|
||||
if self._wipe:
|
||||
action.update({'wipe': self._wipe})
|
||||
return action
|
||||
|
||||
def dump(self):
|
||||
return yaml.dump(self.get(), default_flow_style=False)
|
||||
|
||||
|
||||
class PartitionAction(DiskAction):
|
||||
def __init__(self, parent, partnumber, size, flags=None):
|
||||
def __init__(self, parent, partnum, offset, size, flags=None):
|
||||
self.parent = parent
|
||||
self.partnumber = int(partnumber)
|
||||
self.partnum = int(partnum)
|
||||
self._offset = int(offset)
|
||||
self._size = int(size)
|
||||
self.flags = flags
|
||||
self._action_id = "{}{}_part".format(self.parent.action_id,
|
||||
self.partnumber)
|
||||
self.partnum)
|
||||
|
||||
''' rename action_id for readability '''
|
||||
if self.flags in ['bios_grub']:
|
||||
self._action_id = 'bios_boot_partition'
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
return "{}{}".format(self.parent.action_id, self.partnum)
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
return self._size
|
||||
|
||||
@property
|
||||
def offset(self):
|
||||
return self._offset
|
||||
|
||||
def get(self):
|
||||
return {
|
||||
'device': self.parent.action_id,
|
||||
'flag': self.flags,
|
||||
'id': self.action_id,
|
||||
'number': self.partnumber,
|
||||
'number': self.partnum,
|
||||
'size': '{}B'.format(self.size),
|
||||
'offset': '{}B'.format(self.offset),
|
||||
'type': 'partition',
|
||||
}
|
||||
|
||||
|
|
|
@ -13,10 +13,10 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from collections import OrderedDict
|
||||
from itertools import count
|
||||
import logging
|
||||
import os
|
||||
import parted
|
||||
import re
|
||||
import yaml
|
||||
|
||||
|
@ -57,45 +57,82 @@ class Bcachedev():
|
|||
return self._path
|
||||
|
||||
def getSize(self, unit='MB'):
|
||||
if type(self._backing) == parted.disk.Disk:
|
||||
return self._backing.device.getSize(unit=unit)
|
||||
else:
|
||||
return self._backing.getSize(unit=unit)
|
||||
pass
|
||||
|
||||
|
||||
class Disk():
|
||||
def __init__(self, devpath, serial, model, parttype, size=0):
|
||||
self._devpath = devpath
|
||||
self._serial = serial
|
||||
self._parttype = parttype
|
||||
self._model = model
|
||||
self._size = self._get_size(devpath, size)
|
||||
self._partitions = OrderedDict()
|
||||
|
||||
def _get_size(self, devpath, size):
|
||||
if size:
|
||||
return size
|
||||
sysblock = os.path.join('/sys/block', os.path.basename(devpath))
|
||||
nr_blocks_f = os.path.join(sysblock, 'size')
|
||||
block_sz_f = os.path.join(sysblock, 'queue', 'logical_block_size')
|
||||
with open(nr_blocks_f, 'r') as r:
|
||||
nr_blocks = int(r.read())
|
||||
with open(block_sz_f, 'r') as r:
|
||||
block_sz = int(r.read())
|
||||
|
||||
return nr_blocks * block_sz
|
||||
|
||||
@property
|
||||
def devpath(self):
|
||||
return self._devpath
|
||||
|
||||
@property
|
||||
def serial(self):
|
||||
return self._serial
|
||||
|
||||
@property
|
||||
def model(self):
|
||||
return self._model
|
||||
|
||||
@property
|
||||
def parttype(self):
|
||||
return self._parttype
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
return self._size
|
||||
|
||||
@property
|
||||
def partitions(self):
|
||||
return self._partitions
|
||||
|
||||
def reset(self):
|
||||
self._partitions = OrderedDict()
|
||||
pass
|
||||
|
||||
|
||||
class Blockdev():
|
||||
def __init__(self, devpath, serial, parttype='gpt'):
|
||||
self.serial = serial
|
||||
self.devpath = devpath
|
||||
self._parttype = parttype
|
||||
self.device = parted.getDevice(self.devpath)
|
||||
self.disk = parted.freshDisk(self.device, self.parttype)
|
||||
def __init__(self, devpath, serial, model, parttype='gpt', size=0):
|
||||
self.disk = Disk(devpath, serial, model, parttype, size)
|
||||
self._filesystems = {}
|
||||
self._mounts = {}
|
||||
self.bcache = []
|
||||
self.lvm = []
|
||||
self.baseaction = DiskAction(os.path.basename(self.disk.devpath),
|
||||
self.disk.model, self.disk.serial,
|
||||
self.disk.parttype)
|
||||
|
||||
def reset(self):
|
||||
''' Wipe out any actions queued for this disk '''
|
||||
self.disk = parted.freshDisk(self.device, self.parttype)
|
||||
self.disk.reset()
|
||||
self._filesystems = {}
|
||||
self._mounts = {}
|
||||
self.bcache = []
|
||||
self.lvm = []
|
||||
|
||||
def _get_largest_free_region(self):
|
||||
"""Finds largest free region on the disk"""
|
||||
# There are better ways to do it, but let's be straightforward
|
||||
max_size = -1
|
||||
region = None
|
||||
|
||||
alignment = self.device.optimumAlignment
|
||||
|
||||
for r in self.disk.getFreeSpaceRegions():
|
||||
# Heuristic: Ignore alignment gaps
|
||||
if r.length > max_size and r.length > alignment.grainSize:
|
||||
region = r
|
||||
max_size = r.length
|
||||
|
||||
return region
|
||||
@property
|
||||
def devpath(self):
|
||||
return self.disk.devpath
|
||||
|
||||
@property
|
||||
def mounts(self):
|
||||
|
@ -103,7 +140,7 @@ class Blockdev():
|
|||
|
||||
@property
|
||||
def parttype(self):
|
||||
return self._parttype
|
||||
return self.disk.parttype
|
||||
|
||||
@parttype.setter # NOQA
|
||||
def parttype(self, value):
|
||||
|
@ -111,12 +148,16 @@ class Blockdev():
|
|||
|
||||
@property
|
||||
def size(self):
|
||||
return self.disk.device.getLength(unit='B')
|
||||
return self.disk.size
|
||||
|
||||
@property
|
||||
def partitions(self):
|
||||
return self.disk.partitions
|
||||
|
||||
@property
|
||||
def filesystems(self):
|
||||
return self._filesystems
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
''' return True if has free space or partitions not
|
||||
|
@ -128,27 +169,27 @@ class Blockdev():
|
|||
@property
|
||||
def usedspace(self, unit='b'):
|
||||
''' return amount of used space'''
|
||||
return sum([part.geometry.getSize(unit=unit) for part in
|
||||
self.disk.partitions])
|
||||
space = 0
|
||||
for (num, action) in self.disk.partitions.items():
|
||||
space += int(action.offset)
|
||||
space += int(action.size)
|
||||
|
||||
log.debug('{} usedspace: {}'.format(self.disk.devpath, space))
|
||||
return space
|
||||
|
||||
@property
|
||||
def freespace(self, unit='B'):
|
||||
''' return amount of free space '''
|
||||
geo = self._get_largest_free_region()
|
||||
if geo:
|
||||
return geo.getLength(unit=unit)
|
||||
return 0
|
||||
|
||||
@property
|
||||
def freepartition(self, unit='b'):
|
||||
''' return amount of partitionable space'''
|
||||
return sum([part.geometry.getSize(unit=unit) for part in
|
||||
self.disk.getFreeSpacePartitions()])
|
||||
used = self.usedspace
|
||||
size = self.size
|
||||
log.debug('{} freespace: {} - {} = {}'.format(self.disk.devpath,
|
||||
size, used,
|
||||
size - used))
|
||||
return size - used
|
||||
|
||||
@property
|
||||
def lastpartnumber(self):
|
||||
return self.disk.lastPartitionNumber if \
|
||||
self.disk.lastPartitionNumber > 0 else 0
|
||||
return len(self.disk.partitions)
|
||||
|
||||
def delete_partition(self, partnum=None, sector=None, mountpoint=None):
|
||||
# find part and then call deletePartition()
|
||||
|
@ -161,77 +202,53 @@ class Blockdev():
|
|||
' partnum:%s size:%s fstype:%s mountpoint:%s flag=%s' % (
|
||||
partnum, size, fstype, mountpoint, flag))
|
||||
|
||||
if size > self.freepartition:
|
||||
if size > self.freespace:
|
||||
raise Exception('Not enough space')
|
||||
|
||||
if fstype in ["swap"]:
|
||||
fstype = "linux-swap(v1)"
|
||||
|
||||
geometry = self._get_largest_free_region()
|
||||
if not geometry:
|
||||
raise Exception('No free sectors available')
|
||||
log.debug('largest free region:\n{}'.format(geometry))
|
||||
|
||||
# convert size into a geometry based on existing partitions
|
||||
try:
|
||||
start = self.disk.partitions[-1].geometry.end + 1
|
||||
except IndexError:
|
||||
start = 0
|
||||
length = parted.sizeToSectors(size, 'B', self.device.sectorSize)
|
||||
log.debug('requested start: {} length: {}'.format(start, length))
|
||||
req_geo = parted.Geometry(self.device, start=start, length=length)
|
||||
|
||||
# find common area
|
||||
parttype = parted.PARTITION_NORMAL
|
||||
alignment = self.device.optimalAlignedConstraint
|
||||
geometry = geometry.intersect(req_geo)
|
||||
# update geometry with alignment
|
||||
constraint = parted.Constraint(maxGeom=geometry).intersect(alignment)
|
||||
data = {
|
||||
'start': constraint.startAlign.alignUp(geometry, geometry.start),
|
||||
'end': constraint.endAlign.alignDown(geometry, geometry.end),
|
||||
}
|
||||
geometry = parted.Geometry(device=self.device,
|
||||
start=data['start'],
|
||||
end=data['end'])
|
||||
# create partition
|
||||
if fstype not in [None, 'bcache cache', 'bcache store']:
|
||||
fs = parted.FileSystem(type=fstype, geometry=geometry)
|
||||
if len(self.disk.partitions) == 0:
|
||||
offset = 1 << 20 # 1K offset/aligned
|
||||
size += offset
|
||||
else:
|
||||
fs = None
|
||||
partition = parted.Partition(disk=self.disk, type=parttype,
|
||||
fs=fs, geometry=geometry)
|
||||
offset = 0
|
||||
|
||||
# add flags
|
||||
flags = {
|
||||
"boot": parted.PARTITION_BOOT,
|
||||
"lvm": parted.PARTITION_LVM,
|
||||
"raid": parted.PARTITION_RAID,
|
||||
"bios_grub": parted.PARTITION_BIOS_GRUB
|
||||
}
|
||||
if flag in flags:
|
||||
partition.setFlag(flags[flag])
|
||||
log.debug('requested start: {} length: {}'.format(offset, size))
|
||||
valid_flags = [
|
||||
"boot",
|
||||
"lvm",
|
||||
"raid",
|
||||
"bios_grub",
|
||||
]
|
||||
if flag and flag not in valid_flags:
|
||||
raise Exception('Flag: {} is not valid.'.format(flag))
|
||||
|
||||
self.disk.addPartition(partition=partition, constraint=constraint)
|
||||
# create partition and add
|
||||
part_action = PartitionAction(self.baseaction, partnum,
|
||||
offset, size, flag)
|
||||
log.debug('PartitionAction:\n{}'.format(part_action))
|
||||
|
||||
# fetch the newly created partition
|
||||
partpath = "{}{}".format(self.disk.device.path, partition.number)
|
||||
newpart = self.disk.getPartitionByPath(partpath)
|
||||
self.disk.partitions.update({partnum: part_action})
|
||||
|
||||
# create bcachedev if neded
|
||||
if fstype and fstype.startswith('bcache'):
|
||||
mode = fstype.split()[-1]
|
||||
self.bcache.append(Bcachedev(backing=newpart, mode=mode))
|
||||
# record filesystem formating
|
||||
if fstype:
|
||||
partpath = "{}{}".format(self.disk.devpath, partnum)
|
||||
fs_action = FormatAction(part_action, fstype)
|
||||
log.debug('Adding filesystem: {}:{}'.format(partpath, fs_action))
|
||||
self.filesystems.update({partpath: fs_action})
|
||||
|
||||
# associate partition devpath with mountpoint
|
||||
if mountpoint:
|
||||
self._mounts[partpath] = mountpoint
|
||||
|
||||
log.debug('Partition Added')
|
||||
|
||||
def is_mounted(self):
|
||||
with open('/proc/mounts') as pm:
|
||||
mounts = pm.read()
|
||||
|
||||
regexp = '{}.*'.format(self.disk.device.path)
|
||||
regexp = '{}.*'.format(self.disk.devpath)
|
||||
matches = re.findall(regexp, mounts)
|
||||
if len(matches) > 0:
|
||||
log.debug('Device is mounted: {}'.format(matches))
|
||||
|
@ -245,28 +262,17 @@ class Blockdev():
|
|||
return []
|
||||
|
||||
actions = []
|
||||
baseaction = DiskAction(os.path.basename(self.disk.device.path),
|
||||
self.device.model, self.serial, self.parttype)
|
||||
action = baseaction.get()
|
||||
for part in self.disk.partitions:
|
||||
fs_size = int(part.getSize(unit='B'))
|
||||
if part.fileSystem:
|
||||
fs_type = part.fileSystem.type
|
||||
else:
|
||||
fs_type = None
|
||||
flags = part.getFlagsAsString()
|
||||
|
||||
partition_action = PartitionAction(baseaction,
|
||||
part.number,
|
||||
fs_size, flags)
|
||||
actions.append(partition_action)
|
||||
if fs_type:
|
||||
format_action = FormatAction(partition_action,
|
||||
fs_type)
|
||||
action = self.baseaction.get()
|
||||
for (num, part) in self.disk.partitions.items():
|
||||
partpath = "{}{}".format(self.disk.devpath, part.partnum)
|
||||
actions.append(part)
|
||||
if partpath in self.filesystems:
|
||||
format_action = self.filesystems[partpath]
|
||||
actions.append(format_action)
|
||||
mountpoint = self._mounts.get(part.path)
|
||||
if mountpoint:
|
||||
mount_action = MountAction(format_action, mountpoint)
|
||||
|
||||
if partpath in self._mounts:
|
||||
mount_action = MountAction(format_action,
|
||||
self._mounts[partpath])
|
||||
actions.append(mount_action)
|
||||
|
||||
return [action] + [a.get() for a in actions]
|
||||
|
@ -274,14 +280,13 @@ class Blockdev():
|
|||
def get_fs_table(self):
|
||||
''' list(mountpoint, humansize, fstype, partition_path) '''
|
||||
fs_table = []
|
||||
for part in self.disk.partitions:
|
||||
if part.fileSystem:
|
||||
mntpoint = self._mounts.get(part.path, part.fileSystem.type)
|
||||
fs_size = part.getSize(unit='B')
|
||||
fs_type = part.fileSystem.type
|
||||
devpath = part.path
|
||||
for (num, part) in self.disk.partitions.items():
|
||||
partpath = "{}{}".format(self.disk.devpath, part.partnum)
|
||||
if partpath in self.filesystems:
|
||||
fs = self.filesystems[partpath]
|
||||
mntpoint = self._mounts.get(partpath, fs.fstype)
|
||||
fs_table.append(
|
||||
(mntpoint, fs_size, fs_type, devpath))
|
||||
(mntpoint, part.size, fs.fstype, partpath))
|
||||
|
||||
return fs_table
|
||||
|
||||
|
@ -297,11 +302,17 @@ if __name__ == '__main__':
|
|||
print("USED DISKS")
|
||||
|
||||
devices = []
|
||||
sda = Blockdev('/dev/sda', 'QM_TARGET_01', parttype='gpt')
|
||||
sdb = Blockdev('/dev/sdb', 'dafunk')
|
||||
#Blockdev(devpath, serial, model, parttype='gpt'):
|
||||
GB = 1 << 30
|
||||
sda = Blockdev('/dev/sda', 'QM_TARGET_01', 'QEMU SSD DISK',
|
||||
parttype='gpt', size=128 * GB)
|
||||
sdb = Blockdev('/dev/sdb', 'dafunk', 'QEMU SPINNER', size=500 * GB)
|
||||
|
||||
print(sda.freespace)
|
||||
sda.add_partition(1, 8 * 1024 * 1024 * 1024, 'ext4', '/', 'bios_grub')
|
||||
print(sda.freespace)
|
||||
sda.add_partition(2, 2 * 1024 * 1024 * 1024, 'ext4', '/home')
|
||||
print(sda.freespace)
|
||||
sdb.add_partition(1, 50 * 1024 * 1024 * 1024, 'btrfs', '/opt')
|
||||
|
||||
get_filesystems([sda, sdb])
|
||||
|
|
|
@ -134,13 +134,15 @@ class FilesystemModel(ModelPolicy):
|
|||
|
||||
def get_disk(self, disk):
|
||||
if disk not in self.devices:
|
||||
self.devices[disk] = Blockdev(disk, self.info[disk].serial)
|
||||
self.devices[disk] = Blockdev(disk, self.info[disk].serial,
|
||||
self.info[disk].model)
|
||||
return self.devices[disk]
|
||||
|
||||
def get_partitions(self):
|
||||
partitions = []
|
||||
for dev in self.devices.values():
|
||||
partnames = [part.path for part in dev.disk.partitions]
|
||||
partnames = [part.path for (num, part) in
|
||||
dev.disk.partitions.items()]
|
||||
partitions += partnames
|
||||
|
||||
sorted(partitions)
|
||||
|
|
Loading…
Reference in New Issue