async def _resize_disk(self, part_data, encrypted_pool, geli_resize): partition_number = part_data['partition_number'] if osc.IS_LINUX: await run('sgdisk', '-d', str(partition_number), '-n', f'{partition_number}:0:0', '-c', '2:', '-u', f'{partition_number}:{part_data["partition_uuid"]}', '-t', f'{partition_number}:BF01', part_data['path']) await run('partprobe', os.path.join('/dev', part_data['disk'])) else: if not part_data['disk'].startswith('nvd'): await run('camcontrol', 'reprobe', part_data['disk']) await run('gpart', 'recover', part_data['disk']) await run('gpart', 'resize', '-a', '4k', '-i', str(partition_number), part_data['disk']) if osc.IS_FREEBSD and encrypted_pool: geli_resize_cmd = ('geli', 'resize', '-a', '4k', '-s', str(part_data['size']), part_data['name']) rollback_cmd = ('gpart', 'resize', '-a', '4k', '-i', str(partition_number), '-s', str(part_data['size']), part_data['disk']) logger.warning( 'It will be obligatory to notify GELI that the provider has been resized: %r', join_commandline(geli_resize_cmd)) logger.warning('Or to resize provider back: %r', join_commandline(rollback_cmd)) geli_resize.append((geli_resize_cmd, rollback_cmd))
async def __run_rollback_cmd(rollback_cmd): try: await run(*rollback_cmd, encoding='utf-8', errors='ignore') except subprocess.CalledProcessError as rollback_error: logger.critical( '%r failed: %s. To restore your pool functionality you will have to run this command manually.', join_commandline(rollback_cmd), rollback_error.stderr.strip()) return False else: return True
async def __geli_resize(self, pool, geli_resize, options): failed_rollback = [] lock_job = await self.middleware.call('pool.lock', pool['id'], options['geli']['passphrase']) await lock_job.wait() if lock_job.error: logger.warning('Error locking pool: %s', lock_job.error) for geli_resize_cmd, rollback_cmd in geli_resize: if not await self.__run_rollback_cmd(rollback_cmd): failed_rollback.append(rollback_cmd) if failed_rollback: raise CallError( 'Locking your encrypted pool failed and rolling back changes failed too. ' f'You\'ll need to run the following commands manually:\n%s' % '\n'.join(map(join_commandline, failed_rollback))) else: for geli_resize_cmd, rollback_cmd in geli_resize: try: await run(*geli_resize_cmd, encoding='utf-8', errors='ignore') except subprocess.CalledProcessError as geli_resize_error: if geli_resize_error.stderr.strip( ) == 'geli: Size hasn\'t changed.': logger.info('%s: %s', join_commandline(geli_resize_cmd), geli_resize_error.stderr.strip()) else: logger.error('%r failed: %s. Resizing partition back', join_commandline(geli_resize_cmd), geli_resize_error.stderr.strip()) if not await self.__run_rollback_cmd(rollback_cmd): failed_rollback.append(rollback_cmd) if failed_rollback: raise CallError( 'Resizing partitions of your encrypted pool failed and rolling back ' 'changes failed too. You\'ll need to run the following commands manually:\n%s' % '\n'.join(map(join_commandline, failed_rollback))) if options['geli']['passphrase']: unlock_job = await self.middleware.call( 'pool.unlock', pool['id'], {'passphrase': options['geli']['passphrase']}) else: unlock_job = await self.middleware.call( 'pool.unlock', pool['id'], {'recoverykey': True}, pipes=Pipes(input=self.middleware.pipe())) def copy(): with open(pool['encryptkey_path'], 'rb') as f: shutil.copyfileobj(f, unlock_job.pipes.input.w) try: await self.middleware.run_in_thread(copy) finally: await self.middleware.run_in_thread( unlock_job.pipes.input.w.close) await unlock_job.wait() if unlock_job.error: raise CallError(unlock_job.error)
async def expand(self, job, id, options): """ Expand pool to fit all available disk space. """ pool = await self.middleware.call('pool.get_instance', id) if IS_LINUX: if options.get('passphrase'): raise CallError( 'Passphrase should not be supplied for this platform.') # FIXME: We have issues in ZoL where when pool is created with partition uuids, we are unable # to expand pool where all pool related options error out saying I/O error # https://github.com/zfsonlinux/zfs/issues/9830 raise CallError( 'Expand is not supported on this platform yet because of underlying ZFS issues.' ) else: if pool['encrypt']: if not pool['is_decrypted']: raise CallError('You can only expand decrypted pool') for error in (await self.middleware.call( 'pool.pool_lock_pre_check', pool, options['geli']['passphrase'])).errors: raise CallError(error.errmsg) all_partitions = { p['name']: p for p in await self.middleware.call('disk.list_all_partitions') } try: if not IS_LINUX: sysctl.filter('kern.geom.debugflags')[0].value = 16 geli_resize = [] try: for vdev in sum(pool['topology'].values(), []): if vdev['type'] != 'DISK': logger.debug('Not expanding vdev of type %r', vdev['type']) continue if vdev['status'] != 'ONLINE': logger.debug('Not expanding vdev that is %r', vdev['status']) continue part_data = all_partitions.get(vdev['device']) if not part_data: logger.debug('Unable to find partition data for %s', vdev['device']) partition_number = part_data['partition_number'] if not partition_number: logger.debug( 'Could not parse partition number from %r', vdev['device']) continue assert part_data['disk'] == vdev['disk'] if IS_LINUX: await run( 'sgdisk', '-d', str(partition_number), '-n', f'{partition_number}:0:0', '-c', '2:', '-u', f'{partition_number}:{part_data["partition_uuid"]}', '-t', f'{partition_number}:BF01', part_data['path']) await run('partprobe', os.path.join('/dev', part_data['disk'])) else: await run('camcontrol', 'reprobe', vdev['disk']) await run('gpart', 'recover', vdev['disk']) await run('gpart', 'resize', '-i', str(partition_number), vdev['disk']) if not IS_LINUX and pool['encrypt']: geli_resize_cmd = ('geli', 'resize', '-s', str(part_data['size']), vdev['device']) rollback_cmd = ('gpart', 'resize', '-i', str(partition_number), '-s', str(part_data['size']), vdev['disk']) logger.warning( 'It will be obligatory to notify GELI that the provider has been resized: %r', join_commandline(geli_resize_cmd)) logger.warning('Or to resize provider back: %r', join_commandline(rollback_cmd)) geli_resize.append((geli_resize_cmd, rollback_cmd)) finally: if not IS_LINUX and geli_resize: await self.__geli_resize(pool, geli_resize, options) finally: if not IS_LINUX: sysctl.filter('kern.geom.debugflags')[0].value = 0 for vdev in sum(pool['topology'].values(), []): if vdev['type'] != 'DISK' or vdev['status'] != 'ONLINE': continue await self.middleware.call('zfs.pool.online', pool['name'], vdev['guid'], True)