Merge pull request #30 from CanonicalLtd/drop_pyparted

Drop python3-parted use
This commit is contained in:
Adam Stokes 2015-08-28 09:21:53 -04:00
commit 8ab7e2dd0f
6 changed files with 163 additions and 136 deletions

View File

@ -27,7 +27,7 @@ $(NAME)_$(VERSION).orig.tar.gz: clean
tarball: $(NAME)_$(VERSION).orig.tar.gz tarball: $(NAME)_$(VERSION).orig.tar.gz
install_deps: 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: dryrun:
$(MAKE) ui-view DRYRUN="--dry-run" $(MAKE) ui-view DRYRUN="--dry-run"

View File

@ -35,7 +35,6 @@ SRC_DEPS=(
) )
INSTALLER_DEPS=( INSTALLER_DEPS=(
"petname" "petname"
"python3-parted"
"python3-urwid" "python3-urwid"
"python3-pyudev" "python3-pyudev"
"python3-netifaces" "python3-netifaces"

View File

@ -106,6 +106,7 @@ class FilesystemController(ControllerPolicy):
# adjust downward the partition size to accommodate # adjust downward the partition size to accommodate
# the bios/grub partition # the bios/grub partition
spec['bytes'] -= BIOS_GRUB_SIZE_BYTES spec['bytes'] -= BIOS_GRUB_SIZE_BYTES
spec['partnum'] = 2
if spec["fstype"] in ["swap"]: if spec["fstype"] in ["swap"]:
current_disk.add_partition(partnum=spec["partnum"], current_disk.add_partition(partnum=spec["partnum"],

View File

@ -17,12 +17,13 @@ import yaml
class DiskAction(): 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._action_id = action_id
self.parent = None self.parent = None
self._ptable = ptable self._ptable = ptable
self._model = model self._model = model
self._serial = serial self._serial = serial
self._wipe = wipe
def get_parent(self): def get_parent(self):
return self.parent return self.parent
@ -32,42 +33,55 @@ class DiskAction():
return str(self._action_id) return str(self._action_id)
def get(self): def get(self):
return { action = {
'id': self.action_id, 'id': self.action_id,
'model': self._model, 'model': self._model,
'ptable': self._ptable, 'ptable': self._ptable,
'serial': self._serial, 'serial': self._serial,
'type': 'disk', 'type': 'disk',
} }
if self._wipe:
action.update({'wipe': self._wipe})
return action
def dump(self): def dump(self):
return yaml.dump(self.get(), default_flow_style=False) return yaml.dump(self.get(), default_flow_style=False)
class PartitionAction(DiskAction): class PartitionAction(DiskAction):
def __init__(self, parent, partnumber, size, flags=None): def __init__(self, parent, partnum, offset, size, flags=None):
self.parent = parent self.parent = parent
self.partnumber = int(partnumber) self.partnum = int(partnum)
self._offset = int(offset)
self._size = int(size) self._size = int(size)
self.flags = flags self.flags = flags
self._action_id = "{}{}_part".format(self.parent.action_id, self._action_id = "{}{}_part".format(self.parent.action_id,
self.partnumber) self.partnum)
''' rename action_id for readability ''' ''' rename action_id for readability '''
if self.flags in ['bios_grub']: if self.flags in ['bios_grub']:
self._action_id = 'bios_boot_partition' self._action_id = 'bios_boot_partition'
@property
def path(self):
return "{}{}".format(self.parent.action_id, self.partnum)
@property @property
def size(self): def size(self):
return self._size return self._size
@property
def offset(self):
return self._offset
def get(self): def get(self):
return { return {
'device': self.parent.action_id, 'device': self.parent.action_id,
'flag': self.flags, 'flag': self.flags,
'id': self.action_id, 'id': self.action_id,
'number': self.partnumber, 'number': self.partnum,
'size': '{}B'.format(self.size), 'size': '{}B'.format(self.size),
'offset': '{}B'.format(self.offset),
'type': 'partition', 'type': 'partition',
} }

View File

@ -13,10 +13,10 @@
# You should have received a copy of the GNU Affero General Public License # 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/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from collections import OrderedDict
from itertools import count from itertools import count
import logging import logging
import os import os
import parted
import re import re
import yaml import yaml
@ -57,45 +57,82 @@ class Bcachedev():
return self._path return self._path
def getSize(self, unit='MB'): def getSize(self, unit='MB'):
if type(self._backing) == parted.disk.Disk: pass
return self._backing.device.getSize(unit=unit)
else:
return self._backing.getSize(unit=unit) 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(): class Blockdev():
def __init__(self, devpath, serial, parttype='gpt'): def __init__(self, devpath, serial, model, parttype='gpt', size=0):
self.serial = serial self.disk = Disk(devpath, serial, model, parttype, size)
self.devpath = devpath self._filesystems = {}
self._parttype = parttype
self.device = parted.getDevice(self.devpath)
self.disk = parted.freshDisk(self.device, self.parttype)
self._mounts = {} self._mounts = {}
self.bcache = [] self.bcache = []
self.lvm = [] self.lvm = []
self.baseaction = DiskAction(os.path.basename(self.disk.devpath),
self.disk.model, self.disk.serial,
self.disk.parttype)
def reset(self): def reset(self):
''' Wipe out any actions queued for this disk ''' ''' Wipe out any actions queued for this disk '''
self.disk = parted.freshDisk(self.device, self.parttype) self.disk.reset()
self._filesystems = {}
self._mounts = {} self._mounts = {}
self.bcache = [] self.bcache = []
self.lvm = [] self.lvm = []
def _get_largest_free_region(self): @property
"""Finds largest free region on the disk""" def devpath(self):
# There are better ways to do it, but let's be straightforward return self.disk.devpath
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 @property
def mounts(self): def mounts(self):
@ -103,7 +140,7 @@ class Blockdev():
@property @property
def parttype(self): def parttype(self):
return self._parttype return self.disk.parttype
@parttype.setter # NOQA @parttype.setter # NOQA
def parttype(self, value): def parttype(self, value):
@ -111,12 +148,16 @@ class Blockdev():
@property @property
def size(self): def size(self):
return self.disk.device.getLength(unit='B') return self.disk.size
@property @property
def partitions(self): def partitions(self):
return self.disk.partitions return self.disk.partitions
@property
def filesystems(self):
return self._filesystems
@property @property
def available(self): def available(self):
''' return True if has free space or partitions not ''' return True if has free space or partitions not
@ -128,27 +169,27 @@ class Blockdev():
@property @property
def usedspace(self, unit='b'): def usedspace(self, unit='b'):
''' return amount of used space''' ''' return amount of used space'''
return sum([part.geometry.getSize(unit=unit) for part in space = 0
self.disk.partitions]) 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 @property
def freespace(self, unit='B'): def freespace(self, unit='B'):
''' return amount of free space ''' ''' return amount of free space '''
geo = self._get_largest_free_region() used = self.usedspace
if geo: size = self.size
return geo.getLength(unit=unit) log.debug('{} freespace: {} - {} = {}'.format(self.disk.devpath,
return 0 size, used,
size - used))
@property return size - used
def freepartition(self, unit='b'):
''' return amount of partitionable space'''
return sum([part.geometry.getSize(unit=unit) for part in
self.disk.getFreeSpacePartitions()])
@property @property
def lastpartnumber(self): def lastpartnumber(self):
return self.disk.lastPartitionNumber if \ return len(self.disk.partitions)
self.disk.lastPartitionNumber > 0 else 0
def delete_partition(self, partnum=None, sector=None, mountpoint=None): def delete_partition(self, partnum=None, sector=None, mountpoint=None):
# find part and then call deletePartition() # find part and then call deletePartition()
@ -161,77 +202,53 @@ class Blockdev():
' partnum:%s size:%s fstype:%s mountpoint:%s flag=%s' % ( ' partnum:%s size:%s fstype:%s mountpoint:%s flag=%s' % (
partnum, size, fstype, mountpoint, flag)) partnum, size, fstype, mountpoint, flag))
if size > self.freepartition: if size > self.freespace:
raise Exception('Not enough space') raise Exception('Not enough space')
if fstype in ["swap"]: if fstype in ["swap"]:
fstype = "linux-swap(v1)" fstype = "linux-swap(v1)"
geometry = self._get_largest_free_region() if len(self.disk.partitions) == 0:
if not geometry: offset = 1 << 20 # 1K offset/aligned
raise Exception('No free sectors available') size += offset
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)
else: else:
fs = None offset = 0
partition = parted.Partition(disk=self.disk, type=parttype,
fs=fs, geometry=geometry)
# add flags log.debug('requested start: {} length: {}'.format(offset, size))
flags = { valid_flags = [
"boot": parted.PARTITION_BOOT, "boot",
"lvm": parted.PARTITION_LVM, "lvm",
"raid": parted.PARTITION_RAID, "raid",
"bios_grub": parted.PARTITION_BIOS_GRUB "bios_grub",
} ]
if flag in flags: if flag and flag not in valid_flags:
partition.setFlag(flags[flag]) 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 self.disk.partitions.update({partnum: part_action})
partpath = "{}{}".format(self.disk.device.path, partition.number)
newpart = self.disk.getPartitionByPath(partpath)
# create bcachedev if neded # record filesystem formating
if fstype and fstype.startswith('bcache'): if fstype:
mode = fstype.split()[-1] partpath = "{}{}".format(self.disk.devpath, partnum)
self.bcache.append(Bcachedev(backing=newpart, mode=mode)) 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 # associate partition devpath with mountpoint
if mountpoint: if mountpoint:
self._mounts[partpath] = mountpoint self._mounts[partpath] = mountpoint
log.debug('Partition Added')
def is_mounted(self): def is_mounted(self):
with open('/proc/mounts') as pm: with open('/proc/mounts') as pm:
mounts = pm.read() mounts = pm.read()
regexp = '{}.*'.format(self.disk.device.path) regexp = '{}.*'.format(self.disk.devpath)
matches = re.findall(regexp, mounts) matches = re.findall(regexp, mounts)
if len(matches) > 0: if len(matches) > 0:
log.debug('Device is mounted: {}'.format(matches)) log.debug('Device is mounted: {}'.format(matches))
@ -245,28 +262,17 @@ class Blockdev():
return [] return []
actions = [] actions = []
baseaction = DiskAction(os.path.basename(self.disk.device.path), action = self.baseaction.get()
self.device.model, self.serial, self.parttype) for (num, part) in self.disk.partitions.items():
action = baseaction.get() partpath = "{}{}".format(self.disk.devpath, part.partnum)
for part in self.disk.partitions: actions.append(part)
fs_size = int(part.getSize(unit='B')) if partpath in self.filesystems:
if part.fileSystem: format_action = self.filesystems[partpath]
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)
actions.append(format_action) actions.append(format_action)
mountpoint = self._mounts.get(part.path)
if mountpoint: if partpath in self._mounts:
mount_action = MountAction(format_action, mountpoint) mount_action = MountAction(format_action,
self._mounts[partpath])
actions.append(mount_action) actions.append(mount_action)
return [action] + [a.get() for a in actions] return [action] + [a.get() for a in actions]
@ -274,14 +280,13 @@ class Blockdev():
def get_fs_table(self): def get_fs_table(self):
''' list(mountpoint, humansize, fstype, partition_path) ''' ''' list(mountpoint, humansize, fstype, partition_path) '''
fs_table = [] fs_table = []
for part in self.disk.partitions: for (num, part) in self.disk.partitions.items():
if part.fileSystem: partpath = "{}{}".format(self.disk.devpath, part.partnum)
mntpoint = self._mounts.get(part.path, part.fileSystem.type) if partpath in self.filesystems:
fs_size = part.getSize(unit='B') fs = self.filesystems[partpath]
fs_type = part.fileSystem.type mntpoint = self._mounts.get(partpath, fs.fstype)
devpath = part.path
fs_table.append( fs_table.append(
(mntpoint, fs_size, fs_type, devpath)) (mntpoint, part.size, fs.fstype, partpath))
return fs_table return fs_table
@ -297,11 +302,17 @@ if __name__ == '__main__':
print("USED DISKS") print("USED DISKS")
devices = [] devices = []
sda = Blockdev('/dev/sda', 'QM_TARGET_01', parttype='gpt') #Blockdev(devpath, serial, model, parttype='gpt'):
sdb = Blockdev('/dev/sdb', 'dafunk') 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') sda.add_partition(1, 8 * 1024 * 1024 * 1024, 'ext4', '/', 'bios_grub')
print(sda.freespace)
sda.add_partition(2, 2 * 1024 * 1024 * 1024, 'ext4', '/home') sda.add_partition(2, 2 * 1024 * 1024 * 1024, 'ext4', '/home')
print(sda.freespace)
sdb.add_partition(1, 50 * 1024 * 1024 * 1024, 'btrfs', '/opt') sdb.add_partition(1, 50 * 1024 * 1024 * 1024, 'btrfs', '/opt')
get_filesystems([sda, sdb]) get_filesystems([sda, sdb])

View File

@ -134,13 +134,15 @@ class FilesystemModel(ModelPolicy):
def get_disk(self, disk): def get_disk(self, disk):
if disk not in self.devices: 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] return self.devices[disk]
def get_partitions(self): def get_partitions(self):
partitions = [] partitions = []
for dev in self.devices.values(): 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 partitions += partnames
sorted(partitions) sorted(partitions)