2015-08-10 13:42:19 +00:00
|
|
|
# Copyright 2015 Canonical, Ltd.
|
|
|
|
#
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
|
|
|
#
|
|
|
|
# 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/>.
|
|
|
|
|
|
|
|
import json
|
|
|
|
import logging
|
2015-10-05 15:56:59 +00:00
|
|
|
import re
|
2015-08-10 13:42:19 +00:00
|
|
|
|
2015-10-26 14:57:19 +00:00
|
|
|
from .blockdev import Blockdev, Raiddev, sort_actions
|
2015-08-10 13:42:19 +00:00
|
|
|
import math
|
|
|
|
from subiquity.model import ModelPolicy
|
|
|
|
|
|
|
|
|
2015-08-28 20:30:34 +00:00
|
|
|
HUMAN_UNITS = ['B', 'K', 'M', 'G', 'T', 'P']
|
2015-08-10 13:42:19 +00:00
|
|
|
log = logging.getLogger('subiquity.models.filesystem')
|
|
|
|
|
|
|
|
|
2015-10-02 18:49:10 +00:00
|
|
|
class AttrDict(dict):
|
2015-10-02 00:03:54 +00:00
|
|
|
__getattr__ = dict.__getitem__
|
|
|
|
__setattr__ = dict.__setitem__
|
|
|
|
|
2015-10-02 18:49:10 +00:00
|
|
|
|
2015-08-10 13:42:19 +00:00
|
|
|
class FilesystemModel(ModelPolicy):
|
|
|
|
""" Model representing storage options
|
|
|
|
"""
|
2015-10-29 20:58:51 +00:00
|
|
|
base_signal = 'menu:filesystem:main'
|
2015-08-10 13:42:19 +00:00
|
|
|
signals = [
|
|
|
|
('Filesystem view',
|
2015-10-29 20:58:51 +00:00
|
|
|
base_signal,
|
2015-08-10 13:42:19 +00:00
|
|
|
'filesystem'),
|
2015-10-22 21:12:59 +00:00
|
|
|
('Filesystem error',
|
|
|
|
'filesystem:error',
|
|
|
|
'filesystem_error'),
|
2015-08-10 13:42:19 +00:00
|
|
|
('Filesystem finish',
|
|
|
|
'filesystem:finish',
|
|
|
|
'filesystem_handler'),
|
|
|
|
('Show disk partition view',
|
2015-10-29 20:58:51 +00:00
|
|
|
base_signal + ':show-disk-partition',
|
2015-08-10 13:42:19 +00:00
|
|
|
'disk_partition'),
|
|
|
|
('Finish disk partition',
|
|
|
|
'filesystem:finish-disk-partition',
|
|
|
|
'disk_partition_handler'),
|
|
|
|
('Add disk partition',
|
2015-10-29 20:58:51 +00:00
|
|
|
base_signal + ':add-disk-partition',
|
2015-08-10 13:42:19 +00:00
|
|
|
'add_disk_partition'),
|
|
|
|
('Finish add disk partition',
|
|
|
|
'filesystem:finish-add-disk-partition',
|
|
|
|
'add_disk_partition_handler'),
|
2015-10-26 14:57:19 +00:00
|
|
|
('Finish whole disk format/mount',
|
|
|
|
'filesystem:finish-add-disk-format',
|
|
|
|
'add_disk_format_handler'),
|
2015-08-10 13:42:19 +00:00
|
|
|
('Format or create swap on entire device (unusual, advanced)',
|
2015-10-29 20:58:51 +00:00
|
|
|
base_signal + ':create-swap-entire-device',
|
2015-09-02 19:21:14 +00:00
|
|
|
'create_swap_entire_device'),
|
|
|
|
('Show disk information',
|
2015-10-29 20:58:51 +00:00
|
|
|
base_signal + ':show-disk-information',
|
2015-10-02 00:03:54 +00:00
|
|
|
'show_disk_information'),
|
2015-10-21 21:25:44 +00:00
|
|
|
('Show next disk information',
|
|
|
|
'filesystem:show-disk-info-next',
|
|
|
|
'show_disk_information_next'),
|
|
|
|
('Show prev disk information',
|
|
|
|
'filesystem:show-disk-info-prev',
|
|
|
|
'show_disk_information_prev'),
|
2015-10-02 00:03:54 +00:00
|
|
|
('Add Raid Device',
|
2015-10-29 20:58:51 +00:00
|
|
|
base_signal + ':add-raid-dev',
|
2015-10-02 00:03:54 +00:00
|
|
|
'add_raid_dev'),
|
2015-08-10 13:42:19 +00:00
|
|
|
]
|
|
|
|
|
2015-09-24 12:53:15 +00:00
|
|
|
# TODO: Re-add once curtin supports this.
|
2015-08-10 13:42:19 +00:00
|
|
|
fs_menu = [
|
2015-09-24 12:53:15 +00:00
|
|
|
# ('Connect iSCSI network disk',
|
|
|
|
# 'filesystem:connect-iscsi-disk',
|
|
|
|
# 'connect_iscsi_disk'),
|
|
|
|
# ('Connect Ceph network disk',
|
|
|
|
# 'filesystem:connect-ceph-disk',
|
|
|
|
# 'connect_ceph_disk'),
|
|
|
|
# ('Create volume group (LVM2)',
|
|
|
|
# 'filesystem:create-volume-group',
|
|
|
|
# 'create_volume_group'),
|
2015-10-02 00:03:54 +00:00
|
|
|
('Create software RAID (MD)',
|
2015-10-29 20:58:51 +00:00
|
|
|
base_signal + ':create-raid',
|
2015-10-02 00:03:54 +00:00
|
|
|
'create_raid'),
|
2015-09-24 12:53:15 +00:00
|
|
|
# ('Setup hierarchichal storage (bcache)',
|
|
|
|
# 'filesystem:setup-bcache',
|
|
|
|
# 'setup_bcache')
|
2015-08-10 13:42:19 +00:00
|
|
|
]
|
|
|
|
|
|
|
|
supported_filesystems = [
|
|
|
|
'ext4',
|
|
|
|
'xfs',
|
|
|
|
'btrfs',
|
|
|
|
'swap',
|
|
|
|
'bcache cache',
|
|
|
|
'bcache store',
|
|
|
|
'leave unformatted'
|
|
|
|
]
|
|
|
|
|
2015-10-05 20:09:47 +00:00
|
|
|
partition_flags = [
|
|
|
|
'boot',
|
|
|
|
'lvm',
|
|
|
|
'raid',
|
|
|
|
'bios_grub',
|
|
|
|
]
|
|
|
|
|
2015-10-02 00:03:54 +00:00
|
|
|
# TODO: what is "linear" level?
|
|
|
|
raid_levels = [
|
|
|
|
"0",
|
|
|
|
"1",
|
|
|
|
"5",
|
|
|
|
"6",
|
|
|
|
"10",
|
|
|
|
]
|
|
|
|
|
2015-09-28 16:28:26 +00:00
|
|
|
def __init__(self, prober, opts):
|
|
|
|
self.opts = opts
|
2015-08-28 18:19:13 +00:00
|
|
|
self.prober = prober
|
2015-08-10 13:42:19 +00:00
|
|
|
self.info = {}
|
|
|
|
self.devices = {}
|
2015-10-02 00:03:54 +00:00
|
|
|
self.raid_devices = {}
|
2015-08-28 18:19:13 +00:00
|
|
|
self.storage = {}
|
2015-08-10 13:42:19 +00:00
|
|
|
|
|
|
|
def reset(self):
|
|
|
|
log.debug('FilesystemModel: resetting disks')
|
2015-10-05 15:56:59 +00:00
|
|
|
self.devices = {}
|
2015-11-02 21:19:50 +00:00
|
|
|
self.info = {}
|
2015-08-10 13:42:19 +00:00
|
|
|
|
|
|
|
def get_signal_by_name(self, selection):
|
|
|
|
for x, y, z in self.get_signals():
|
|
|
|
if x == selection:
|
|
|
|
return y
|
|
|
|
|
|
|
|
def get_signals(self):
|
|
|
|
return self.signals + self.fs_menu
|
|
|
|
|
|
|
|
def get_menu(self):
|
|
|
|
return self.fs_menu
|
|
|
|
|
|
|
|
def probe_storage(self):
|
2015-08-28 18:19:13 +00:00
|
|
|
log.debug('model.probe_storage: probing storage')
|
|
|
|
self.storage = self.prober.get_storage()
|
|
|
|
log.debug('got storage:\n{}'.format(self.storage))
|
2015-08-10 13:42:19 +00:00
|
|
|
# TODO: Put this into a logging namespace for probert
|
|
|
|
# since its quite a bit of log information.
|
|
|
|
# log.debug('storage probe data:\n{}'.format(
|
|
|
|
# json.dumps(self.storage, indent=4, sort_keys=True)))
|
|
|
|
|
|
|
|
# TODO: replace this with Storage.get_device_by_match()
|
|
|
|
# which takes a lambda fn for matching
|
|
|
|
VALID_MAJORS = ['8', '253']
|
|
|
|
for disk in self.storage.keys():
|
|
|
|
if self.storage[disk]['DEVTYPE'] == 'disk' and \
|
|
|
|
self.storage[disk]['MAJOR'] in VALID_MAJORS:
|
|
|
|
log.debug('disk={}\n{}'.format(disk,
|
|
|
|
json.dumps(self.storage[disk], indent=4,
|
|
|
|
sort_keys=True)))
|
2015-08-28 18:19:13 +00:00
|
|
|
self.info[disk] = self.prober.get_storage_info(disk)
|
2015-08-10 13:42:19 +00:00
|
|
|
|
|
|
|
def get_disk(self, disk):
|
2015-10-05 15:56:59 +00:00
|
|
|
'''get disk object given path. If provided a partition, then
|
|
|
|
return the parent disk. /dev/sda2 --> /dev/sda obj'''
|
2015-10-02 18:49:10 +00:00
|
|
|
log.debug('probe_storage: get_disk({})'.format(disk))
|
2015-10-05 15:56:59 +00:00
|
|
|
|
2015-08-10 13:42:19 +00:00
|
|
|
if disk not in self.devices:
|
2015-10-05 15:56:59 +00:00
|
|
|
try:
|
|
|
|
self.devices[disk] = Blockdev(disk, self.info[disk].serial,
|
|
|
|
self.info[disk].model,
|
|
|
|
size=self.info[disk].size)
|
|
|
|
except KeyError:
|
|
|
|
''' if it looks like a partition, try again with
|
|
|
|
parent device '''
|
|
|
|
if disk[-1].isdigit():
|
|
|
|
return self.get_disk(re.split('[\d+]', disk)[0])
|
|
|
|
|
2015-08-10 13:42:19 +00:00
|
|
|
return self.devices[disk]
|
|
|
|
|
2015-10-02 18:49:10 +00:00
|
|
|
def get_available_disks(self):
|
|
|
|
''' currently only returns available disks '''
|
|
|
|
disks = [d for d in self.get_all_disks() if d.available]
|
|
|
|
log.debug('get_available_disks -> {}'.format(
|
|
|
|
",".join([d.devpath for d in disks])))
|
|
|
|
return disks
|
|
|
|
|
|
|
|
def get_all_disks(self):
|
2015-10-02 00:03:54 +00:00
|
|
|
possible_devices = list(set(list(self.devices.keys()) +
|
|
|
|
list(self.info.keys())))
|
|
|
|
possible_disks = [self.get_disk(d) for d in sorted(possible_devices)]
|
2015-10-02 18:49:10 +00:00
|
|
|
log.debug('get_all_disks -> {}'.format(",".join([d.devpath for d in
|
|
|
|
possible_disks])))
|
|
|
|
return possible_disks
|
2015-10-02 00:03:54 +00:00
|
|
|
|
|
|
|
def calculate_raid_size(self, raid_level, raid_devices, spare_devices):
|
|
|
|
'''
|
|
|
|
0: array size is the size of the smallest component partition times
|
|
|
|
the number of component partitions
|
|
|
|
1: array size is the size of the smallest component partition
|
|
|
|
5: array size is the size of the smallest component partition times
|
|
|
|
the number of component partitions munus 1
|
|
|
|
6: array size is the size of the smallest component partition times
|
|
|
|
the number of component partitions munus 2
|
|
|
|
'''
|
|
|
|
# https://raid.wiki.kernel.org/ \
|
|
|
|
# index.php/RAID_superblock_formats#Total_Size_of_superblock
|
|
|
|
# Version-1 superblock format on-disk layout:
|
|
|
|
# Total size of superblock: 256 Bytes plus 2 bytes per device in the
|
|
|
|
# array
|
|
|
|
log.debug('calc_raid_size: level={} rd={} sd={}'.format(raid_level,
|
|
|
|
raid_devices,
|
|
|
|
spare_devices))
|
|
|
|
overhead_bytes = 256 + (2 * (len(raid_devices) + len(spare_devices)))
|
|
|
|
log.debug('calc_raid_size: overhead_bytes={}'.format(overhead_bytes))
|
|
|
|
|
|
|
|
# find the smallest device
|
|
|
|
min_dev_size = min([d.size for d in raid_devices])
|
|
|
|
log.debug('calc_raid_size: min_dev_size={}'.format(min_dev_size))
|
|
|
|
|
|
|
|
if raid_level == 0:
|
|
|
|
array_size = min_dev_size * len(raid_devices)
|
|
|
|
elif raid_level == 1:
|
|
|
|
array_size = min_dev_size
|
|
|
|
elif raid_level == 5:
|
|
|
|
array_size = min_dev_size * (len(raid_devices) - 1)
|
|
|
|
elif raid_level == 10:
|
|
|
|
array_size = min_dev_size * int((len(raid_devices) /
|
|
|
|
len(spare_devices)))
|
|
|
|
total_size = array_size - overhead_bytes
|
|
|
|
log.debug('calc_raid_size: array_size:{} - overhead:{} = {}'.format(
|
|
|
|
array_size, overhead_bytes, total_size))
|
|
|
|
return total_size
|
|
|
|
|
|
|
|
def add_raid_device(self, raidspec):
|
|
|
|
# assume raidspec has already been valided in view/controller
|
|
|
|
log.debug('Attempting to create a raid device')
|
|
|
|
'''
|
|
|
|
raidspec = {
|
|
|
|
'devices': ['/dev/sdb 1.819T, HDS5C3020ALA632',
|
|
|
|
'/dev/sdc 1.819T, 001-9YN164',
|
|
|
|
'/dev/sdf 1.819T, 001-9YN164',
|
|
|
|
'/dev/sdg 1.819T, 001-9YN164',
|
|
|
|
'/dev/sdh 1.819T, HDS5C3020ALA632',
|
|
|
|
'/dev/sdi 1.819T, 001-9YN164'],
|
2015-10-26 14:57:19 +00:00
|
|
|
'/dev/sdj 1.819T, Unknown Model'],
|
2015-10-02 00:03:54 +00:00
|
|
|
'raid_level': '0',
|
|
|
|
'hot_spares': '0',
|
|
|
|
'chunk_size': '4K',
|
|
|
|
}
|
2015-10-05 15:56:59 +00:00
|
|
|
could be /dev/sda1, /dev/md0, /dev/bcache1, /dev/vg_foo/foobar2?
|
2015-10-02 00:03:54 +00:00
|
|
|
'''
|
|
|
|
raid_devices = []
|
|
|
|
spare_devices = []
|
|
|
|
all_devices = [r.split() for r in raidspec.get('devices', [])]
|
|
|
|
nr_spares = int(raidspec.get('hot_spares'))
|
|
|
|
|
|
|
|
# XXX: curtin requires a partition table on the base devices
|
|
|
|
# and then one partition of type raid
|
2015-10-26 14:57:19 +00:00
|
|
|
for (devpath, *_) in all_devices:
|
2015-10-02 00:03:54 +00:00
|
|
|
disk = self.get_disk(devpath)
|
2015-10-05 15:56:59 +00:00
|
|
|
|
|
|
|
# add or update a partition to be raid type
|
|
|
|
if disk.path != devpath: # we must have got a partition
|
|
|
|
raiddev = disk.get_partition(devpath)
|
|
|
|
raiddev.flags = 'raid'
|
|
|
|
else:
|
|
|
|
disk.add_partition(1, disk.freespace, None, None, flag='raid')
|
|
|
|
raiddev = disk
|
|
|
|
|
2015-10-02 00:03:54 +00:00
|
|
|
if len(raid_devices) + nr_spares < len(all_devices):
|
2015-10-05 15:56:59 +00:00
|
|
|
raid_devices.append(raiddev)
|
2015-10-02 00:03:54 +00:00
|
|
|
else:
|
2015-10-05 15:56:59 +00:00
|
|
|
spare_devices.append(raiddev)
|
2015-10-02 00:03:54 +00:00
|
|
|
|
|
|
|
# auto increment md number based in registered devices
|
|
|
|
raid_dev_name = '/dev/md{}'.format(len(self.raid_devices))
|
|
|
|
raid_serial = '{}_serial'.format(raid_dev_name)
|
|
|
|
raid_model = '{}_model'.format(raid_dev_name)
|
|
|
|
raid_parttype = 'gpt'
|
|
|
|
raid_level = int(raidspec.get('raid_level'))
|
|
|
|
raid_size = self.calculate_raid_size(raid_level, raid_devices,
|
|
|
|
spare_devices)
|
2015-10-05 15:56:59 +00:00
|
|
|
|
2015-10-02 00:03:54 +00:00
|
|
|
# create a Raiddev (pass in only the names)
|
2015-10-26 14:57:19 +00:00
|
|
|
raid_parts = []
|
|
|
|
for dev in raid_devices:
|
|
|
|
for num, action in dev.partitions.items():
|
|
|
|
raid_parts.append(action.action_id)
|
|
|
|
spare_parts = []
|
|
|
|
for dev in spare_devices:
|
|
|
|
for num, action in dev.partitions.items():
|
|
|
|
spare_parts.append(action.action_id)
|
|
|
|
|
2015-10-02 00:03:54 +00:00
|
|
|
raid_dev = Raiddev(raid_dev_name, raid_serial, raid_model,
|
|
|
|
raid_parttype, raid_size,
|
2015-10-26 14:57:19 +00:00
|
|
|
raid_parts,
|
2015-10-02 00:03:54 +00:00
|
|
|
raid_level,
|
2015-10-26 14:57:19 +00:00
|
|
|
spare_parts)
|
2015-10-02 00:03:54 +00:00
|
|
|
|
|
|
|
# add it to the model's info dict
|
|
|
|
raid_dev_info = {
|
|
|
|
'type': 'disk',
|
|
|
|
'name': raid_dev_name,
|
|
|
|
'size': raid_size,
|
|
|
|
'serial': raid_serial,
|
|
|
|
'vendor': 'Linux Software RAID',
|
|
|
|
'model': raid_model,
|
|
|
|
'is_virtual': True,
|
2015-11-02 19:39:09 +00:00
|
|
|
'raw': {
|
|
|
|
'MAJOR': '9',
|
|
|
|
},
|
2015-10-02 00:03:54 +00:00
|
|
|
}
|
|
|
|
self.info[raid_dev_name] = AttrDict(raid_dev_info)
|
|
|
|
|
|
|
|
# add it to the model's raid devices
|
|
|
|
self.raid_devices[raid_dev_name] = raid_dev
|
|
|
|
# add it to the model's devices
|
|
|
|
self.add_device(raid_dev_name, raid_dev)
|
|
|
|
|
|
|
|
log.debug('Successfully added raid_dev: {}'.format(raid_dev))
|
|
|
|
|
|
|
|
def add_device(self, devpath, device):
|
|
|
|
log.debug("adding device: {} = {}".format(devpath, device))
|
|
|
|
self.devices[devpath] = device
|
2015-09-28 16:28:26 +00:00
|
|
|
|
2015-08-10 13:42:19 +00:00
|
|
|
def get_partitions(self):
|
2015-08-28 18:19:13 +00:00
|
|
|
log.debug('probe_storage: get_partitions()')
|
2015-08-10 13:42:19 +00:00
|
|
|
partitions = []
|
|
|
|
for dev in self.devices.values():
|
2015-10-07 20:24:19 +00:00
|
|
|
partnames = [part.devpath for (num, part) in
|
2015-08-27 19:26:37 +00:00
|
|
|
dev.disk.partitions.items()]
|
2015-08-10 13:42:19 +00:00
|
|
|
partitions += partnames
|
|
|
|
|
2015-10-07 21:34:57 +00:00
|
|
|
partitions = sorted(partitions)
|
2015-09-01 16:28:23 +00:00
|
|
|
log.debug('probe_storage: get_partitions() returns: {}'.format(
|
|
|
|
partitions))
|
2015-08-10 13:42:19 +00:00
|
|
|
return partitions
|
|
|
|
|
2015-10-26 14:57:19 +00:00
|
|
|
def get_filesystems(self):
|
|
|
|
log.debug('get_fs')
|
|
|
|
fs = []
|
|
|
|
for dev in self.devices.values():
|
|
|
|
fs += dev.filesystems
|
|
|
|
|
|
|
|
return fs
|
|
|
|
|
2015-10-02 00:03:54 +00:00
|
|
|
def installable(self):
|
|
|
|
''' one or more disks has used space
|
|
|
|
and has "/" as a mount
|
|
|
|
'''
|
2015-10-02 18:49:10 +00:00
|
|
|
for disk in self.get_all_disks():
|
2015-10-02 00:03:54 +00:00
|
|
|
if disk.usedspace > 0 and "/" in disk.mounts:
|
|
|
|
return True
|
|
|
|
|
2015-10-07 22:11:10 +00:00
|
|
|
return False
|
|
|
|
|
2015-10-02 18:49:10 +00:00
|
|
|
def bootable(self):
|
|
|
|
''' true if one disk has a boot partition '''
|
|
|
|
log.debug('bootable check')
|
|
|
|
for disk in self.get_all_disks():
|
|
|
|
for (num, action) in disk.partitions.items():
|
|
|
|
if action.flags in ['bios_grub']:
|
|
|
|
log.debug('bootable check: we\'ve got boot!')
|
|
|
|
return True
|
2015-08-10 13:42:19 +00:00
|
|
|
|
2015-10-02 18:49:10 +00:00
|
|
|
log.debug('bootable check: no disks have been marked bootable')
|
|
|
|
return False
|
|
|
|
|
2015-11-02 17:43:33 +00:00
|
|
|
def valid_mount(self, format_spec):
|
|
|
|
""" format spec
|
|
|
|
|
|
|
|
{
|
|
|
|
'format' Str(ext4|btrfs..,
|
|
|
|
'mountpoint': Str
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
mountpoint = format_spec.get('mountpoint')
|
|
|
|
if not mountpoint:
|
|
|
|
raise ValueError('Is None')
|
|
|
|
|
|
|
|
# If the value is in error state, return the
|
|
|
|
# the same error
|
|
|
|
err_prefix = 'error: '
|
|
|
|
if mountpoint.lower().startswith(err_prefix):
|
|
|
|
mountpoint = mountpoint[len(err_prefix):]
|
|
|
|
raise ValueError(mountpoint)
|
|
|
|
|
|
|
|
if not mountpoint.startswith('/'):
|
|
|
|
raise ValueError('Does not start with /')
|
|
|
|
|
2015-11-02 18:06:51 +00:00
|
|
|
# /usr/include/linux/limits.h:PATH_MAX
|
|
|
|
if len(mountpoint) > 4095:
|
|
|
|
raise ValueError('Path exceeds PATH_MAX')
|
|
|
|
|
2015-11-02 17:43:33 +00:00
|
|
|
all_mounts = self.get_mounts()
|
|
|
|
if mountpoint in all_mounts:
|
|
|
|
raise ValueError('Already mounted')
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
2015-10-05 15:56:59 +00:00
|
|
|
def get_empty_disks(self):
|
|
|
|
''' empty disk is one that does not have any
|
|
|
|
partitions, filesystems or mounts, and is non-zero size '''
|
|
|
|
empty = []
|
|
|
|
for dev in self.get_available_disks():
|
|
|
|
if len(dev.partitions) == 0 and \
|
|
|
|
len(dev.mounts) == 0 and \
|
|
|
|
len(dev.filesystems) == 0:
|
|
|
|
empty.append(dev)
|
2015-11-02 22:48:37 +00:00
|
|
|
log.debug('empty_disks: {}'.format(
|
|
|
|
", ".join([dev.path for dev in empty])))
|
2015-10-05 15:56:59 +00:00
|
|
|
return empty
|
|
|
|
|
|
|
|
def get_empty_disk_names(self):
|
|
|
|
return [dev.disk.devpath for dev in self.get_empty_disks()]
|
|
|
|
|
|
|
|
def get_empty_partition_names(self):
|
|
|
|
''' empty partitions have non-zero size, but are not part
|
|
|
|
of a filesystem or mount point or other raid '''
|
|
|
|
empty = []
|
|
|
|
for dev in self.get_available_disks():
|
|
|
|
empty += dev.available_partitions
|
|
|
|
|
|
|
|
log.debug('empty_partitions: {}'.format(", ".join(empty)))
|
|
|
|
return empty
|
|
|
|
|
2015-10-02 18:49:10 +00:00
|
|
|
def get_available_disk_names(self):
|
|
|
|
return [dev.disk.devpath for dev in self.get_available_disks()]
|
|
|
|
|
|
|
|
def get_used_disk_names(self):
|
|
|
|
return [dev.disk.devpath for dev in self.get_all_disks()
|
2015-08-10 13:42:19 +00:00
|
|
|
if dev.available is False]
|
|
|
|
|
|
|
|
def get_disk_info(self, disk):
|
2015-10-02 00:03:54 +00:00
|
|
|
return self.info.get(disk, {})
|
2015-08-10 13:42:19 +00:00
|
|
|
|
2015-10-02 18:49:10 +00:00
|
|
|
def get_mounts(self):
|
|
|
|
mounts = []
|
|
|
|
for dev in self.get_all_disks():
|
|
|
|
mounts += dev.mounts
|
|
|
|
|
|
|
|
return mounts
|
|
|
|
|
2015-08-10 13:42:19 +00:00
|
|
|
def get_disk_action(self, disk):
|
|
|
|
return self.devices[disk].get_actions()
|
|
|
|
|
|
|
|
def get_actions(self):
|
|
|
|
actions = []
|
|
|
|
for dev in self.devices.values():
|
|
|
|
actions += dev.get_actions()
|
2015-10-26 14:57:19 +00:00
|
|
|
|
|
|
|
log.debug('****')
|
|
|
|
log.debug('all actions:{}'.format(actions))
|
|
|
|
log.debug('****')
|
|
|
|
return sort_actions(actions)
|
2015-08-10 13:42:19 +00:00
|
|
|
|
|
|
|
|
|
|
|
def _humanize_size(size):
|
|
|
|
size = abs(size)
|
|
|
|
if size == 0:
|
|
|
|
return "0B"
|
|
|
|
p = math.floor(math.log(size, 2) / 10)
|
2015-08-25 16:49:27 +00:00
|
|
|
return "%.3f%s" % (size / math.pow(1024, p), HUMAN_UNITS[int(p)])
|
2015-08-10 13:42:19 +00:00
|
|
|
|
|
|
|
|
|
|
|
def _dehumanize_size(size):
|
|
|
|
# convert human 'size' to integer
|
|
|
|
size_in = size
|
|
|
|
if size.endswith("B"):
|
|
|
|
size = size[:-1]
|
|
|
|
|
2015-08-28 20:30:34 +00:00
|
|
|
# build mpliers based on HUMAN_UNITS
|
|
|
|
mpliers = {}
|
|
|
|
for (unit, exponent) in zip(HUMAN_UNITS, range(0, len(HUMAN_UNITS))):
|
|
|
|
mpliers.update({unit: 2 ** (exponent * 10)})
|
2015-08-10 13:42:19 +00:00
|
|
|
|
|
|
|
num = size
|
|
|
|
mplier = 'B'
|
|
|
|
for m in mpliers:
|
|
|
|
if size.endswith(m):
|
|
|
|
mplier = m
|
|
|
|
num = size[0:-len(m)]
|
|
|
|
|
|
|
|
try:
|
|
|
|
num = float(num)
|
|
|
|
except ValueError:
|
|
|
|
raise ValueError("'{}' is not valid input.".format(size_in))
|
|
|
|
|
|
|
|
if num < 0:
|
|
|
|
raise ValueError("'{}': cannot be negative".format(size_in))
|
|
|
|
|
|
|
|
return int(num * mpliers[mplier])
|