user_storage_quota now accepts units with value

user_storage_quota now accept value in B, KB, MB, GB
or TB.  The unit is optional. If no unit is specified
Bytes is used as default.

DocImpact

Change-Id: Icc3f672869a5947cbcae38de92993c88ce0ef4e1
Closes-Bug: #1261747
This commit is contained in:
Vaibhav Bhatkar 2014-03-11 09:53:20 +00:00
parent a1b216afb0
commit cff35c1999
6 changed files with 91 additions and 16 deletions

View File

@ -395,8 +395,13 @@ The following configuration option is specified in the
Optional. Default: 0 (Unlimited).
This value specifies the maximum amount of bytes that each user can use
across all storage systems.
This value specifies the maximum amount of storage that each user can use
across all storage systems. Optionally unit can be specified for the value.
Values are accepted in B, KB, MB, GB or TB which are for Bytes, KiloBytes,
MegaBytes, GigaBytes and TeraBytes respectively. Default unit is Bytes.
Example values would be,
user_storage_quota=20GB
Configuring the Filesystem Storage Backend
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -13,11 +13,14 @@
# License for the specific language governing permissions and limitations
# under the License.
import re
from oslo.config import cfg
from glance.common import exception
from glance.openstack.common import excutils
from glance.openstack.common import log as logging
from glance.openstack.common import units
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
@ -99,6 +102,25 @@ def get_remaining_quota(context, db_api, image_id=None):
#NOTE(jbresnah) in the future this value will come from a call to
# keystone.
users_quota = CONF.user_storage_quota
# set quota must have a number optionally followed by B, KB, MB,
# GB or TB without any spaces in between
pattern = re.compile('^(\d+)((K|M|G|T)?B)?$')
match = pattern.match(users_quota)
if not match:
LOG.warn(_("Invalid value for option user_storage_quota: "
"%(users_quota)s")
% {'users_quota': users_quota})
return None
quota_value, quota_unit = (match.groups())[0:2]
# fall back to Bytes if user specified anything other than
# permitted values
quota_unit = quota_unit or "B"
factor = getattr(units, quota_unit.replace('B', 'i'), 1)
users_quota = int(quota_value) * factor
if users_quota <= 0:
return None

View File

