def put(self, request, pname, command): """ resize a pool. @pname: pool's name @command: 'add' - add a list of disks and hence expand the pool 'remove' - remove a list of disks and hence shrink the pool """ try: if (not Pool.objects.filter(name=pname).exists()): msg = ('pool: %s does not exist' % pname) raise Exception(msg) disks = request.DATA['disks'].split(',') if (len(disks) == 0): msg = ('list of disks in the input is empty') raise Exception(msg) pool = Pool.objects.get(name=pname) mount_disk = Disk.objects.filter(pool=pool)[0].name if (command == 'add'): for d in disks: d_o = Disk.objects.get(name=d) if (d_o.pool is not None): msg = ('disk %s already part of pool %s' % (d, d_o.pool.name)) raise Exception(msg) d_o.pool = pool d_o.save() resize_pool(pool.name, mount_disk, disks) elif (command == 'remove'): if (len(Disk.objects.filter(pool=pool)) == 1): msg = ( 'pool %s had only one disk. use delete command instead' ) raise Exception(msg) for d in disks: d_o = Disk.objects.get(name=d) if (d_o.pool != pool): msg = ('disk %s not part of pool %s' % (d, d_o.pool.name)) raise Exception(msg) d_o.pool = None d_o.save() mount_disk = Disk.objects.filter(pool=pool)[0].name resize_pool(pool.name, mount_disk, disks, add=False) else: msg = ('unknown command: %s' % command) raise Exception(msg) usage = pool_usage(mount_disk) pool.size = usage[0] pool.save() return Response(PoolInfoSerializer(pool).data) except Exception, e: handle_exception(e, request)
def put(self, request, pname, command): """ resize a pool. @pname: pool's name @command: 'add' - add a list of disks and hence expand the pool 'remove' - remove a list of disks and hence shrink the pool """ try: if (not Pool.objects.filter(name=pname).exists()): msg = ('pool: %s does not exist' % pname) raise Exception(msg) disks = request.DATA['disks'].split(',') if (len(disks) == 0): msg = ('list of disks in the input is empty') raise Exception(msg) pool = Pool.objects.get(name=pname) mount_disk = Disk.objects.filter(pool=pool)[0].name if (command == 'add'): for d in disks: d_o = Disk.objects.get(name=d) if (d_o.pool is not None): msg = ('disk %s already part of pool %s' % (d, d_o.pool.name)) raise Exception(msg) d_o.pool = pool d_o.save() resize_pool(pool.name, mount_disk, disks) elif (command == 'remove'): if (len(Disk.objects.filter(pool=pool)) == 1): msg = ('pool %s had only one disk. use delete command instead') raise Exception(msg) for d in disks: d_o = Disk.objects.get(name=d) if (d_o.pool != pool): msg = ('disk %s not part of pool %s' % (d, d_o.pool.name)) raise Exception(msg) d_o.pool = None d_o.save() mount_disk = Disk.objects.filter(pool=pool)[0].name resize_pool(pool.name, mount_disk, disks, add=False) else: msg = ('unknown command: %s' % command) raise Exception(msg) usage = pool_usage(mount_disk) pool.size = usage[0] pool.save() return Response(PoolInfoSerializer(pool).data) except Exception, e: handle_exception(e, request)
def put(self, request, pname, command): """ resize a pool. @pname: pool's name @command: 'add' - add a list of disks and hence expand the pool 'remove' - remove a list of disks and hence shrink the pool """ with self._handle_exception(request): try: pool = Pool.objects.get(name=pname) except: e_msg = ('Pool(%s) does not exist.' % pname) handle_exception(Exception(e_msg), request) if (pool.role == 'root'): e_msg = ('Edit operations are not allowed on this Pool(%s) ' 'as it contains the operating system.' % pname) handle_exception(Exception(e_msg), request) if (command == 'remount'): return self._remount(request, pool) disks = [self._validate_disk(d, request) for d in request.data.get('disks', [])] num_new_disks = len(disks) dnames = [d.name for d in disks] new_raid = request.data.get('raid_level', pool.raid) num_total_disks = (Disk.objects.filter(pool=pool).count() + num_new_disks) if (command == 'add'): for d in disks: if (d.pool is not None): e_msg = ('Disk(%s) cannot be added to this Pool(%s) ' 'because it belongs to another pool(%s)' % (d.name, pool.name, d.pool.name)) handle_exception(Exception(e_msg), request) if (d.btrfs_uuid is not None): e_msg = ('Disk(%s) has a BTRFS filesystem from the ' 'past. If you really like to add it, wipe it ' 'from the Storage -> Disks screen of the ' 'web-ui' % d.name) handle_exception(Exception(e_msg), request) if (new_raid == 'single'): e_msg = ('Pool migration from %s to %s is not supported.' % (pool.raid, new_raid)) handle_exception(Exception(e_msg), request) if (new_raid == 'raid10' and num_total_disks < 4): e_msg = ('A minimum of Four drives are required for the ' 'raid level: raid10') handle_exception(Exception(e_msg), request) if (new_raid == 'raid6' and num_total_disks < 3): e_msg = ('A minimum of Three drives are required for the ' 'raid level: raid6') handle_exception(Exception(e_msg), request) if (new_raid == 'raid5' and num_total_disks < 2): e_msg == ('A minimum of Two drives are required for the ' 'raid level: raid5') handle_exception(Exception(e_msg), request) if (PoolBalance.objects.filter( pool=pool, status__regex=r'(started|running)').exists()): e_msg = ('A Balance process is already running for this ' 'pool(%s). Resize is not supported during a ' 'balance process.' % pool.name) handle_exception(Exception(e_msg), request) resize_pool(pool, dnames) tid = self._balance_start(pool, convert=new_raid) ps = PoolBalance(pool=pool, tid=tid) ps.save() pool.raid = new_raid for d_o in disks: d_o.pool = pool d_o.save() elif (command == 'remove'): if (new_raid != pool.raid): e_msg = ('Raid configuration cannot be changed while ' 'removing disks') handle_exception(Exception(e_msg), request) for d in disks: if (d.pool is None or d.pool != pool): e_msg = ('Disk(%s) cannot be removed because it does ' 'not belong to this Pool(%s)' % (d.name, pool.name)) handle_exception(Exception(e_msg), request) remaining_disks = (Disk.objects.filter(pool=pool).count() - num_new_disks) if (pool.raid in ('raid0', 'single',)): e_msg = ('Disks cannot be removed from a pool with this ' 'raid(%s) configuration' % pool.raid) handle_exception(Exception(e_msg), request) if (pool.raid == 'raid1' and remaining_disks < 2): e_msg = ('Disks cannot be removed from this pool ' 'because its raid configuration(raid1) ' 'requires a minimum of 2 disks') handle_exception(Exception(e_msg), request) if (pool.raid == 'raid10' and remaining_disks < 4): e_msg = ('Disks cannot be removed from this pool ' 'because its raid configuration(raid10) ' 'requires a minimum of 4 disks') handle_exception(Exception(e_msg), request) if (pool.raid == 'raid5' and remaining_disks < 2): e_msg = ('Disks cannot be removed from this pool because ' 'its raid configuration(raid5) requires a ' 'minimum of 2 disks') handle_exception(Exception(e_msg), request) if (pool.raid == 'raid6' and remaining_disks < 3): e_msg = ('Disks cannot be removed from this pool because ' 'its raid configuration(raid6) requires a ' 'minimum of 3 disks') handle_exception(Exception(e_msg), request) usage = pool_usage('/%s/%s' % (settings.MNT_PT, pool.name)) size_cut = 0 for d in disks: size_cut += d.size if (size_cut >= usage[2]): e_msg = ('Removing these(%s) disks may shrink the pool by ' '%dKB, which is greater than available free space' ' %dKB. This is not supported.' % (dnames, size_cut, usage[2])) handle_exception(Exception(e_msg), request) resize_pool(pool, dnames, add=False) tid = self._balance_start(pool) ps = PoolBalance(pool=pool, tid=tid) ps.save() for d in disks: d.pool = None d.save() else: e_msg = ('command(%s) is not supported.' % command) handle_exception(Exception(e_msg), request) usage = pool_usage('/%s/%s' % (settings.MNT_PT, pool.name)) pool.size = usage[0] pool.save() return Response(PoolInfoSerializer(pool).data)
def put(self, request, pid, command): """ resize a pool. @pname: pool's name @command: 'add' - add a list of disks and hence expand the pool 'remove' - remove a list of disks and hence shrink the pool """ with self._handle_exception(request): try: pool = Pool.objects.get(id=pid) except: e_msg = ('Pool(%d) does not exist.' % pid) handle_exception(Exception(e_msg), request) if (pool.role == 'root'): e_msg = ('Edit operations are not allowed on this Pool(%d) ' 'as it contains the operating system.' % pid) handle_exception(Exception(e_msg), request) if (command == 'remount'): return self._remount(request, pool) disks = [ self._validate_disk(d, request) for d in request.data.get('disks', []) ] num_new_disks = len(disks) dnames = self._role_filter_disk_names(disks, request) new_raid = request.data.get('raid_level', pool.raid) num_total_disks = (Disk.objects.filter(pool=pool).count() + num_new_disks) if (command == 'add'): for d in disks: if (d.pool is not None): e_msg = ('Disk(%s) cannot be added to this Pool(%s) ' 'because it belongs to another pool(%s)' % (d.name, pool.name, d.pool.name)) handle_exception(Exception(e_msg), request) if (d.btrfs_uuid is not None): e_msg = ('Disk(%s) has a BTRFS filesystem from the ' 'past. If you really like to add it, wipe it ' 'from the Storage -> Disks screen of the ' 'web-ui' % d.name) handle_exception(Exception(e_msg), request) if (pool.raid != 'single' and new_raid == 'single'): e_msg = ('Pool migration from %s to %s is not supported.' % (pool.raid, new_raid)) handle_exception(Exception(e_msg), request) if (new_raid == 'raid10' and num_total_disks < 4): e_msg = ('A minimum of Four drives are required for the ' 'raid level: raid10') handle_exception(Exception(e_msg), request) if (new_raid == 'raid6' and num_total_disks < 3): e_msg = ('A minimum of Three drives are required for the ' 'raid level: raid6') handle_exception(Exception(e_msg), request) if (new_raid == 'raid5' and num_total_disks < 2): e_msg = ('A minimum of Two drives are required for the ' 'raid level: raid5') handle_exception(Exception(e_msg), request) if (PoolBalance.objects.filter( pool=pool, status__regex= r'(started|running|cancelling|pausing|paused)').exists( )): # noqa E501 e_msg = ('A Balance process is already running or paused ' 'for this pool(%s). Resize is not supported ' 'during a balance process.' % pool.name) handle_exception(Exception(e_msg), request) resize_pool(pool, dnames) tid = self._balance_start(pool, convert=new_raid) ps = PoolBalance(pool=pool, tid=tid) ps.save() pool.raid = new_raid for d_o in disks: d_o.pool = pool d_o.save() # Now we ensure udev info is updated via system wide trigger trigger_udev_update() elif (command == 'remove'): if (new_raid != pool.raid): e_msg = ('Raid configuration cannot be changed while ' 'removing disks') handle_exception(Exception(e_msg), request) for d in disks: if (d.pool is None or d.pool != pool): e_msg = ('Disk(%s) cannot be removed because it does ' 'not belong to this Pool(%s)' % (d.name, pool.name)) handle_exception(Exception(e_msg), request) remaining_disks = (Disk.objects.filter(pool=pool).count() - num_new_disks) if (pool.raid == 'raid0'): e_msg = ('Disks cannot be removed from a pool with this ' 'raid(%s) configuration' % pool.raid) handle_exception(Exception(e_msg), request) if (pool.raid == 'raid1' and remaining_disks < 2): e_msg = ('Disks cannot be removed from this pool ' 'because its raid configuration(raid1) ' 'requires a minimum of 2 disks') handle_exception(Exception(e_msg), request) if (pool.raid == 'raid10' and remaining_disks < 4): e_msg = ('Disks cannot be removed from this pool ' 'because its raid configuration(raid10) ' 'requires a minimum of 4 disks') handle_exception(Exception(e_msg), request) if (pool.raid == 'raid5' and remaining_disks < 2): e_msg = ('Disks cannot be removed from this pool because ' 'its raid configuration(raid5) requires a ' 'minimum of 2 disks') handle_exception(Exception(e_msg), request) if (pool.raid == 'raid6' and remaining_disks < 3): e_msg = ('Disks cannot be removed from this pool because ' 'its raid configuration(raid6) requires a ' 'minimum of 3 disks') handle_exception(Exception(e_msg), request) usage = pool_usage('/%s/%s' % (settings.MNT_PT, pool.name)) size_cut = 0 for d in disks: size_cut += d.size if size_cut >= (pool.size - usage): e_msg = ('Removing these(%s) disks may shrink the pool by ' '%dKB, which is greater than available free space' ' %dKB. This is not supported.' % (dnames, size_cut, usage)) handle_exception(Exception(e_msg), request) resize_pool(pool, dnames, add=False) tid = self._balance_start(pool) ps = PoolBalance(pool=pool, tid=tid) ps.save() for d in disks: d.pool = None d.save() else: e_msg = ('command(%s) is not supported.' % command) handle_exception(Exception(e_msg), request) pool.size = pool.usage_bound() pool.save() return Response(PoolInfoSerializer(pool).data)
def put(self, request, pid, command): """ resize a pool. @pname: pool's name @command: 'add' - add a list of disks and hence expand the pool 'remove' - remove a list of disks and hence shrink the pool 'remount' - remount the pool, to apply changed mount options 'quotas' - request pool quota setting change """ with self._handle_exception(request): try: pool = Pool.objects.get(id=pid) except: e_msg = 'Pool with id ({}) does not exist.'.format(pid) handle_exception(Exception(e_msg), request) if (pool.role == 'root' and command != 'quotas'): e_msg = ('Edit operations are not allowed on this pool ({}) ' 'as it contains the operating ' 'system.').format(pool.name) handle_exception(Exception(e_msg), request) if (command == 'remount'): return self._remount(request, pool) if (command == 'quotas'): # There is a pending btrfs change that allows for quota state # change on unmounted Volumes (pools). return self._quotas(request, pool) if not pool.is_mounted: e_msg = ('Pool member / raid edits require an active mount. ' 'Please see the "Maintenance required" section.') handle_exception(Exception(e_msg), request) if command == 'remove' and \ request.data.get('disks', []) == ['missing']: disks = [] logger.debug('Remove missing request skipping disk validation') else: disks = [ self._validate_disk_id(diskId, request) for diskId in request.data.get('disks', []) ] num_disks_selected = len(disks) dnames = self._role_filter_disk_names(disks, request) new_raid = request.data.get('raid_level', pool.raid) if (command == 'add'): # Only attached disks can be selected during an add operation. num_total_attached_disks = pool.disk_set.attached().count() \ + num_disks_selected for d in disks: if (d.pool is not None): e_msg = ('Disk ({}) cannot be added to this pool ({}) ' 'because it belongs to another pool ({})' '.').format(d.name, pool.name, d.pool.name) handle_exception(Exception(e_msg), request) if (d.btrfs_uuid is not None): e_msg = ('Disk ({}) has a BTRFS filesystem from the ' 'past. If you really like to add it, wipe it ' 'from the Storage -> Disks screen of the ' 'web-ui.').format(d.name) handle_exception(Exception(e_msg), request) if pool.raid == 'single' and new_raid == 'raid10': # TODO: Consider removing once we have better space calc. # Avoid extreme raid level change upwards (space issues). e_msg = ('Pool migration from {} to {} is not ' 'supported.').format(pool.raid, new_raid) handle_exception(Exception(e_msg), request) if (new_raid == 'raid10' and num_total_attached_disks < 4): e_msg = ('A minimum of 4 drives are required for the ' 'raid level: raid10.') handle_exception(Exception(e_msg), request) if (new_raid == 'raid6' and num_total_attached_disks < 3): e_msg = ('A minimum of 3 drives are required for the ' 'raid level: raid6.') handle_exception(Exception(e_msg), request) if (new_raid == 'raid5' and num_total_attached_disks < 2): e_msg = ('A minimum of 2 drives are required for the ' 'raid level: raid5.') handle_exception(Exception(e_msg), request) if (PoolBalance.objects.filter( pool=pool, status__regex= r'(started|running|cancelling|pausing|paused)').exists( )): # noqa E501 e_msg = ('A Balance process is already running or paused ' 'for this pool ({}). Resize is not supported ' 'during a balance process.').format(pool.name) handle_exception(Exception(e_msg), request) # TODO: run resize_pool() as async task like start_balance() resize_pool(pool, dnames) # None if no action force = False # During dev add we also offer raid level change, if selected # blanket apply '-f' to allow for reducing metadata integrity. if new_raid != pool.raid: force = True tid = self._balance_start(pool, force=force, convert=new_raid) ps = PoolBalance(pool=pool, tid=tid) ps.save() pool.raid = new_raid for d_o in disks: d_o.pool = pool d_o.save() # Now we ensure udev info is updated via system wide trigger trigger_udev_update() elif (command == 'remove'): if (new_raid != pool.raid): e_msg = ('Raid configuration cannot be changed while ' 'removing disks.') handle_exception(Exception(e_msg), request) detached_disks_selected = 0 for d in disks: # to be removed if (d.pool is None or d.pool != pool): e_msg = ('Disk ({}) cannot be removed because it does ' 'not belong to this ' 'pool ({}).').format(d.name, pool.name) handle_exception(Exception(e_msg), request) if re.match('detached-', d.name) is not None: detached_disks_selected += 1 if detached_disks_selected >= 3: # Artificial constraint but no current btrfs raid level yet # allows for > 2 dev detached and we have a mounted vol. e_msg = ('We currently only support removing two' 'detached disks at a time.') handle_exception(Exception(e_msg), request) attached_disks_selected = (num_disks_selected - detached_disks_selected) remaining_attached_disks = (pool.disk_set.attached().count() - attached_disks_selected) if (pool.raid == 'raid0'): e_msg = ('Disks cannot be removed from a pool with this ' 'raid ({}) configuration.').format(pool.raid) handle_exception(Exception(e_msg), request) if (pool.raid == 'raid1' and remaining_attached_disks < 2): e_msg = ('Disks cannot be removed from this pool ' 'because its raid configuration (raid1) ' 'requires a minimum of 2 disks.') handle_exception(Exception(e_msg), request) if (pool.raid == 'raid10' and remaining_attached_disks < 4): e_msg = ('Disks cannot be removed from this pool ' 'because its raid configuration (raid10) ' 'requires a minimum of 4 disks.') handle_exception(Exception(e_msg), request) if (pool.raid == 'raid5' and remaining_attached_disks < 2): e_msg = ('Disks cannot be removed from this pool because ' 'its raid configuration (raid5) requires a ' 'minimum of 2 disks.') handle_exception(Exception(e_msg), request) if (pool.raid == 'raid6' and remaining_attached_disks < 3): e_msg = ('Disks cannot be removed from this pool because ' 'its raid configuration (raid6) requires a ' 'minimum of 3 disks.') handle_exception(Exception(e_msg), request) usage = pool_usage('/%s/%s' % (settings.MNT_PT, pool.name)) size_cut = 0 for d in disks: size_cut += d.size if size_cut >= (pool.size - usage): e_msg = ('Removing disks ({}) may shrink the pool by ' '{} KB, which is greater than available free ' 'space {} KB. This is ' 'not supported.').format(dnames, size_cut, usage) handle_exception(Exception(e_msg), request) # TODO: run resize_pool() as async task like start_balance(), # particularly important on device delete as it initiates an # internal volume balance which cannot be monitored by: # btrfs balance status. # See https://github.com/rockstor/rockstor-core/issues/1722 # Hence we need also to add a 'DIY' status / percentage # reporting method. resize_pool(pool, dnames, add=False) # None if no action # Unlike resize_pool() with add=True a delete has an implicit # balance where the deleted disks contents are re-distributed # across the remaining disks. for d in disks: d.pool = None d.save() else: e_msg = 'Command ({}) is not supported.'.format(command) handle_exception(Exception(e_msg), request) pool.size = pool.usage_bound() pool.save() return Response(PoolInfoSerializer(pool).data)
def put(self, request, pid, command): """ resize a pool. @pname: pool's name @command: 'add' - add a list of disks and hence expand the pool 'remove' - remove a list of disks and hence shrink the pool """ with self._handle_exception(request): try: pool = Pool.objects.get(id=pid) except: e_msg = 'Pool with id ({}) does not exist.'.format(pid) handle_exception(Exception(e_msg), request) if (pool.role == 'root' and command != 'quotas'): e_msg = ('Edit operations are not allowed on this pool ({}) ' 'as it contains the operating ' 'system.').format(pool.name) handle_exception(Exception(e_msg), request) if (command == 'remount'): return self._remount(request, pool) if (command == 'quotas'): return self._quotas(request, pool) disks = [ self._validate_disk(d, request) for d in request.data.get('disks', []) ] num_new_disks = len(disks) dnames = self._role_filter_disk_names(disks, request) new_raid = request.data.get('raid_level', pool.raid) num_total_disks = (Disk.objects.filter(pool=pool).count() + num_new_disks) if (command == 'add'): for d in disks: if (d.pool is not None): e_msg = ('Disk ({}) cannot be added to this pool ({}) ' 'because it belongs to another pool ({})' '.').format(d.name, pool.name, d.pool.name) handle_exception(Exception(e_msg), request) if (d.btrfs_uuid is not None): e_msg = ('Disk ({}) has a BTRFS filesystem from the ' 'past. If you really like to add it, wipe it ' 'from the Storage -> Disks screen of the ' 'web-ui.').format(d.name) handle_exception(Exception(e_msg), request) if pool.raid == 'single' and new_raid == 'raid10': # TODO: Consider removing once we have better space calc. # Avoid extreme raid level change upwards (space issues). e_msg = ('Pool migration from {} to {} is not ' 'supported.').format(pool.raid, new_raid) handle_exception(Exception(e_msg), request) if (new_raid == 'raid10' and num_total_disks < 4): e_msg = ('A minimum of 4 drives are required for the ' 'raid level: raid10.') handle_exception(Exception(e_msg), request) if (new_raid == 'raid6' and num_total_disks < 3): e_msg = ('A minimum of 3 drives are required for the ' 'raid level: raid6.') handle_exception(Exception(e_msg), request) if (new_raid == 'raid5' and num_total_disks < 2): e_msg = ('A minimum of 2 drives are required for the ' 'raid level: raid5.') handle_exception(Exception(e_msg), request) if (PoolBalance.objects.filter( pool=pool, status__regex= r'(started|running|cancelling|pausing|paused)').exists( )): # noqa E501 e_msg = ('A Balance process is already running or paused ' 'for this pool ({}). Resize is not supported ' 'during a balance process.').format(pool.name) handle_exception(Exception(e_msg), request) resize_pool(pool, dnames) # During dev add we also offer raid level change, if selected # blanket apply '-f' to allow for reducing metadata integrity. force = False if new_raid != pool.raid: force = True tid = self._balance_start(pool, force=force, convert=new_raid) ps = PoolBalance(pool=pool, tid=tid) ps.save() pool.raid = new_raid for d_o in disks: d_o.pool = pool d_o.save() # Now we ensure udev info is updated via system wide trigger trigger_udev_update() elif (command == 'remove'): if (new_raid != pool.raid): e_msg = ('Raid configuration cannot be changed while ' 'removing disks.') handle_exception(Exception(e_msg), request) for d in disks: if (d.pool is None or d.pool != pool): e_msg = ('Disk ({}) cannot be removed because it does ' 'not belong to this ' 'pool ({}).').format(d.name, pool.name) handle_exception(Exception(e_msg), request) remaining_disks = (Disk.objects.filter(pool=pool).count() - num_new_disks) if (pool.raid == 'raid0'): e_msg = ('Disks cannot be removed from a pool with this ' 'raid ({}) configuration.').format(pool.raid) handle_exception(Exception(e_msg), request) if (pool.raid == 'raid1' and remaining_disks < 2): e_msg = ('Disks cannot be removed from this pool ' 'because its raid configuration (raid1) ' 'requires a minimum of 2 disks.') handle_exception(Exception(e_msg), request) if (pool.raid == 'raid10' and remaining_disks < 4): e_msg = ('Disks cannot be removed from this pool ' 'because its raid configuration (raid10) ' 'requires a minimum of 4 disks.') handle_exception(Exception(e_msg), request) if (pool.raid == 'raid5' and remaining_disks < 2): e_msg = ('Disks cannot be removed from this pool because ' 'its raid configuration (raid5) requires a ' 'minimum of 2 disks.') handle_exception(Exception(e_msg), request) if (pool.raid == 'raid6' and remaining_disks < 3): e_msg = ('Disks cannot be removed from this pool because ' 'its raid configuration (raid6) requires a ' 'minimum of 3 disks.') handle_exception(Exception(e_msg), request) usage = pool_usage('/%s/%s' % (settings.MNT_PT, pool.name)) size_cut = 0 for d in disks: size_cut += d.size if size_cut >= (pool.size - usage): e_msg = ('Removing disks ({}) may shrink the pool by ' '{} KB, which is greater than available free ' 'space {} KB. This is ' 'not supported.').format(dnames, size_cut, usage) handle_exception(Exception(e_msg), request) resize_pool(pool, dnames, add=False) tid = self._balance_start(pool) ps = PoolBalance(pool=pool, tid=tid) ps.save() for d in disks: d.pool = None d.save() else: e_msg = 'Command ({}) is not supported.'.format(command) handle_exception(Exception(e_msg), request) pool.size = pool.usage_bound() pool.save() return Response(PoolInfoSerializer(pool).data)
def put(self, request, pname, command): """ resize a pool. @pname: pool's name @command: 'add' - add a list of disks and hence expand the pool 'remove' - remove a list of disks and hence shrink the pool """ with self._handle_exception(request): if (pname == settings.ROOT_POOL): e_msg = ('Edit operations are not allowed on this Pool(%s) ' 'as it contains the operating system.' % pname) handle_exception(Exception(e_msg), request) try: pool = Pool.objects.get(name=pname) except: e_msg = ('Pool(%s) does not exist.' % pname) handle_exception(Exception(e_msg), request) if (command == 'remount'): return self._remount(request, pool) disks = [self._validate_disk(d, request) for d in request.data.get('disks')] num_new_disks = len(disks) if (num_new_disks == 0): e_msg = ('List of disks in the input cannot be empty.') handle_exception(Exception(e_msg), request) dnames = [d.name for d in disks] mount_disk = Disk.objects.filter(pool=pool)[0].name new_raid = request.data.get('raid_level', pool.raid) num_total_disks = (Disk.objects.filter(pool=pool).count() + num_new_disks) usage = pool_usage('/%s/%s' % (settings.MNT_PT, pool.name)) # free_percent = (usage[2]/usage[0]) * 100 free_percent = (usage[2]* 100)/usage[0] threshold_percent = self.ADD_THRESHOLD * 100 if (command == 'add'): for d in disks: if (d.pool is not None): e_msg = ('Disk(%s) cannot be added to this Pool(%s) ' 'because it belongs to another pool(%s)' % (d.name, pool.name, d.pool.name)) handle_exception(Exception(e_msg), request) if (d.btrfs_uuid is not None): e_msg = ('Disk(%s) has a BTRFS filesystem from the ' 'past. If you really like to add it, wipe it ' 'from the Storage -> Disks screen of the ' 'web-ui' % d.name) handle_exception(Exception(e_msg), request) if (new_raid not in self.SUPPORTED_MIGRATIONS[pool.raid]): e_msg = ('Pool migration from %s to %s is not supported.' % (pool.raid, new_raid)) handle_exception(Exception(e_msg), request) if (PoolBalance.objects.filter( pool=pool, status__regex=r'(started|running)').exists()): e_msg = ('A Balance process is already running for this ' 'pool(%s). Resize is not supported during a ' 'balance process.' % pool.name) handle_exception(Exception(e_msg), request) if (free_percent < threshold_percent): e_msg = ('Resize is only supported when there is at least ' '%d percent free space available. But currently ' 'only %d percent is free. Remove some data and ' 'try again.' % (threshold_percent, free_percent)) handle_exception(Exception(e_msg), request) if (new_raid != pool.raid): if (((pool.raid in ('single', 'raid0')) and new_raid in ('raid1', 'raid10'))): cur_num_disks = num_total_disks - num_new_disks if (num_new_disks < cur_num_disks): e_msg = ('For single/raid0 to raid1/raid10 ' 'conversion, at least as many as present ' 'number of disks must be added. %d ' 'disks are provided, but at least %d are ' 'required.' % (num_new_disks, cur_num_disks)) handle_exception(Exception(e_msg), request) resize_pool(pool, mount_disk, dnames) balance_pid = balance_start(pool, mount_disk, convert=new_raid) ps = PoolBalance(pool=pool, pid=balance_pid) ps.save() pool.raid = new_raid for d_o in disks: d_o.pool = pool d_o.save() elif (command == 'remove'): if (new_raid != pool.raid): e_msg = ('Raid configuration cannot be changed while ' 'removing disks') handle_exception(Exception(e_msg), request) for d in disks: if (d.pool is None or d.pool != pool): e_msg = ('Disk(%s) cannot be removed because it does ' 'not belong to this Pool(%s)' % (d.name, pool.name)) handle_exception(Exception(e_msg), request) remaining_disks = (Disk.objects.filter(pool=pool).count() - num_new_disks) if (pool.raid in ('raid0', 'single',)): e_msg = ('Disks cannot be removed from a pool with this ' 'raid(%s) configuration' % pool.raid) handle_exception(Exception(e_msg), request) if (pool.raid in ('raid5', 'raid6',)): e_msg = ('Disk removal is not supported for pools with ' 'raid5/6 configuration') handle_exception(Exception(e_msg), request) if (pool.raid == 'raid10'): if (num_new_disks != 2): e_msg = ('Only two disks can be removed at once from ' 'this pool because of its raid ' 'configuration(%s)' % pool.raid) handle_exception(Exception(e_msg), request) elif (remaining_disks < 4): e_msg = ('Disks cannot be removed from this pool ' 'because its raid configuration(%s) ' 'requires a minimum of 4 disks' % pool.raid) handle_exception(Exception(e_msg), request) elif (pool.raid == 'raid1'): if (num_new_disks != 1): e_msg = ('Only one disk can be removed at once from ' 'this pool because of its raid ' 'configuration(%s)' % pool.raid) handle_exception(Exception(e_msg), request) elif (remaining_disks < 2): e_msg = ('Disks cannot be removed from this pool ' 'because its raid configuration(%s) ' 'requires a minimum of 2 disks' % pool.raid) handle_exception(Exception(e_msg), request) threshold_percent = 100 - threshold_percent if (free_percent < threshold_percent): e_msg = ('Removing disks is only supported when there is ' 'at least %d percent free space available. But ' 'currently only %d percent is free. Remove some ' 'data and try again.' % (threshold_percent, free_percent)) handle_exception(Exception(e_msg), request) resize_pool(pool, mount_disk, dnames, add=False) balance_pid = balance_start(pool, mount_disk) ps = PoolBalance(pool=pool, pid=balance_pid) ps.save() for d in disks: d.pool = None d.save() else: e_msg = ('command(%s) is not supported.' % command) handle_exception(Exception(e_msg), request) usage = pool_usage('/%s/%s' % (settings.MNT_PT, pool.name)) pool.size = usage[0] pool.save() return Response(PoolInfoSerializer(pool).data)
def put(self, request, pid, command): """ resize a pool. @pname: pool's name @command: 'add' - add a list of disks and hence expand the pool 'remove' - remove a list of disks and hence shrink the pool 'remount' - remount the pool, to apply changed mount options 'quotas' - request pool quota setting change """ with self._handle_exception(request): try: pool = Pool.objects.get(id=pid) except: e_msg = 'Pool with id ({}) does not exist.'.format(pid) handle_exception(Exception(e_msg), request) if (pool.role == 'root' and command != 'quotas'): e_msg = ('Edit operations are not allowed on this pool ({}) ' 'as it contains the operating ' 'system.').format(pool.name) handle_exception(Exception(e_msg), request) if (command == 'remount'): return self._remount(request, pool) if (command == 'quotas'): # There is a pending btrfs change that allows for quota state # change on unmounted Volumes (pools). return self._quotas(request, pool) if not pool.is_mounted: e_msg = ('Pool member / raid edits require an active mount. ' 'Please see the "Maintenance required" section.') handle_exception(Exception(e_msg), request) if command == 'remove' and \ request.data.get('disks', []) == ['missing']: disks = [] logger.debug('Remove missing request skipping disk validation') else: disks = [self._validate_disk_id(diskId, request) for diskId in request.data.get('disks', [])] num_disks_selected = len(disks) dnames = self._role_filter_disk_names(disks, request) new_raid = request.data.get('raid_level', pool.raid) if (command == 'add'): # Only attached disks can be selected during an add operation. num_total_attached_disks = pool.disk_set.attached().count() \ + num_disks_selected for d in disks: if (d.pool is not None): e_msg = ('Disk ({}) cannot be added to this pool ({}) ' 'because it belongs to another pool ({})' '.').format(d.name, pool.name, d.pool.name) handle_exception(Exception(e_msg), request) if (d.btrfs_uuid is not None): e_msg = ('Disk ({}) has a BTRFS filesystem from the ' 'past. If you really like to add it, wipe it ' 'from the Storage -> Disks screen of the ' 'web-ui.').format(d.name) handle_exception(Exception(e_msg), request) if pool.raid == 'single' and new_raid == 'raid10': # TODO: Consider removing once we have better space calc. # Avoid extreme raid level change upwards (space issues). e_msg = ('Pool migration from {} to {} is not ' 'supported.').format(pool.raid, new_raid) handle_exception(Exception(e_msg), request) if (new_raid == 'raid10' and num_total_attached_disks < 4): e_msg = ('A minimum of 4 drives are required for the ' 'raid level: raid10.') handle_exception(Exception(e_msg), request) if (new_raid == 'raid6' and num_total_attached_disks < 3): e_msg = ('A minimum of 3 drives are required for the ' 'raid level: raid6.') handle_exception(Exception(e_msg), request) if (new_raid == 'raid5' and num_total_attached_disks < 2): e_msg = ('A minimum of 2 drives are required for the ' 'raid level: raid5.') handle_exception(Exception(e_msg), request) if (PoolBalance.objects.filter( pool=pool, status__regex=r'(started|running|cancelling|pausing|paused)').exists()): # noqa E501 e_msg = ('A Balance process is already running or paused ' 'for this pool ({}). Resize is not supported ' 'during a balance process.').format(pool.name) handle_exception(Exception(e_msg), request) # TODO: run resize_pool() as async task like start_balance() resize_pool(pool, dnames) # None if no action force = False # During dev add we also offer raid level change, if selected # blanket apply '-f' to allow for reducing metadata integrity. if new_raid != pool.raid: force = True tid = self._balance_start(pool, force=force, convert=new_raid) ps = PoolBalance(pool=pool, tid=tid) ps.save() pool.raid = new_raid for d_o in disks: d_o.pool = pool d_o.save() # Now we ensure udev info is updated via system wide trigger trigger_udev_update() elif (command == 'remove'): if (new_raid != pool.raid): e_msg = ('Raid configuration cannot be changed while ' 'removing disks.') handle_exception(Exception(e_msg), request) detached_disks_selected = 0 for d in disks: # to be removed if (d.pool is None or d.pool != pool): e_msg = ('Disk ({}) cannot be removed because it does ' 'not belong to this ' 'pool ({}).').format(d.name, pool.name) handle_exception(Exception(e_msg), request) if re.match('detached-', d.name) is not None: detached_disks_selected += 1 if detached_disks_selected >= 3: # Artificial constraint but no current btrfs raid level yet # allows for > 2 dev detached and we have a mounted vol. e_msg = ('We currently only support removing two' 'detached disks at a time.') handle_exception(Exception(e_msg), request) attached_disks_selected = ( num_disks_selected - detached_disks_selected) remaining_attached_disks = ( pool.disk_set.attached().count() - attached_disks_selected) if (pool.raid == 'raid0'): e_msg = ('Disks cannot be removed from a pool with this ' 'raid ({}) configuration.').format(pool.raid) handle_exception(Exception(e_msg), request) if (pool.raid == 'raid1' and remaining_attached_disks < 2): e_msg = ('Disks cannot be removed from this pool ' 'because its raid configuration (raid1) ' 'requires a minimum of 2 disks.') handle_exception(Exception(e_msg), request) if (pool.raid == 'raid10' and remaining_attached_disks < 4): e_msg = ('Disks cannot be removed from this pool ' 'because its raid configuration (raid10) ' 'requires a minimum of 4 disks.') handle_exception(Exception(e_msg), request) if (pool.raid == 'raid5' and remaining_attached_disks < 2): e_msg = ('Disks cannot be removed from this pool because ' 'its raid configuration (raid5) requires a ' 'minimum of 2 disks.') handle_exception(Exception(e_msg), request) if (pool.raid == 'raid6' and remaining_attached_disks < 3): e_msg = ('Disks cannot be removed from this pool because ' 'its raid configuration (raid6) requires a ' 'minimum of 3 disks.') handle_exception(Exception(e_msg), request) usage = pool_usage('/%s/%s' % (settings.MNT_PT, pool.name)) size_cut = 0 for d in disks: size_cut += d.size if size_cut >= (pool.size - usage): e_msg = ('Removing disks ({}) may shrink the pool by ' '{} KB, which is greater than available free ' 'space {} KB. This is ' 'not supported.').format(dnames, size_cut, usage) handle_exception(Exception(e_msg), request) # TODO: run resize_pool() as async task like start_balance(), # particularly important on device delete as it initiates an # internal volume balance which cannot be monitored by: # btrfs balance status. # See https://github.com/rockstor/rockstor-core/issues/1722 # Hence we need also to add a 'DIY' status / percentage # reporting method. resize_pool(pool, dnames, add=False) # None if no action # Unlike resize_pool() with add=True a delete has an implicit # balance where the deleted disks contents are re-distributed # across the remaining disks. for d in disks: d.pool = None d.save() else: e_msg = 'Command ({}) is not supported.'.format(command) handle_exception(Exception(e_msg), request) pool.size = pool.usage_bound() pool.save() return Response(PoolInfoSerializer(pool).data)
def put(self, request, pname, command): """ resize a pool. @pname: pool's name @command: 'add' - add a list of disks and hence expand the pool 'remove' - remove a list of disks and hence shrink the pool """ with self._handle_exception(request): if pname == settings.ROOT_POOL: e_msg = ( "Edit operations are not allowed on this Pool(%s) " "as it contains the operating system." % pname ) handle_exception(Exception(e_msg), request) try: pool = Pool.objects.get(name=pname) except: e_msg = "Pool(%s) does not exist." % pname handle_exception(Exception(e_msg), request) if command == "remount": return self._remount(request, pool) disks = [self._validate_disk(d, request) for d in request.data.get("disks")] num_new_disks = len(disks) if num_new_disks == 0: e_msg = "List of disks in the input cannot be empty." handle_exception(Exception(e_msg), request) dnames = [d.name for d in disks] mount_disk = Disk.objects.filter(pool=pool)[0].name new_raid = request.data.get("raid_level", pool.raid) num_total_disks = Disk.objects.filter(pool=pool).count() + num_new_disks usage = pool_usage("/%s/%s" % (settings.MNT_PT, pool.name)) # free_percent = (usage[2]/usage[0]) * 100 free_percent = (usage[2] * 100) / usage[0] threshold_percent = self.ADD_THRESHOLD * 100 if command == "add": for d in disks: if d.pool is not None: e_msg = ( "Disk(%s) cannot be added to this Pool(%s) " "because it belongs to another pool(%s)" % (d.name, pool.name, d.pool.name) ) handle_exception(Exception(e_msg), request) if d.btrfs_uuid is not None: e_msg = ( "Disk(%s) has a BTRFS filesystem from the " "past. If you really like to add it, wipe it " "from the Storage -> Disks screen of the " "web-ui" % d.name ) handle_exception(Exception(e_msg), request) if new_raid not in self.SUPPORTED_MIGRATIONS[pool.raid]: e_msg = "Pool migration from %s to %s is not supported." % (pool.raid, new_raid) handle_exception(Exception(e_msg), request) if PoolBalance.objects.filter(pool=pool, status__regex=r"(started|running)").exists(): e_msg = ( "A Balance process is already running for this " "pool(%s). Resize is not supported during a " "balance process." % pool.name ) handle_exception(Exception(e_msg), request) if free_percent < threshold_percent: e_msg = ( "Resize is only supported when there is at least " "%d percent free space available. But currently " "only %d percent is free. Remove some data and " "try again." % (threshold_percent, free_percent) ) handle_exception(Exception(e_msg), request) if new_raid != pool.raid: if (pool.raid in ("single", "raid0")) and new_raid in ("raid1", "raid10"): cur_num_disks = num_total_disks - num_new_disks if num_new_disks < cur_num_disks: e_msg = ( "For single/raid0 to raid1/raid10 " "conversion, at least as many as present " "number of disks must be added. %d " "disks are provided, but at least %d are " "required." % (num_new_disks, cur_num_disks) ) handle_exception(Exception(e_msg), request) resize_pool(pool, mount_disk, dnames) balance_pid = balance_start(pool, mount_disk, convert=new_raid) ps = PoolBalance(pool=pool, pid=balance_pid) ps.save() pool.raid = new_raid for d_o in disks: d_o.pool = pool d_o.save() elif command == "remove": if new_raid != pool.raid: e_msg = "Raid configuration cannot be changed while " "removing disks" handle_exception(Exception(e_msg), request) for d in disks: if d.pool is None or d.pool != pool: e_msg = "Disk(%s) cannot be removed because it does " "not belong to this Pool(%s)" % ( d.name, pool.name, ) handle_exception(Exception(e_msg), request) remaining_disks = Disk.objects.filter(pool=pool).count() - num_new_disks if pool.raid in ("raid0", "single"): e_msg = "Disks cannot be removed from a pool with this " "raid(%s) configuration" % pool.raid handle_exception(Exception(e_msg), request) if pool.raid in ("raid5", "raid6"): e_msg = "Disk removal is not supported for pools with " "raid5/6 configuration" handle_exception(Exception(e_msg), request) if pool.raid == "raid10": if num_new_disks != 2: e_msg = ( "Only two disks can be removed at once from " "this pool because of its raid " "configuration(%s)" % pool.raid ) handle_exception(Exception(e_msg), request) elif remaining_disks < 4: e_msg = ( "Disks cannot be removed from this pool " "because its raid configuration(%s) " "requires a minimum of 4 disks" % pool.raid ) handle_exception(Exception(e_msg), request) elif pool.raid == "raid1": if num_new_disks != 1: e_msg = ( "Only one disk can be removed at once from " "this pool because of its raid " "configuration(%s)" % pool.raid ) handle_exception(Exception(e_msg), request) elif remaining_disks < 2: e_msg = ( "Disks cannot be removed from this pool " "because its raid configuration(%s) " "requires a minimum of 2 disks" % pool.raid ) handle_exception(Exception(e_msg), request) threshold_percent = 100 - threshold_percent if free_percent < threshold_percent: e_msg = ( "Removing disks is only supported when there is " "at least %d percent free space available. But " "currently only %d percent is free. Remove some " "data and try again." % (threshold_percent, free_percent) ) handle_exception(Exception(e_msg), request) resize_pool(pool, mount_disk, dnames, add=False) balance_pid = balance_start(pool, mount_disk) ps = PoolBalance(pool=pool, pid=balance_pid) ps.save() for d in disks: d.pool = None d.save() else: e_msg = "command(%s) is not supported." % command handle_exception(Exception(e_msg), request) usage = pool_usage("/%s/%s" % (settings.MNT_PT, pool.name)) pool.size = usage[0] pool.save() return Response(PoolInfoSerializer(pool).data)
def put(self, request, pname, command): """ resize a pool. @pname: pool's name @command: 'add' - add a list of disks and hence expand the pool 'remove' - remove a list of disks and hence shrink the pool """ with self._handle_exception(request): if (pname == settings.ROOT_POOL): e_msg = ('Edit operations are not allowed on this Pool(%s) ' 'as it contains the operating system.' % pname) handle_exception(Exception(e_msg), request) try: pool = Pool.objects.get(name=pname) except: e_msg = ('Pool(%s) does not exist.' % pname) handle_exception(Exception(e_msg), request) if (command == 'remount'): return self._remount(request, pool) disks = [ self._validate_disk(d, request) for d in request.data.get('disks') ] num_new_disks = len(disks) if (num_new_disks == 0): e_msg = ('List of disks in the input cannot be empty.') handle_exception(Exception(e_msg), request) dnames = [d.name for d in disks] mount_disk = Disk.objects.filter(pool=pool)[0].name new_raid = request.data.get('raid_level', pool.raid) num_total_disks = (Disk.objects.filter(pool=pool).count() + num_new_disks) usage = pool_usage('/%s/%s' % (settings.MNT_PT, pool.name)) # free_percent = (usage[2]/usage[0]) * 100 free_percent = (usage[2] * 100) / usage[0] threshold_percent = self.ADD_THRESHOLD * 100 if (command == 'add'): for d in disks: if (d.pool is not None): e_msg = ('Disk(%s) cannot be added to this Pool(%s) ' 'because it belongs to another pool(%s)' % (d.name, pool.name, d.pool.name)) handle_exception(Exception(e_msg), request) if (d.btrfs_uuid is not None): e_msg = ('Disk(%s) has a BTRFS filesystem from the ' 'past. If you really like to add it, wipe it ' 'from the Storage -> Disks screen of the ' 'web-ui' % d.name) handle_exception(Exception(e_msg), request) if (new_raid not in self.SUPPORTED_MIGRATIONS[pool.raid]): e_msg = ('Pool migration from %s to %s is not supported.' % (pool.raid, new_raid)) handle_exception(Exception(e_msg), request) if (PoolBalance.objects.filter( pool=pool, status__regex=r'(started|running)').exists()): e_msg = ('A Balance process is already running for this ' 'pool(%s). Resize is not supported during a ' 'balance process.' % pool.name) handle_exception(Exception(e_msg), request) if (free_percent < threshold_percent): e_msg = ('Resize is only supported when there is at least ' '%d percent free space available. But currently ' 'only %d percent is free. Remove some data and ' 'try again.' % (threshold_percent, free_percent)) handle_exception(Exception(e_msg), request) if (new_raid != pool.raid): if (((pool.raid in ('single', 'raid0')) and new_raid in ('raid1', 'raid10'))): cur_num_disks = num_total_disks - num_new_disks if (num_new_disks < cur_num_disks): e_msg = ('For single/raid0 to raid1/raid10 ' 'conversion, at least as many as present ' 'number of disks must be added. %d ' 'disks are provided, but at least %d are ' 'required.' % (num_new_disks, cur_num_disks)) handle_exception(Exception(e_msg), request) resize_pool(pool, mount_disk, dnames) balance_pid = balance_start(pool, mount_disk, convert=new_raid) ps = PoolBalance(pool=pool, pid=balance_pid) ps.save() pool.raid = new_raid for d_o in disks: d_o.pool = pool d_o.save() elif (command == 'remove'): if (new_raid != pool.raid): e_msg = ('Raid configuration cannot be changed while ' 'removing disks') handle_exception(Exception(e_msg), request) for d in disks: if (d.pool is None or d.pool != pool): e_msg = ('Disk(%s) cannot be removed because it does ' 'not belong to this Pool(%s)' % (d.name, pool.name)) handle_exception(Exception(e_msg), request) remaining_disks = (Disk.objects.filter(pool=pool).count() - num_new_disks) if (pool.raid in ( 'raid0', 'single', )): e_msg = ('Disks cannot be removed from a pool with this ' 'raid(%s) configuration' % pool.raid) handle_exception(Exception(e_msg), request) if (pool.raid in ( 'raid5', 'raid6', )): e_msg = ('Disk removal is not supported for pools with ' 'raid5/6 configuration') handle_exception(Exception(e_msg), request) if (pool.raid == 'raid10'): if (num_new_disks != 2): e_msg = ('Only two disks can be removed at once from ' 'this pool because of its raid ' 'configuration(%s)' % pool.raid) handle_exception(Exception(e_msg), request) elif (remaining_disks < 4): e_msg = ('Disks cannot be removed from this pool ' 'because its raid configuration(%s) ' 'requires a minimum of 4 disks' % pool.raid) handle_exception(Exception(e_msg), request) elif (pool.raid == 'raid1'): if (num_new_disks != 1): e_msg = ('Only one disk can be removed at once from ' 'this pool because of its raid ' 'configuration(%s)' % pool.raid) handle_exception(Exception(e_msg), request) elif (remaining_disks < 2): e_msg = ('Disks cannot be removed from this pool ' 'because its raid configuration(%s) ' 'requires a minimum of 2 disks' % pool.raid) handle_exception(Exception(e_msg), request) threshold_percent = 100 - threshold_percent if (free_percent < threshold_percent): e_msg = ('Removing disks is only supported when there is ' 'at least %d percent free space available. But ' 'currently only %d percent is free. Remove some ' 'data and try again.' % (threshold_percent, free_percent)) handle_exception(Exception(e_msg), request) resize_pool(pool, mount_disk, dnames, add=False) balance_pid = balance_start(pool, mount_disk) ps = PoolBalance(pool=pool, pid=balance_pid) ps.save() for d in disks: d.pool = None d.save() else: e_msg = ('command(%s) is not supported.' % command) handle_exception(Exception(e_msg), request) usage = pool_usage('/%s/%s' % (settings.MNT_PT, pool.name)) pool.size = usage[0] pool.save() return Response(PoolInfoSerializer(pool).data)
def put(self, request, pname, command): """ resize a pool. @pname: pool's name @command: 'add' - add a list of disks and hence expand the pool 'remove' - remove a list of disks and hence shrink the pool """ with self._handle_exception(request): try: pool = Pool.objects.get(name=pname) except: e_msg = ('pool: %s does not exist' % pname) handle_exception(Exception(e_msg), request) disks = [self._validate_disk(d, request) for d in request.DATA.get('disks')] if (len(disks) == 0): msg = ('list of disks in the input is empty') raise Exception(msg) dnames = [d.name for d in disks] for d in disks: if (d.pool is not None and d.pool != pool): e_msg = ('Disk(%s) belongs to another pool(%s)' % (d.name, d.pool.name)) handle_exception(Exception(e_msg), request) mount_disk = Disk.objects.filter(pool=pool)[0].name if (command == 'add'): for d_o in disks: d_o.pool = pool d_o.save() resize_pool(pool.name, mount_disk, dnames) elif (command == 'remove'): remaining_disks = Disk.objects.filter(pool=pool).count() - len(disks) logger.debug('remaining disks = %d' % remaining_disks) if (pool.raid == 'raid0' or pool.raid == 'raid1' or pool.raid == 'raid10' or pool.raid == 'single'): e_msg = ('Removing drives from this(%s) raid ' 'configuration is not supported' % pool.raid) handle_exception(Exception(e_msg), request) if (pool.raid == 'raid5' and remaining_disks < 3): e_msg = ('Resize not possible because a minimum of 3 ' 'drives is required for this(%s) ' 'raid configuration.' % pool.raid) handle_exception(Exception(e_msg), request) if (pool.raid == 'raid6' and remaining_disks < 4): e_msg = ('Resize not possible because a minimum of 4 ' 'drives is required for this(%s) raid ' 'configuration' % pool.raid) handle_exception(Exception(e_msg), request) for d in disks: d.pool = None d.save() mount_disk = Disk.objects.filter(pool=pool)[0].name resize_pool(pool.name, mount_disk, dnames, add=False) else: msg = ('unknown command: %s' % command) raise Exception(msg) usage = pool_usage(mount_disk) pool.size = usage[0] pool.save() return Response(PoolInfoSerializer(pool).data)