From 5dcc2fa490873fdb7cd9cc437f77cf8b6409c111 Mon Sep 17 00:00:00 2001 From: Philip Guyton Date: Mon, 18 Dec 2017 18:17:58 +0000 Subject: [PATCH] improve quotas not enabled behaviour #1869 Uses the existing -1/-1 default for pqgoups to represent an unset state for the share.pqgroup and adds an active return to this value whenever quotas are deemed to be disabled. Pqgroup setup is moved from the bootstrap process into the import_shares / shares refresh section. This allows for live setting / resetting of pqgroups via the regular share re-fresh process. Pool.quotas_enabled and share.pqgroup_exist properties are added and all used low level quota actions are adjusted to catch, log, and ignore a quota disabled state. Additionally active pqgroup assignment is added to share resize and share refresh procedures which aid in returning to existence the expected native 2015/n pqgroups and their relationship to the auto generated 0/n qgroups as whenever quotas are disabled all this info (var the 0/n) is lost. UI elements are added in pool and share focused pages to indicate a live status for the associated pool quotas enabled status. --- src/rockstor/fs/btrfs.py | 165 +++++++++++++++++- src/rockstor/storageadmin/models/pool.py | 11 +- src/rockstor/storageadmin/models/share.py | 14 ++ src/rockstor/storageadmin/serializers.py | 2 + .../js/templates/pool/pool_info_module.jst | 7 + .../js/templates/pool/pools_table.jst | 8 +- .../js/templates/share/share_usage_module.jst | 7 + .../js/templates/share/shares_table.jst | 13 +- .../js/views/share_usage_module.js | 1 + src/rockstor/storageadmin/views/command.py | 6 +- src/rockstor/storageadmin/views/disk.py | 1 + src/rockstor/storageadmin/views/share.py | 26 ++- .../storageadmin/views/share_helpers.py | 38 +++- 13 files changed, 276 insertions(+), 23 deletions(-) diff --git a/src/rockstor/fs/btrfs.py b/src/rockstor/fs/btrfs.py index a2a32a550..790c0f246 100644 --- a/src/rockstor/fs/btrfs.py +++ b/src/rockstor/fs/btrfs.py @@ -26,6 +26,7 @@ from system.exceptions import (CommandException) from pool_scrub import PoolScrub from django_ztask.decorators import task +from django.conf import settings import logging """ @@ -41,7 +42,8 @@ DEFAULT_MNT_DIR = '/mnt2/' RMDIR = '/bin/rmdir' QID = '2015' - +# The following model/db default setting is also used when quotas are disabled. +PQGROUP_DEFAULT = settings.MODEL_DEFS['pqgroup'] def add_pool(pool, disks): """ @@ -678,13 +680,71 @@ def disable_quota(pool_name): return switch_quota(pool_name, flag='disable') +def are_quotas_enabled(mnt_pt): + """ + Simple wrapper around 'btrfs qgroup show -f --raw mnt_pt' intended + as a fast determiner of True / False status of quotas enabled + :param mnt_pt: Mount point of btrfs filesystem + :return: True on rc = 0 False otherwise. + """ + o, e, rc = run_command([BTRFS, 'qgroup', 'show', '-f', '--raw', mnt_pt]) + if rc == 0: + return True + return False + + +def qgroup_exists(mnt_pt, qgroup): + """ + Simple wrapper around 'btrfs qgroup show --raw mnt_pt' intended to + establish if a specific qgroup exists on a btrfs filesystem. + :param mnt_pt: btrfs filesystem mount point, usually the pool. + :param qgroup: qgroup of the form 2015/n (intended for use with pqgroup) + :return: True is given qgroup exists in command output, False otherwise. + """ + o, e, rc = run_command([BTRFS, 'qgroup', 'show', '--raw', mnt_pt]) + # example output: + # 'qgroupid rfer excl ' + # '------- ---- ---- ' + # '0/5 16384 16384 ' + # ... + # '2015/12 0 0 ' + if rc == 0 and len(o) > 2: + # index from 2 to miss header lines and -1 to skip end blank line = [] + qgroup_list = [line.split()[0] for line in o[2:-1]] + # eg from rockstor_rockstor pool we get: + # qgroup_list=['0/5', '0/257', '0/258', '0/260', '2015/1', '2015/2'] + if qgroup in qgroup_list: + return True + return False + + def qgroup_id(pool, share_name): sid = share_id(pool, share_name) return '0/' + sid def qgroup_max(mnt_pt): - o, e, rc = run_command([BTRFS, 'qgroup', 'show', mnt_pt], log=True) + """ + Parses the output of "btrfs qgroup show mnt_pt" to find the highest qgroup + matching QID/* if non is found then 0 will be returned. + Quotas not enabled is flagged by a -1 return value. + :param mnt_pt: A given btrfs mount point. + :return: -1 if quotas not enabled, else highest 2015/* qgroup found or 0 + """ + try: + o, e, rc = run_command([BTRFS, 'qgroup', 'show', mnt_pt], log=True) + except CommandException as e: + # disabled quotas will result in o = [''], rc = 1 and e[0] = + emsg = "ERROR: can't list qgroups: quotas not enabled" + # this is non fatal so we catch this specific error and info log it. + if e.rc == 1 and e.err[0] == emsg: + logger.info('Mount Point: {} has Quotas disabled, skipping qgroup ' + 'show.'.format(mnt_pt)) + # and return our default res + return -1 + # otherwise we raise an exception as normal. + raise + # if no exception was raised find the max 2015/qgroup res = 0 for l in o: if (re.match('%s/' % QID, l) is not None): @@ -694,10 +754,33 @@ def qgroup_max(mnt_pt): return res -def qgroup_create(pool): +def qgroup_create(pool, qgroup='-1/-1'): + """ + When passed only a pool an attempt will be made to ascertain if quotas is + enabled, if not '-1/-1' is returned as a flag to indicate this state. + If quotas are not enabled then the highest available quota of the form + 2015/n is selected and created, if possible. + If passed both a pool and a specific qgroup an attempt is made, given the + same behaviour as above, to create this specific group: this scenario is + primarily used to re-establish prior existing qgroups post quota disable, + share manipulation, quota enable cycling. + :param pool: A pool object. + :param qgroup: native qgroup of the form 2015/n + :return: -1/-1 on quotas disabled, otherwise it will return the native + quota whose creation was attempt. + """ # mount pool mnt_pt = mount_root(pool) - qid = ('%s/%d' % (QID, qgroup_max(mnt_pt) + 1)) + max_native_qgroup = qgroup_max(mnt_pt) + if max_native_qgroup == -1: + # We have received a quotas disabled flag so will be unable to create + # a new quota group. So return our db default which can in turn flag + # an auto updated of pqgroup upon next refresh-share-state. + return PQGROUP_DEFAULT + if qgroup != PQGROUP_DEFAULT: + qid = qgroup + else: + qid = ('%s/%d' % (QID, max_native_qgroup + 1)) try: out, err, rc = run_command([BTRFS, 'qgroup', 'create', qid, mnt_pt], log=True) @@ -715,7 +798,18 @@ def qgroup_create(pool): def qgroup_destroy(qid, mnt_pt): - o, e, rc = run_command([BTRFS, 'qgroup', 'show', mnt_pt]) + cmd = [BTRFS, 'qgroup', 'show', mnt_pt] + try: + o, e, rc = run_command(cmd, log=True) + except CommandException as e: + # we may have quotas disabled so catch and deal. + emsg = "ERROR: can't list qgroups: quotas not enabled" + if e.rc == 1 and e.err[0] == emsg: + # we have quotas disabled so can't destroy any anyway so skip + # and deal by returning False so our caller moves on. + return False + # otherwise we raise an exception as normal + raise e for l in o: if (re.match(qid, l) is not None and l.split()[0] == qid): return run_command([BTRFS, 'qgroup', 'destroy', qid, mnt_pt], @@ -726,7 +820,19 @@ def qgroup_destroy(qid, mnt_pt): def qgroup_is_assigned(qid, pqid, mnt_pt): # Returns true if the given qgroup qid is already assigned to pqid for the # path(mnt_pt) - o, e, rc = run_command([BTRFS, 'qgroup', 'show', '-pc', mnt_pt]) + cmd = [BTRFS, 'qgroup', 'show', '-pc', mnt_pt] + try: + o, e, rc = run_command(cmd, log=True) + except CommandException as e: + # we may have quotas disabled so catch and deal. + emsg = "ERROR: can't list qgroups: quotas not enabled" + if e.rc == 1 and e.err[0] == emsg: + # No deed to scan output as nothing to see with quotas disabled. + # And since no quota capability can be enacted we return True + # to avoid our caller trying any further with quotas. + return True + # otherwise we raise an exception as normal + raise e for l in o: fields = l.split() if (len(fields) > 3 and @@ -736,7 +842,26 @@ def qgroup_is_assigned(qid, pqid, mnt_pt): return False +def share_pqgroup_assign(pqgroup, share): + """ + Convenience wrapper to qgroup_assign() for use with a share object where + we wish to assign / reassign it's current db held qgroup to a passed + pqgroup. + :param pqgroup: pqgroup to use as parent. + :param share: share object + :return: qgroup_assign() result. + """ + mnt_pt = '{}/{}'.format(settings.MNT_PT, share.pool.name) + return qgroup_assign(share.qgroup, pqgroup, mnt_pt) + + def qgroup_assign(qid, pqid, mnt_pt): + """ + Wrapper for 'BTRFS, qgroup, assign, qid, pqid, mnt_pt' + :param qid: qgroup to assign as child of pqgroup + :param pqid: pqgroup to use as parent + :param mnt_pt: btrfs filesystem mountpoint (usually the associated pool) + """ if (qgroup_is_assigned(qid, pqid, mnt_pt)): return True @@ -744,7 +869,7 @@ def qgroup_assign(qid, pqid, mnt_pt): # "WARNING: # quotas may be inconsistent, rescan needed" and returns with # exit code 1. try: - run_command([BTRFS, 'qgroup', 'assign', qid, pqid, mnt_pt]) + run_command([BTRFS, 'qgroup', 'assign', qid, pqid, mnt_pt], log=True) except CommandException as e: wmsg = 'WARNING: quotas may be inconsistent, rescan needed' if (e.rc == 1 and e.err[0] == wmsg): @@ -766,14 +891,21 @@ def qgroup_assign(qid, pqid, mnt_pt): def update_quota(pool, qgroup, size_bytes): + # TODO: consider changing qgroup to pqgroup if we are only used this way. root_pool_mnt = mount_root(pool) # Until btrfs adds better support for qgroup limits. We'll not set limits. # It looks like we'll see the fixes in 4.2 and final ones by 4.3. + # Update: Further quota improvements look to be landing in 4.15. # cmd = [BTRFS, 'qgroup', 'limit', str(size_bytes), qgroup, root_pool_mnt] cmd = [BTRFS, 'qgroup', 'limit', 'none', qgroup, root_pool_mnt] # Set defaults in case our run_command fails to assign them. out = err = [''] rc = 0 + if qgroup == '-1/-1': + # We have a 'quotas disabled' qgroup value flag, log and return blank. + logger.info('Pool: {} ignoring ' + 'update_quota on {}'.format(pool.name, qgroup)) + return out, err, rc try: out, err, rc = run_command(cmd, log=True) except CommandException as e: @@ -785,6 +917,25 @@ def update_quota(pool, qgroup, size_bytes): logger.info('Pool: {} is Read-only, skipping qgroup ' 'limit.'.format(pool.name)) return out, err, rc + # quotas disabled results in o = [''], rc = 1 and e[0] = + emsg2 = 'ERROR: unable to limit requested quota group: ' \ + 'Invalid argument' + # quotas disabled is not a fatal failure but here we key from what + # is a non specific error: 'Invalid argument'. + # TODO: improve this clause as currently too broad. + # TODO: we could for example use if qgroup_max(mnt) == -1 + if e.rc == 1 and e.err[0] == emsg2: + logger.info('Pool: {} has encountered a qgroup limit issue, ' + 'skipping qgroup limit. Disabled quotas can cause ' + 'this error'.format(pool.name)) + return out, err, rc + emsg3 = 'ERROR: unable to limit requested quota group: ' \ + 'No such file or directory' + if e.rc == 1 and e.err[0] == emsg3: + logger.info('Pool: {} is missing expected ' + 'qgroup {}'.format(pool.name, qgroup)) + logger.info('Previously disabled quotas can cause this issue') + return out, err, rc # raise an exception as usual otherwise raise return out, err, rc diff --git a/src/rockstor/storageadmin/models/pool.py b/src/rockstor/storageadmin/models/pool.py index 68bd67901..be8660752 100644 --- a/src/rockstor/storageadmin/models/pool.py +++ b/src/rockstor/storageadmin/models/pool.py @@ -18,7 +18,8 @@ from django.db import models from django.conf import settings -from fs.btrfs import pool_usage, usage_bound +from fs.btrfs import pool_usage, usage_bound, \ + are_quotas_enabled from system.osi import mount_status RETURN_BOOLEAN = True @@ -74,5 +75,13 @@ def is_mounted(self, *args, **kwargs): except: return False + @property + def quotas_enabled(self, *args, **kwargs): + # Calls are_quotas_enabled for boolean response + try: + return are_quotas_enabled('%s%s' % (settings.MNT_PT, self.name)) + except: + return False + class Meta: app_label = 'storageadmin' diff --git a/src/rockstor/storageadmin/models/share.py b/src/rockstor/storageadmin/models/share.py index 7eeaa3746..209080944 100644 --- a/src/rockstor/storageadmin/models/share.py +++ b/src/rockstor/storageadmin/models/share.py @@ -21,6 +21,7 @@ from django.db.models.signals import (post_save, post_delete) from django.dispatch import receiver +from fs.btrfs import qgroup_exists from storageadmin.models import Pool from system.osi import mount_status from .netatalk_share import NetatalkShare @@ -81,6 +82,19 @@ def is_mounted(self, *args, **kwargs): except: return False + @property + def pqgroup_exist(self, *args, **kwargs): + # Returns boolean status of pqgroup existence + try: + if str(self.pqgroup) == '-1/-1': + return False + else: + return qgroup_exists( + '%s%s' % (settings.MNT_PT, self.pool.name), + '%s' % self.pqgroup) + except: + return False + class Meta: app_label = 'storageadmin' diff --git a/src/rockstor/storageadmin/serializers.py b/src/rockstor/storageadmin/serializers.py index 54ffa0a93..e8d6c178d 100644 --- a/src/rockstor/storageadmin/serializers.py +++ b/src/rockstor/storageadmin/serializers.py @@ -51,6 +51,7 @@ class PoolInfoSerializer(serializers.ModelSerializer): reclaimable = serializers.IntegerField() mount_status = serializers.CharField() is_mounted = serializers.BooleanField() + quotas_enabled = serializers.BooleanField() class Meta: model = Pool @@ -145,6 +146,7 @@ class ShareSerializer(serializers.ModelSerializer): nfs_exports = NFSExportSerializer(many=True, source='nfsexport_set') mount_status = serializers.CharField() is_mounted = serializers.BooleanField() + pqgroup_exist = serializers.BooleanField() class Meta: model = Share diff --git a/src/rockstor/storageadmin/static/storageadmin/js/templates/pool/pool_info_module.jst b/src/rockstor/storageadmin/static/storageadmin/js/templates/pool/pool_info_module.jst index 78e7d9859..14dd032cc 100644 --- a/src/rockstor/storageadmin/static/storageadmin/js/templates/pool/pool_info_module.jst +++ b/src/rockstor/storageadmin/static/storageadmin/js/templates/pool/pool_info_module.jst @@ -30,6 +30,13 @@ {{else}} {{model.mount_status}} {{/if}} +
+ Quotas: + {{#if model.quotas_enabled}} + Enabled + {{else}} + Disabled + {{/if}} diff --git a/src/rockstor/storageadmin/static/storageadmin/js/templates/pool/pools_table.jst b/src/rockstor/storageadmin/static/storageadmin/js/templates/pool/pools_table.jst index c985406a7..0d3fe920d 100644 --- a/src/rockstor/storageadmin/static/storageadmin/js/templates/pool/pools_table.jst +++ b/src/rockstor/storageadmin/static/storageadmin/js/templates/pool/pools_table.jst @@ -5,6 +5,7 @@ Name Size Usage + Quotas Raid Active mount options / Status Compression @@ -32,7 +33,12 @@ {{humanReadableSize 'usage' this.size this.reclaimable this.free}} ({{humanReadableSize 'usagePercent' this.size this.reclaimable this.free}} %) - + {{#if this.quotas_enabled}} + Enabled + {{else}} + Disabled + {{/if}} + {{this.raid}} {{#unless (isRoot this.role)}}   diff --git a/src/rockstor/storageadmin/static/storageadmin/js/templates/share/share_usage_module.jst b/src/rockstor/storageadmin/static/storageadmin/js/templates/share/share_usage_module.jst index b9b477a44..6476900a6 100644 --- a/src/rockstor/storageadmin/static/storageadmin/js/templates/share/share_usage_module.jst +++ b/src/rockstor/storageadmin/static/storageadmin/js/templates/share/share_usage_module.jst @@ -28,6 +28,13 @@ ({{pool_mount_status}}) {{/if}}
+ Pool Quotas:  + {{#if pool_quotas_enabled}} + Enabled + {{else}} + Disabled + {{/if}} +
Active mount options / Status: {{#if share_is_mounted}} diff --git a/src/rockstor/storageadmin/static/storageadmin/js/templates/share/shares_table.jst b/src/rockstor/storageadmin/static/storageadmin/js/templates/share/shares_table.jst index 148e18a20..e6f08a5c5 100644 --- a/src/rockstor/storageadmin/static/storageadmin/js/templates/share/shares_table.jst +++ b/src/rockstor/storageadmin/static/storageadmin/js/templates/share/shares_table.jst @@ -12,11 +12,11 @@ Name Size - Usage - Btrfs Usage + Usage + Btrfs Usage Active mount options / Status - Pool (Active mount options / Status) - Compression + Pool (Active mount options / Status) Quotas + Compression Actions @@ -40,6 +40,11 @@ {{else}} ({{this.pool.mount_status}}) {{/if}} + {{# if this.pool.quotas_enabled}} + Enabled + {{else}} + Disabled + {{/if}} {{displayCompressionAlgo this.compression_algo this.id}} diff --git a/src/rockstor/storageadmin/static/storageadmin/js/views/share_usage_module.js b/src/rockstor/storageadmin/static/storageadmin/js/views/share_usage_module.js index 65bb4533c..5b0ef1b26 100644 --- a/src/rockstor/storageadmin/static/storageadmin/js/views/share_usage_module.js +++ b/src/rockstor/storageadmin/static/storageadmin/js/views/share_usage_module.js @@ -47,6 +47,7 @@ ShareUsageModule = RockstorModuleView.extend({ poolName: this.share.get('pool').name, pool_is_mounted: this.share.get('pool').is_mounted, pool_mount_status: this.share.get('pool').mount_status, + pool_quotas_enabled: this.share.get('pool').quotas_enabled, share_is_mounted: this.share.get('is_mounted'), share_mount_status: this.share.get('mount_status'), pid: this.share.get('pool').id, diff --git a/src/rockstor/storageadmin/views/command.py b/src/rockstor/storageadmin/views/command.py index ef315d046..e265a5a5d 100644 --- a/src/rockstor/storageadmin/views/command.py +++ b/src/rockstor/storageadmin/views/command.py @@ -26,7 +26,7 @@ from rest_framework.permissions import IsAuthenticated from storageadmin.views import DiskMixin from system.osi import (uptime, kernel_info) -from fs.btrfs import (mount_share, mount_root, qgroup_create, get_pool_info, +from fs.btrfs import (mount_share, mount_root, get_pool_info, pool_raid, mount_snap) from system.ssh import (sftp_mount_map, sftp_mount) from system.services import systemctl @@ -92,15 +92,13 @@ def post(self, request, command, rtcepoch=None): for p in Pool.objects.all(): if p.disk_set.attached().count() == 0: continue + # Import / update db shares counterpart for managed pool. import_shares(p, request) for share in Share.objects.all(): if share.pool.disk_set.attached().count() == 0: continue try: - if (share.pqgroup == settings.MODEL_DEFS['pqgroup']): - share.pqgroup = qgroup_create(share.pool) - share.save() if not share.is_mounted: mnt_pt = ('%s%s' % (settings.MNT_PT, share.name)) mount_share(share, mnt_pt) diff --git a/src/rockstor/storageadmin/views/disk.py b/src/rockstor/storageadmin/views/disk.py index f7569c12a..383b78a9a 100644 --- a/src/rockstor/storageadmin/views/disk.py +++ b/src/rockstor/storageadmin/views/disk.py @@ -707,6 +707,7 @@ def _btrfs_disk_import(self, did, request): po.raid = pool_raid('%s%s' % (settings.MNT_PT, po.name))['data'] po.size = po.usage_bound() po.save() + # TODO: enable_quota could well break an import from a ro pool. enable_quota(po) import_shares(po, request) for share in Share.objects.filter(pool=po): diff --git a/src/rockstor/storageadmin/views/share.py b/src/rockstor/storageadmin/views/share.py index 60712dcf7..dc13de92b 100644 --- a/src/rockstor/storageadmin/views/share.py +++ b/src/rockstor/storageadmin/views/share.py @@ -24,7 +24,8 @@ SFTP, RockOn) from smart_manager.models import Replica from fs.btrfs import (add_share, remove_share, update_quota, volume_usage, - set_property, mount_share, qgroup_id, qgroup_create) + set_property, mount_share, qgroup_id, qgroup_create, + share_pqgroup_assign) from system.services import systemctl from storageadmin.serializers import ShareSerializer, SharePoolSerializer from storageadmin.util import handle_exception @@ -34,8 +35,12 @@ from smart_manager.models import Service import logging + logger = logging.getLogger(__name__) +# The following model/db default setting is also used when quotas are disabled. +PQGROUP_DEFAULT = settings.MODEL_DEFS['pqgroup'] + class ShareMixin(object): @@ -216,7 +221,24 @@ def put(self, request, sid): 'of the share.' % (new_size, cur_rusage)) handle_exception(Exception(e_msg), request) - update_quota(share.pool, share.pqgroup, new_size * 1024) + # quota maintenance + if share.pool.quotas_enabled: + # Only try create / update quotas if they are enabled, + # pqgroup of PQGROUP_DEFAULT (-1/-1) indicates no pqgroup, + # ie quotas were disabled when update was requested. + if share.pqgroup == PQGROUP_DEFAULT or \ + not share.pqgroup_exist: + # if quotas were disabled or pqgroup non-existent. + share.pqgroup = qgroup_create(share.pool) + share.save() + update_quota(share.pool, share.pqgroup, new_size * 1024) + share_pqgroup_assign(share.pqgroup, share) + else: + # Our pool's quotas are disabled so reset pqgroup to -1/-1. + if share.pqgroup != PQGROUP_DEFAULT: + # Only reset if necessary + share.pqgroup = PQGROUP_DEFAULT + share.save() share.size = new_size if ('compression' in request.data): new_compression = self._validate_compression(request) diff --git a/src/rockstor/storageadmin/views/share_helpers.py b/src/rockstor/storageadmin/views/share_helpers.py index 0488adda4..6ee73afac 100644 --- a/src/rockstor/storageadmin/views/share_helpers.py +++ b/src/rockstor/storageadmin/views/share_helpers.py @@ -23,10 +23,12 @@ from smart_manager.models import ShareUsage from fs.btrfs import (mount_share, mount_snap, is_mounted, umount_root, shares_info, volume_usage, snaps_info, - qgroup_create, update_quota) + qgroup_create, update_quota, share_pqgroup_assign) from storageadmin.util import handle_exception +from copy import deepcopy import logging + logger = logging.getLogger(__name__) NEW_ENTRY = True @@ -78,20 +80,48 @@ def import_shares(pool, request): # Find the actual/current shares/subvols within the given pool: # Limited to Rockstor relevant subvols ie shares and clones. shares_in_pool = shares_info(pool) + # List of pool's share.pqgroups so we can remove inadvertent duplication. + # All pqgroups are removed when quotas are disabled, combined with a part + # refresh we could have duplicates within the db. + share_pqgroups_used = [] # Delete db Share object if it is no longer found on disk. for s_in_pool_db in shares_in_pool_db: if s_in_pool_db not in shares_in_pool: Share.objects.get(pool=pool, name=s_in_pool_db).delete() # Check if each share in pool also has a db counterpart. for s_in_pool in shares_in_pool: - logger.debug('Share name = {}.'.format(s_in_pool)) + logger.debug('---- Share name = {}.'.format(s_in_pool)) if s_in_pool in shares_in_pool_db: logger.debug('Updating pre-existing same pool db share entry.') # We have a pool db share counterpart so retrieve and update it. share = Share.objects.get(name=s_in_pool, pool=pool) + # Initially default our pqgroup value to db default of '-1/-1' + # This way, unless quotas are enabled, all pqgroups will be + # returned to db default. + pqgroup = settings.MODEL_DEFS['pqgroup'] + if share.pool.quotas_enabled: + # Quotas are enabled on our pool so we can validate pqgroup. + if share.pqgroup == pqgroup or not share.pqgroup_exist \ + or share.pqgroup in share_pqgroups_used: + # we have a void '-1/-1' or non existent pqgroup or + # this pqgroup has already been seen / used in this pool. + logger.debug('#### replacing void, non-existent, or ' + 'duplicate pqgroup') + pqgroup = qgroup_create(pool) + update_quota(pool, pqgroup, share.size * 1024) + share_pqgroup_assign(pqgroup, share) + else: + # Our share's pqgroup looks OK so use it. + pqgroup = share.pqgroup + # Record our use of this pqgroup to spot duplicates later. + share_pqgroups_used.append(deepcopy(share.pqgroup)) + if share.pqgroup != pqgroup: + # we need to update our share.pqgroup + share.pqgroup = pqgroup + share.save() share.qgroup = shares_in_pool[s_in_pool] rusage, eusage, pqgroup_rusage, pqgroup_eusage = \ - volume_usage(pool, share.qgroup, share.pqgroup) + volume_usage(pool, share.qgroup, pqgroup) if (rusage != share.rusage or eusage != share.eusage or pqgroup_rusage != share.pqgroup_rusage or pqgroup_eusage != share.pqgroup_eusage): @@ -137,7 +167,7 @@ def import_shares(pool, request): except Share.DoesNotExist: logger.debug('Db share entry does not exist - creating.') # We have a share on disk that has no db counterpart so create one. - # Retrieve pool quota id for use in db Share object creation. + # Retrieve new pool quota id for use in db Share object creation. pqid = qgroup_create(pool) update_quota(pool, pqid, pool.size * 1024) rusage, eusage, pqgroup_rusage, pqgroup_eusage = \