@ -99,10 +99,16 @@ common_opts = [
cfg.IntOpt('image_size_cap', default=1099511627776,
help=_("Maximum size of image a user can upload in bytes. "
"Defaults to 1099511627776 bytes (1 TB).")),
cfg.IntOpt('user_storage_quota', default=0,
help=_("Set a system wide quota for every user. This value is "
"the total number of bytes that a user can use across "
"all storage systems. A value of 0 means unlimited.")),
cfg.StrOpt('user_storage_quota', default='0',
help=_("Set a system wide quota for every user. This value is "
"the total capacity that a user can use across "
"all storage systems. A value of 0 means unlimited."
"Optional unit can be specified for the value. Accepted "
"units are B, KB, MB, GB and TB representing "
"Bytes, KiloBytes, MegaBytes, GigaBytes and TeraBytes"
"respectively. If no unit is specified then Bytes is "
"assumed. Note that there should not be any space "
"between value and unit and units are case sensitive.")),
cfg.BoolOpt('enable_v1_api', default=True,
help=_("Deploy the v1 OpenStack Images API.")),
cfg.BoolOpt('enable_v2_api', default=True,

View File

@ -321,7 +321,7 @@ class ApiServer(Server):
default_sql_connection = 'sqlite:////%s/tests.sqlite' % self.test_dir
self.sql_connection = os.environ.get('GLANCE_TEST_SQL_CONNECTION',
default_sql_connection)
self.user_storage_quota = 0
self.user_storage_quota = '0'
self.lock_path = self.test_dir
self.location_strategy = 'location_order'
@ -473,7 +473,7 @@ class RegistryServer(Server):
self.owner_is_tenant = True
self.workers = 0
self.api_version = 1
self.user_storage_quota = 0
self.user_storage_quota = '0'
self.conf_base = """[DEFAULT]
verbose = %(verbose)s

View File

@ -18,6 +18,7 @@ from mock import patch
import uuid
from glance.common import exception
from glance.openstack.common import units
import glance.quota
import glance.store
from glance.tests.unit import utils as unit_test_utils
@ -69,7 +70,7 @@ class TestImageQuota(test_utils.BaseTestCase):
def test_quota_allowed(self):
quota = 10
self.config(user_storage_quota=quota)
self.config(user_storage_quota=str(quota))
context = FakeContext()
db_api = unit_test_utils.FakeDB()
base_image = FakeImage()
@ -80,6 +81,33 @@ class TestImageQuota(test_utils.BaseTestCase):
image.set_data(data)
self.assertEqual(quota, base_image.size)
def _test_quota_allowed_unit(self, data_length, config_quota):
self.config(user_storage_quota=config_quota)
context = FakeContext()
db_api = unit_test_utils.FakeDB()
base_image = FakeImage()
base_image.image_id = 'id'
image = glance.quota.ImageProxy(base_image, context, db_api)
data = '*' * data_length
base_image.set_data(data, size=None)
image.set_data(data)
self.assertEqual(data_length, base_image.size)
def test_quota_allowed_unit_b(self):
self._test_quota_allowed_unit(10, '10B')
def test_quota_allowed_unit_kb(self):
self._test_quota_allowed_unit(10, '1KB')
def test_quota_allowed_unit_mb(self):
self._test_quota_allowed_unit(10, '1MB')
def test_quota_allowed_unit_gb(self):
self._test_quota_allowed_unit(10, '1GB')
def test_quota_allowed_unit_tb(self):
self._test_quota_allowed_unit(10, '1TB')
def _quota_exceeded_size(self, quota, data,
deleted=True, size=None):
self.config(user_storage_quota=quota)
@ -111,17 +139,31 @@ class TestImageQuota(test_utils.BaseTestCase):
# That's why 'get_remaining_quota' is mocked with return_value = 0.
with patch.object(glance.api.common, 'get_remaining_quota',
return_value=0):
self._quota_exceeded_size(quota, data)
self._quota_exceeded_size(str(quota), data)
def test_quota_exceeded_with_right_size(self):
quota = 10
data = '*' * (quota + 1)
self._quota_exceeded_size(quota, data, size=len(data), deleted=False)
self._quota_exceeded_size(str(quota), data, size=len(data),
deleted=False)
def test_quota_exceeded_with_right_size_b(self):
quota = 10
data = '*' * (quota + 1)
self._quota_exceeded_size('10B', data, size=len(data),
deleted=False)
def test_quota_exceeded_with_right_size_kb(self):
quota = units.Ki
data = '*' * (quota + 1)
self._quota_exceeded_size('1KB', data, size=len(data),
deleted=False)
def test_quota_exceeded_with_lie_size(self):
quota = 10
data = '*' * (quota + 1)
self._quota_exceeded_size(quota, data, deleted=False, size=quota - 1)
self._quota_exceeded_size(str(quota), data, deleted=False,
size=quota - 1)
def test_append_location(self):
new_location = {'url': 'file:///a/path', 'metadata': {}}
@ -163,7 +205,7 @@ class TestImageQuota(test_utils.BaseTestCase):
def _make_image_with_quota(self, image_size=10, location_count=2):
quota = image_size * location_count
self.config(user_storage_quota=quota)
self.config(user_storage_quota=str(quota))
return self._get_image(image_size=image_size,
location_count=location_count)

View File

@ -560,7 +560,7 @@ class TestGlanceAPI(base.IsolatedUnitTest):
def test_add_image_size_header_exceed_quota(self):
quota = 500
self.config(user_storage_quota=quota)
self.config(user_storage_quota=str(quota))
fixture_headers = {'x-image-meta-size': quota + 1,
'x-image-meta-name': 'fake image #3',
'x-image-meta-container_format': 'bare',
@ -578,7 +578,7 @@ class TestGlanceAPI(base.IsolatedUnitTest):
def test_add_image_size_data_exceed_quota(self):
quota = 500
self.config(user_storage_quota=quota)
self.config(user_storage_quota=str(quota))
fixture_headers = {
'x-image-meta-name': 'fake image #3',
'x-image-meta-container_format': 'bare',
@ -598,7 +598,7 @@ class TestGlanceAPI(base.IsolatedUnitTest):
def test_add_image_size_data_exceed_quota_readd(self):
quota = 500
self.config(user_storage_quota=quota)
self.config(user_storage_quota=str(quota))
fixture_headers = {
'x-image-meta-name': 'fake image #3',
'x-image-meta-container_format': 'bare',