Implement volume quota support in the cinderclient
* fix for bug 1023311 * Implements quota extensions in cinderclient * Implements absolute limits and rate limits Change-Id: I7e3f8474476cbc03efb2fefcb8400f5fec85ddcb
This commit is contained in:
parent
a153f10b2b
commit
f270f22cb0
@ -80,12 +80,14 @@ You'll find complete documentation on the shell by running
|
|||||||
|
|
||||||
Positional arguments:
|
Positional arguments:
|
||||||
<subcommand>
|
<subcommand>
|
||||||
|
absolute-limits Print a list of absolute limits for a user
|
||||||
create Add a new volume.
|
create Add a new volume.
|
||||||
credentials Show user credentials returned from auth
|
credentials Show user credentials returned from auth
|
||||||
delete Remove a volume.
|
delete Remove a volume.
|
||||||
endpoints Discover endpoints that get returned from the
|
endpoints Discover endpoints that get returned from the
|
||||||
authenticate services
|
authenticate services
|
||||||
list List all the volumes.
|
list List all the volumes.
|
||||||
|
rate-limits Print a list of rate limits for a user
|
||||||
show Show details about a volume.
|
show Show details about a volume.
|
||||||
snapshot-create Add a new snapshot.
|
snapshot-create Add a new snapshot.
|
||||||
snapshot-delete Remove a snapshot.
|
snapshot-delete Remove a snapshot.
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
from cinderclient import client
|
from cinderclient import client
|
||||||
|
from cinderclient.v1 import limits
|
||||||
|
from cinderclient.v1 import quota_classes
|
||||||
|
from cinderclient.v1 import quotas
|
||||||
from cinderclient.v1 import volumes
|
from cinderclient.v1 import volumes
|
||||||
from cinderclient.v1 import volume_snapshots
|
from cinderclient.v1 import volume_snapshots
|
||||||
from cinderclient.v1 import volume_types
|
from cinderclient.v1 import volume_types
|
||||||
@ -6,7 +9,7 @@ from cinderclient.v1 import volume_types
|
|||||||
|
|
||||||
class Client(object):
|
class Client(object):
|
||||||
"""
|
"""
|
||||||
Top-level object to access the OpenStack Compute API.
|
Top-level object to access the OpenStack Volume API.
|
||||||
|
|
||||||
Create an instance with your creds::
|
Create an instance with your creds::
|
||||||
|
|
||||||
@ -26,16 +29,19 @@ class Client(object):
|
|||||||
insecure=False, timeout=None, proxy_tenant_id=None,
|
insecure=False, timeout=None, proxy_tenant_id=None,
|
||||||
proxy_token=None, region_name=None,
|
proxy_token=None, region_name=None,
|
||||||
endpoint_type='publicURL', extensions=None,
|
endpoint_type='publicURL', extensions=None,
|
||||||
service_type='compute', service_name=None,
|
service_type='volume', service_name=None,
|
||||||
volume_service_name=None):
|
volume_service_name=None):
|
||||||
# FIXME(comstud): Rename the api_key argument above when we
|
# FIXME(comstud): Rename the api_key argument above when we
|
||||||
# know it's not being used as keyword argument
|
# know it's not being used as keyword argument
|
||||||
password = api_key
|
password = api_key
|
||||||
|
self.limits = limits.LimitsManager(self)
|
||||||
|
|
||||||
# extensions
|
# extensions
|
||||||
self.volumes = volumes.VolumeManager(self)
|
self.volumes = volumes.VolumeManager(self)
|
||||||
self.volume_snapshots = volume_snapshots.SnapshotManager(self)
|
self.volume_snapshots = volume_snapshots.SnapshotManager(self)
|
||||||
self.volume_types = volume_types.VolumeTypeManager(self)
|
self.volume_types = volume_types.VolumeTypeManager(self)
|
||||||
|
self.quota_classes = quota_classes.QuotaClassSetManager(self)
|
||||||
|
self.quotas = quotas.QuotaSetManager(self)
|
||||||
|
|
||||||
# Add in any extensions...
|
# Add in any extensions...
|
||||||
if extensions:
|
if extensions:
|
||||||
|
79
cinderclient/v1/limits.py
Normal file
79
cinderclient/v1/limits.py
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
# Copyright 2011 OpenStack LLC.
|
||||||
|
|
||||||
|
from cinderclient import base
|
||||||
|
|
||||||
|
|
||||||
|
class Limits(base.Resource):
|
||||||
|
"""A collection of RateLimit and AbsoluteLimit objects"""
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<Limits>"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def absolute(self):
|
||||||
|
for (name, value) in self._info['absolute'].items():
|
||||||
|
yield AbsoluteLimit(name, value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rate(self):
|
||||||
|
for group in self._info['rate']:
|
||||||
|
uri = group['uri']
|
||||||
|
regex = group['regex']
|
||||||
|
for rate in group['limit']:
|
||||||
|
yield RateLimit(rate['verb'], uri, regex, rate['value'],
|
||||||
|
rate['remaining'], rate['unit'],
|
||||||
|
rate['next-available'])
|
||||||
|
|
||||||
|
|
||||||
|
class RateLimit(object):
|
||||||
|
"""Data model that represents a flattened view of a single rate limit"""
|
||||||
|
|
||||||
|
def __init__(self, verb, uri, regex, value, remain,
|
||||||
|
unit, next_available):
|
||||||
|
self.verb = verb
|
||||||
|
self.uri = uri
|
||||||
|
self.regex = regex
|
||||||
|
self.value = value
|
||||||
|
self.remain = remain
|
||||||
|
self.unit = unit
|
||||||
|
self.next_available = next_available
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.uri == other.uri \
|
||||||
|
and self.regex == other.regex \
|
||||||
|
and self.value == other.value \
|
||||||
|
and self.verb == other.verb \
|
||||||
|
and self.remain == other.remain \
|
||||||
|
and self.unit == other.unit \
|
||||||
|
and self.next_available == other.next_available
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<RateLimit: method=%s uri=%s>" % (self.method, self.uri)
|
||||||
|
|
||||||
|
|
||||||
|
class AbsoluteLimit(object):
|
||||||
|
"""Data model that represents a single absolute limit"""
|
||||||
|
|
||||||
|
def __init__(self, name, value):
|
||||||
|
self.name = name
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.value == other.value and self.name == other.name
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<AbsoluteLimit: name=%s>" % (self.name)
|
||||||
|
|
||||||
|
|
||||||
|
class LimitsManager(base.Manager):
|
||||||
|
"""Manager object used to interact with limits resource"""
|
||||||
|
|
||||||
|
resource_class = Limits
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
"""
|
||||||
|
Get a specific extension.
|
||||||
|
|
||||||
|
:rtype: :class:`Limits`
|
||||||
|
"""
|
||||||
|
return self._get("/limits", "limits")
|
52
cinderclient/v1/quota_classes.py
Normal file
52
cinderclient/v1/quota_classes.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# Copyright 2012 OpenStack LLC.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from cinderclient import base
|
||||||
|
|
||||||
|
|
||||||
|
class QuotaClassSet(base.Resource):
|
||||||
|
|
||||||
|
@property
|
||||||
|
def id(self):
|
||||||
|
"""QuotaClassSet does not have a 'id' attribute but base.Resource
|
||||||
|
needs it to self-refresh and QuotaSet is indexed by class_name"""
|
||||||
|
return self.class_name
|
||||||
|
|
||||||
|
def update(self, *args, **kwargs):
|
||||||
|
self.manager.update(self.class_name, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class QuotaClassSetManager(base.ManagerWithFind):
|
||||||
|
resource_class = QuotaClassSet
|
||||||
|
|
||||||
|
def get(self, class_name):
|
||||||
|
return self._get("/os-quota-class-sets/%s" % (class_name),
|
||||||
|
"quota_class_set")
|
||||||
|
|
||||||
|
def update(self,
|
||||||
|
class_name,
|
||||||
|
volumes=None,
|
||||||
|
gigabytes=None):
|
||||||
|
|
||||||
|
body = {'quota_class_set': {
|
||||||
|
'class_name': class_name,
|
||||||
|
'volumes': volumes,
|
||||||
|
'gigabytes': gigabytes}}
|
||||||
|
|
||||||
|
for key in body['quota_class_set'].keys():
|
||||||
|
if body['quota_class_set'][key] is None:
|
||||||
|
body['quota_class_set'].pop(key)
|
||||||
|
|
||||||
|
self._update('/os-quota-class-sets/%s' % (class_name), body)
|
54
cinderclient/v1/quotas.py
Normal file
54
cinderclient/v1/quotas.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
# Copyright 2011 OpenStack LLC.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from cinderclient import base
|
||||||
|
|
||||||
|
|
||||||
|
class QuotaSet(base.Resource):
|
||||||
|
|
||||||
|
@property
|
||||||
|
def id(self):
|
||||||
|
"""QuotaSet does not have a 'id' attribute but base.Resource needs it
|
||||||
|
to self-refresh and QuotaSet is indexed by tenant_id"""
|
||||||
|
return self.tenant_id
|
||||||
|
|
||||||
|
def update(self, *args, **kwargs):
|
||||||
|
self.manager.update(self.tenant_id, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class QuotaSetManager(base.ManagerWithFind):
|
||||||
|
resource_class = QuotaSet
|
||||||
|
|
||||||
|
def get(self, tenant_id):
|
||||||
|
if hasattr(tenant_id, 'tenant_id'):
|
||||||
|
tenant_id = tenant_id.tenant_id
|
||||||
|
return self._get("/os-quota-sets/%s" % (tenant_id), "quota_set")
|
||||||
|
|
||||||
|
def update(self, tenant_id, volumes=None, gigabytes=None):
|
||||||
|
|
||||||
|
body = {'quota_set': {
|
||||||
|
'tenant_id': tenant_id,
|
||||||
|
'volumes': volumes,
|
||||||
|
'gigabytes': gigabytes}}
|
||||||
|
|
||||||
|
for key in body['quota_set'].keys():
|
||||||
|
if body['quota_set'][key] is None:
|
||||||
|
body['quota_set'].pop(key)
|
||||||
|
|
||||||
|
self._update('/os-quota-sets/%s' % (tenant_id), body)
|
||||||
|
|
||||||
|
def defaults(self, tenant_id):
|
||||||
|
return self._get('/os-quota-sets/%s/defaults' % tenant_id,
|
||||||
|
'quota_set')
|
@ -346,3 +346,100 @@ def do_credentials(cs, args):
|
|||||||
catalog = cs.client.service_catalog.catalog
|
catalog = cs.client.service_catalog.catalog
|
||||||
utils.print_dict(catalog['access']['user'], "User Credentials")
|
utils.print_dict(catalog['access']['user'], "User Credentials")
|
||||||
utils.print_dict(catalog['access']['token'], "Token")
|
utils.print_dict(catalog['access']['token'], "Token")
|
||||||
|
|
||||||
|
_quota_resources = ['volumes', 'gigabytes']
|
||||||
|
|
||||||
|
|
||||||
|
def _quota_show(quotas):
|
||||||
|
quota_dict = {}
|
||||||
|
for resource in _quota_resources:
|
||||||
|
quota_dict[resource] = getattr(quotas, resource, None)
|
||||||
|
utils.print_dict(quota_dict)
|
||||||
|
|
||||||
|
|
||||||
|
def _quota_update(manager, identifier, args):
|
||||||
|
updates = {}
|
||||||
|
for resource in _quota_resources:
|
||||||
|
val = getattr(args, resource, None)
|
||||||
|
if val is not None:
|
||||||
|
updates[resource] = val
|
||||||
|
|
||||||
|
if updates:
|
||||||
|
manager.update(identifier, **updates)
|
||||||
|
|
||||||
|
|
||||||
|
@utils.arg('tenant', metavar='<tenant_id>',
|
||||||
|
help='UUID of tenant to list the quotas for.')
|
||||||
|
@utils.service_type('volume')
|
||||||
|
def do_quota_show(cs, args):
|
||||||
|
"""List the quotas for a tenant."""
|
||||||
|
|
||||||
|
_quota_show(cs.quotas.get(args.tenant))
|
||||||
|
|
||||||
|
|
||||||
|
@utils.arg('tenant', metavar='<tenant_id>',
|
||||||
|
help='UUID of tenant to list the default quotas for.')
|
||||||
|
@utils.service_type('volume')
|
||||||
|
def do_quota_defaults(cs, args):
|
||||||
|
"""List the default quotas for a tenant."""
|
||||||
|
|
||||||
|
_quota_show(cs.quotas.defaults(args.tenant))
|
||||||
|
|
||||||
|
|
||||||
|
@utils.arg('tenant', metavar='<tenant_id>',
|
||||||
|
help='UUID of tenant to set the quotas for.')
|
||||||
|
@utils.arg('--volumes',
|
||||||
|
metavar='<volumes>',
|
||||||
|
type=int, default=None,
|
||||||
|
help='New value for the "volumes" quota.')
|
||||||
|
@utils.arg('--gigabytes',
|
||||||
|
metavar='<gigabytes>',
|
||||||
|
type=int, default=None,
|
||||||
|
help='New value for the "gigabytes" quota.')
|
||||||
|
@utils.service_type('volume')
|
||||||
|
def do_quota_update(cs, args):
|
||||||
|
"""Update the quotas for a tenant."""
|
||||||
|
|
||||||
|
_quota_update(cs.quotas, args.tenant, args)
|
||||||
|
|
||||||
|
|
||||||
|
@utils.arg('class_name', metavar='<class>',
|
||||||
|
help='Name of quota class to list the quotas for.')
|
||||||
|
@utils.service_type('volume')
|
||||||
|
def do_quota_class_show(cs, args):
|
||||||
|
"""List the quotas for a quota class."""
|
||||||
|
|
||||||
|
_quota_show(cs.quota_classes.get(args.class_name))
|
||||||
|
|
||||||
|
|
||||||
|
@utils.arg('class_name', metavar='<class>',
|
||||||
|
help='Name of quota class to set the quotas for.')
|
||||||
|
@utils.arg('--volumes',
|
||||||
|
metavar='<volumes>',
|
||||||
|
type=int, default=None,
|
||||||
|
help='New value for the "volumes" quota.')
|
||||||
|
@utils.arg('--gigabytes',
|
||||||
|
metavar='<gigabytes>',
|
||||||
|
type=int, default=None,
|
||||||
|
help='New value for the "gigabytes" quota.')
|
||||||
|
@utils.service_type('volume')
|
||||||
|
def do_quota_class_update(cs, args):
|
||||||
|
"""Update the quotas for a quota class."""
|
||||||
|
|
||||||
|
_quota_update(cs.quota_classes, args.class_name, args)
|
||||||
|
|
||||||
|
|
||||||
|
@utils.service_type('volume')
|
||||||
|
def do_absolute_limits(cs, args):
|
||||||
|
"""Print a list of absolute limits for a user"""
|
||||||
|
limits = cs.limits.get().absolute
|
||||||
|
columns = ['Name', 'Value']
|
||||||
|
utils.print_list(limits, columns)
|
||||||
|
|
||||||
|
|
||||||
|
@utils.service_type('volume')
|
||||||
|
def do_rate_limits(cs, args):
|
||||||
|
"""Print a list of rate limits for a user"""
|
||||||
|
limits = cs.limits.get().rate
|
||||||
|
columns = ['Verb', 'URI', 'Value', 'Remain', 'Unit', 'Next_Available']
|
||||||
|
utils.print_list(limits, columns)
|
||||||
|
52
tests/v1/test_limits.py
Normal file
52
tests/v1/test_limits.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
|
||||||
|
from cinderclient.v1 import limits
|
||||||
|
from tests import utils
|
||||||
|
from tests.v1 import fakes
|
||||||
|
|
||||||
|
|
||||||
|
cs = fakes.FakeClient()
|
||||||
|
|
||||||
|
|
||||||
|
class LimitsTest(utils.TestCase):
|
||||||
|
|
||||||
|
def test_get_limits(self):
|
||||||
|
obj = cs.limits.get()
|
||||||
|
cs.assert_called('GET', '/limits')
|
||||||
|
self.assertTrue(isinstance(obj, limits.Limits))
|
||||||
|
|
||||||
|
def test_absolute_limits(self):
|
||||||
|
obj = cs.limits.get()
|
||||||
|
|
||||||
|
expected = (
|
||||||
|
limits.AbsoluteLimit("maxTotalRAMSize", 51200),
|
||||||
|
limits.AbsoluteLimit("maxServerMeta", 5),
|
||||||
|
limits.AbsoluteLimit("maxImageMeta", 5),
|
||||||
|
limits.AbsoluteLimit("maxPersonality", 5),
|
||||||
|
limits.AbsoluteLimit("maxPersonalitySize", 10240),
|
||||||
|
)
|
||||||
|
|
||||||
|
abs_limits = list(obj.absolute)
|
||||||
|
self.assertEqual(len(abs_limits), len(expected))
|
||||||
|
|
||||||
|
for limit in abs_limits:
|
||||||
|
self.assertTrue(limit in expected)
|
||||||
|
|
||||||
|
def test_rate_limits(self):
|
||||||
|
obj = cs.limits.get()
|
||||||
|
|
||||||
|
expected = (
|
||||||
|
limits.RateLimit('POST', '*', '.*', 10, 2, 'MINUTE',
|
||||||
|
'2011-12-15T22:42:45Z'),
|
||||||
|
limits.RateLimit('PUT', '*', '.*', 10, 2, 'MINUTE',
|
||||||
|
'2011-12-15T22:42:45Z'),
|
||||||
|
limits.RateLimit('DELETE', '*', '.*', 100, 100, 'MINUTE',
|
||||||
|
'2011-12-15T22:42:45Z'),
|
||||||
|
limits.RateLimit('POST', '*/servers', '^/servers', 25, 24, 'DAY',
|
||||||
|
'2011-12-15T22:42:45Z'),
|
||||||
|
)
|
||||||
|
|
||||||
|
rate_limits = list(obj.rate)
|
||||||
|
self.assertEqual(len(rate_limits), len(expected))
|
||||||
|
|
||||||
|
for limit in rate_limits:
|
||||||
|
self.assertTrue(limit in expected)
|
42
tests/v1/test_quota_classes.py
Normal file
42
tests/v1/test_quota_classes.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# Copyright 2011 OpenStack LLC.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from tests import utils
|
||||||
|
from tests.v1 import fakes
|
||||||
|
|
||||||
|
|
||||||
|
cs = fakes.FakeClient()
|
||||||
|
|
||||||
|
|
||||||
|
class QuotaClassSetsTest(utils.TestCase):
|
||||||
|
|
||||||
|
def test_class_quotas_get(self):
|
||||||
|
class_name = 'test'
|
||||||
|
cs.quota_classes.get(class_name)
|
||||||
|
cs.assert_called('GET', '/os-quota-class-sets/%s' % class_name)
|
||||||
|
|
||||||
|
def test_update_quota(self):
|
||||||
|
q = cs.quota_classes.get('test')
|
||||||
|
q.update(volumes=2)
|
||||||
|
cs.assert_called('PUT', '/os-quota-class-sets/test')
|
||||||
|
|
||||||
|
def test_refresh_quota(self):
|
||||||
|
q = cs.quota_classes.get('test')
|
||||||
|
q2 = cs.quota_classes.get('test')
|
||||||
|
self.assertEqual(q.volumes, q2.volumes)
|
||||||
|
q2.volumes = 0
|
||||||
|
self.assertNotEqual(q.volumes, q2.volumes)
|
||||||
|
q2.get()
|
||||||
|
self.assertEqual(q.volumes, q2.volumes)
|
47
tests/v1/test_quotas.py
Normal file
47
tests/v1/test_quotas.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# Copyright 2011 OpenStack LLC.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from tests import utils
|
||||||
|
from tests.v1 import fakes
|
||||||
|
|
||||||
|
|
||||||
|
cs = fakes.FakeClient()
|
||||||
|
|
||||||
|
|
||||||
|
class QuotaSetsTest(utils.TestCase):
|
||||||
|
|
||||||
|
def test_tenant_quotas_get(self):
|
||||||
|
tenant_id = 'test'
|
||||||
|
cs.quotas.get(tenant_id)
|
||||||
|
cs.assert_called('GET', '/os-quota-sets/%s' % tenant_id)
|
||||||
|
|
||||||
|
def test_tenant_quotas_defaults(self):
|
||||||
|
tenant_id = 'test'
|
||||||
|
cs.quotas.defaults(tenant_id)
|
||||||
|
cs.assert_called('GET', '/os-quota-sets/%s/defaults' % tenant_id)
|
||||||
|
|
||||||
|
def test_update_quota(self):
|
||||||
|
q = cs.quotas.get('test')
|
||||||
|
q.update(volumes=2)
|
||||||
|
cs.assert_called('PUT', '/os-quota-sets/test')
|
||||||
|
|
||||||
|
def test_refresh_quota(self):
|
||||||
|
q = cs.quotas.get('test')
|
||||||
|
q2 = cs.quotas.get('test')
|
||||||
|
self.assertEqual(q.volumes, q2.volumes)
|
||||||
|
q2.volumes = 0
|
||||||
|
self.assertNotEqual(q.volumes, q2.volumes)
|
||||||
|
q2.get()
|
||||||
|
self.assertEqual(q.volumes, q2.volumes)
|
Loading…
Reference in New Issue
Block a user