Esempio n. 1
0
    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)
Esempio n. 2
0
    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)
Esempio n. 3
0
    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)
Esempio n. 4
0
    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)
Esempio n. 5
0
    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)
Esempio n. 6
0
    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)
Esempio n. 7
0
    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)
Esempio n. 8
0
    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)
Esempio n. 9
0
    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)
Esempio n. 10
0
    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)
Esempio n. 11
0
    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)