Add magnum client support for resource quotas
Change-Id: I5fd050a4deedb84c7287f75cf8c606e8c0d4db9a Closes-Bug: #1659125
This commit is contained in:
parent
01b130f255
commit
7937f06a64
152
magnumclient/tests/v1/test_quotas.py
Normal file
152
magnumclient/tests/v1/test_quotas.py
Normal file
@ -0,0 +1,152 @@
|
||||
# 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.
|
||||
|
||||
import copy
|
||||
|
||||
import testtools
|
||||
from testtools import matchers
|
||||
|
||||
from magnumclient.tests import utils
|
||||
from magnumclient.v1 import quotas
|
||||
|
||||
|
||||
QUOTA1 = {
|
||||
'id': 123,
|
||||
'resource': "Cluster",
|
||||
'hard_limit': 5,
|
||||
'project_id': 'abc'
|
||||
}
|
||||
|
||||
QUOTA2 = {
|
||||
'id': 124,
|
||||
'resource': "Cluster",
|
||||
'hard_limit': 10,
|
||||
'project_id': 'bcd'
|
||||
}
|
||||
|
||||
CREATE_QUOTA = copy.deepcopy(QUOTA1)
|
||||
del CREATE_QUOTA['id']
|
||||
|
||||
UPDATED_QUOTA = copy.deepcopy(QUOTA2)
|
||||
NEW_HARD_LIMIT = 20
|
||||
UPDATED_QUOTA['hard_limit'] = NEW_HARD_LIMIT
|
||||
|
||||
fake_responses = {
|
||||
'/v1/quotas?all_tenants=True':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'quotas': [QUOTA1, QUOTA2]},
|
||||
),
|
||||
},
|
||||
'/v1/quotas':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{'quotas': [QUOTA1]},
|
||||
),
|
||||
'POST': (
|
||||
{},
|
||||
QUOTA1,
|
||||
),
|
||||
},
|
||||
'/v1/quotas/%(id)s/%(res)s' % {'id': QUOTA2['project_id'],
|
||||
'res': QUOTA2['resource']}:
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
QUOTA2,
|
||||
),
|
||||
'PATCH': (
|
||||
{},
|
||||
UPDATED_QUOTA,
|
||||
),
|
||||
'DELETE': (
|
||||
{},
|
||||
None,
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class QuotasManagerTest(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(QuotasManagerTest, self).setUp()
|
||||
self.api = utils.FakeAPI(fake_responses)
|
||||
self.mgr = quotas.QuotasManager(self.api)
|
||||
|
||||
def test_list_quotas(self):
|
||||
quotas = self.mgr.list()
|
||||
expect = [
|
||||
('GET', '/v1/quotas', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(quotas, matchers.HasLength(1))
|
||||
|
||||
def test_list_quotas_all(self):
|
||||
quotas = self.mgr.list(all_tenants=True)
|
||||
expect = [
|
||||
('GET', '/v1/quotas?all_tenants=True', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(quotas, matchers.HasLength(2))
|
||||
|
||||
def test_show_project_resource_quota(self):
|
||||
expect = [
|
||||
('GET',
|
||||
'/v1/quotas/%(id)s/%(res)s' % {'id': QUOTA2['project_id'],
|
||||
'res': QUOTA2['resource']},
|
||||
{},
|
||||
None),
|
||||
]
|
||||
quotas = self.mgr.get(QUOTA2['project_id'], QUOTA2['resource'])
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
expected_quotas = QUOTA2
|
||||
self.assertEqual(expected_quotas, quotas._info)
|
||||
|
||||
def test_quota_create(self):
|
||||
quota = self.mgr.create(**CREATE_QUOTA)
|
||||
expect = [
|
||||
('POST', '/v1/quotas', {}, CREATE_QUOTA),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(QUOTA1, quota._info)
|
||||
|
||||
def test_quota_update(self):
|
||||
patch = {
|
||||
'resource': "Cluster",
|
||||
'hard_limit': NEW_HARD_LIMIT,
|
||||
'project_id': 'bcd'
|
||||
}
|
||||
quota = self.mgr.update(id=QUOTA2['project_id'],
|
||||
resource=QUOTA2['resource'],
|
||||
patch=patch)
|
||||
expect = [
|
||||
('PATCH', '/v1/quotas/%(id)s/%(res)s' % {
|
||||
'id': QUOTA2['project_id'],
|
||||
'res': QUOTA2['resource']}, {}, patch),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(NEW_HARD_LIMIT, quota.hard_limit)
|
||||
|
||||
def test_quota_delete(self):
|
||||
quota = self.mgr.delete(QUOTA2['project_id'], QUOTA2['resource'])
|
||||
expect = [
|
||||
('DELETE',
|
||||
'/v1/quotas/%(id)s/%(res)s' % {'id': QUOTA2['project_id'],
|
||||
'res': QUOTA2['resource']},
|
||||
{},
|
||||
None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertIsNone(quota)
|
117
magnumclient/tests/v1/test_quotas_shell.py
Normal file
117
magnumclient/tests/v1/test_quotas_shell.py
Normal file
@ -0,0 +1,117 @@
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
|
||||
from magnumclient.tests.v1 import shell_test_base
|
||||
|
||||
|
||||
class ShellTest(shell_test_base.TestCommandLineArgument):
|
||||
|
||||
def _get_expected_args_list(self, marker=None, limit=None, sort_dir=None,
|
||||
sort_key=None, all_tenants=False):
|
||||
expected_args = {}
|
||||
expected_args['marker'] = marker
|
||||
expected_args['limit'] = limit
|
||||
expected_args['sort_dir'] = sort_dir
|
||||
expected_args['sort_key'] = sort_key
|
||||
expected_args['all_tenants'] = False
|
||||
return expected_args
|
||||
|
||||
def _get_expected_args_create(self, project_id, resource, hard_limit):
|
||||
expected_args = {}
|
||||
expected_args['project_id'] = project_id
|
||||
expected_args['resource'] = resource
|
||||
expected_args['hard_limit'] = hard_limit
|
||||
return expected_args
|
||||
|
||||
@mock.patch('magnumclient.v1.quotas.QuotasManager.list')
|
||||
def test_quotas_list_success(self, mock_list):
|
||||
self._test_arg_success('quotas-list')
|
||||
expected_args = self._get_expected_args_list()
|
||||
mock_list.assert_called_once_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.quotas.QuotasManager.list')
|
||||
def test_quotas_list_failure(self, mock_list):
|
||||
self._test_arg_failure('quotas-list --wrong',
|
||||
self._unrecognized_arg_error)
|
||||
mock_list.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.quotas.QuotasManager.create')
|
||||
def test_quotas_create_success(self, mock_create):
|
||||
self._test_arg_success('quotas-create --project-id abc '
|
||||
'--resource Cluster '
|
||||
'--hard-limit 15')
|
||||
expected_args = self._get_expected_args_create('abc', 'Cluster', 15)
|
||||
mock_create.assert_called_with(**expected_args)
|
||||
|
||||
@mock.patch('magnumclient.v1.quotas.QuotasManager.create')
|
||||
def test_quotas_create_failure_only_project_id(self, mock_create):
|
||||
self._test_arg_failure('quotas-create --project-id abc',
|
||||
self._mandatory_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.quotas.QuotasManager.create')
|
||||
def test_quotas_create_failure_only_resource(self, mock_create):
|
||||
self._test_arg_failure('quotas-create --resource Cluster',
|
||||
self._mandatory_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.quotas.QuotasManager.create')
|
||||
def test_quotas_create_failure_only_hard_limit(self, mock_create):
|
||||
self._test_arg_failure('quotas-create --hard-limit 10',
|
||||
self._mandatory_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.quotas.QuotasManager.create')
|
||||
def test_quotas_create_failure_no_arg(self, mock_create):
|
||||
self._test_arg_failure('quotas-create',
|
||||
self._mandatory_arg_error)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.quotas.QuotasManager.delete')
|
||||
def test_quotas_delete_success(self, mock_delete):
|
||||
self._test_arg_success(
|
||||
'quotas-delete --project-id xxx --resource Cluster')
|
||||
mock_delete.assert_called_once_with('xxx', 'Cluster')
|
||||
|
||||
@mock.patch('magnumclient.v1.quotas.QuotasManager.delete')
|
||||
def test_quotas_delete_failure_no_project_id(self, mock_delete):
|
||||
self._test_arg_failure('quotas-delete --resource Cluster',
|
||||
self._mandatory_arg_error)
|
||||
mock_delete.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.quotas.QuotasManager.delete')
|
||||
def test_quotas_delete_failure_no_resource(self, mock_delete):
|
||||
self._test_arg_failure('quotas-delete --project-id xxx',
|
||||
self._mandatory_arg_error)
|
||||
mock_delete.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.quotas.QuotasManager.get')
|
||||
def test_quotas_show_success(self, mock_show):
|
||||
self._test_arg_success('quotas-show --project-id abc '
|
||||
'--resource Cluster')
|
||||
mock_show.assert_called_once_with('abc', 'Cluster')
|
||||
|
||||
@mock.patch('magnumclient.v1.quotas.QuotasManager.get')
|
||||
def test_quotas_show_failure_no_arg(self, mock_show):
|
||||
self._test_arg_failure('quotas-show',
|
||||
self._mandatory_arg_error)
|
||||
mock_show.assert_not_called()
|
||||
|
||||
@mock.patch('magnumclient.v1.quotas.QuotasManager.update')
|
||||
def test_quotas_update_success(self, mock_update):
|
||||
self._test_arg_success('quotas-update --project-id abc '
|
||||
'--resource Cluster '
|
||||
'--hard-limit 20')
|
||||
patch = {'project_id': 'abc', 'resource': 'Cluster', 'hard_limit': 20}
|
||||
mock_update.assert_called_once_with('abc', 'Cluster', patch)
|
@ -25,6 +25,7 @@ from magnumclient.v1 import certificates
|
||||
from magnumclient.v1 import cluster_templates
|
||||
from magnumclient.v1 import clusters
|
||||
from magnumclient.v1 import mservices
|
||||
from magnumclient.v1 import quotas
|
||||
from magnumclient.v1 import stats
|
||||
|
||||
profiler = importutils.try_import("osprofiler.profiler")
|
||||
@ -213,3 +214,4 @@ class Client(object):
|
||||
# initialization of osprofiler on the server side.
|
||||
profiler.init(profile)
|
||||
self.stats = stats.StatsManager(self.http_client)
|
||||
self.quotas = quotas.QuotasManager(self.http_client)
|
||||
|
78
magnumclient/v1/quotas.py
Normal file
78
magnumclient/v1/quotas.py
Normal file
@ -0,0 +1,78 @@
|
||||
# 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 magnumclient.common import utils
|
||||
from magnumclient import exceptions
|
||||
from magnumclient.v1 import basemodels
|
||||
|
||||
|
||||
CREATION_ATTRIBUTES = ['project_id', 'resource', 'hard_limit']
|
||||
|
||||
|
||||
class Quotas(basemodels.BaseModel):
|
||||
model_name = "Quotas"
|
||||
|
||||
|
||||
class QuotasManager(basemodels.BaseModelManager):
|
||||
api_name = "quotas"
|
||||
resource_class = Quotas
|
||||
|
||||
@staticmethod
|
||||
def _path(id=None, resource=None):
|
||||
if not id:
|
||||
return '/v1/quotas'
|
||||
|
||||
return '/v1/quotas/%(id)s/%(res)s' % {'id': id, 'res': resource}
|
||||
|
||||
def list(self, limit=None, marker=None, sort_key=None,
|
||||
sort_dir=None, all_tenants=False):
|
||||
|
||||
if limit is not None:
|
||||
limit = int(limit)
|
||||
|
||||
filters = utils.common_filters(marker, limit, sort_key, sort_dir)
|
||||
|
||||
if all_tenants:
|
||||
filters.append('all_tenants=True')
|
||||
|
||||
path = self._path()
|
||||
if filters:
|
||||
path += '?' + '&'.join(filters)
|
||||
|
||||
if limit is None:
|
||||
return self._list(path, self.api_name)
|
||||
else:
|
||||
return self._list_pagination(path, self.api_name,
|
||||
limit=limit)
|
||||
|
||||
def get(self, id, resource):
|
||||
try:
|
||||
return self._list(self._path(id, resource))[0]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
def create(self, **kwargs):
|
||||
new = {}
|
||||
for (key, value) in kwargs.items():
|
||||
if key in CREATION_ATTRIBUTES:
|
||||
new[key] = value
|
||||
else:
|
||||
raise exceptions.InvalidAttribute(
|
||||
"Key must be in %s" % ",".join(CREATION_ATTRIBUTES))
|
||||
return self._create(self._path(), new)
|
||||
|
||||
def delete(self, id, resource):
|
||||
return self._delete(self._path(id, resource))
|
||||
|
||||
def update(self, id, resource, patch):
|
||||
url = self._path(id, resource)
|
||||
return self._update(url, patch)
|
143
magnumclient/v1/quotas_shell.py
Normal file
143
magnumclient/v1/quotas_shell.py
Normal file
@ -0,0 +1,143 @@
|
||||
# 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 magnumclient.common import cliutils as utils
|
||||
from magnumclient.common import utils as magnum_utils
|
||||
from magnumclient.i18n import _
|
||||
|
||||
|
||||
def _show_quota(quota):
|
||||
utils.print_dict(quota._info)
|
||||
|
||||
|
||||
@utils.arg('--marker',
|
||||
metavar='<marker>',
|
||||
default=None,
|
||||
help=_('The last quota UUID of the previous page; '
|
||||
'displays list of quotas after "marker".'))
|
||||
@utils.arg('--limit',
|
||||
metavar='<limit>',
|
||||
type=int,
|
||||
help=_('Maximum number of quotas to return.'))
|
||||
@utils.arg('--sort-key',
|
||||
metavar='<sort-key>',
|
||||
help=_('Column to sort results by.'))
|
||||
@utils.arg('--sort-dir',
|
||||
metavar='<sort-dir>',
|
||||
choices=['desc', 'asc'],
|
||||
help=_('Direction to sort. "asc" or "desc".'))
|
||||
@utils.arg('--all-tenants',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=_('Flag to indicate list all tenant quotas.'))
|
||||
def do_quotas_list(cs, args):
|
||||
"""Print a list of available quotas."""
|
||||
quotas = cs.quotas.list(marker=args.marker,
|
||||
limit=args.limit,
|
||||
sort_key=args.sort_key,
|
||||
sort_dir=args.sort_dir,
|
||||
all_tenants=args.all_tenants)
|
||||
columns = ['project_id', 'resource', 'hard_limit']
|
||||
utils.print_list(quotas, columns,
|
||||
{'versions': magnum_utils.print_list_field('versions')},
|
||||
sortby_index=None)
|
||||
|
||||
|
||||
@utils.arg('--project-id',
|
||||
required=True,
|
||||
metavar='<project-id>',
|
||||
help=_('Project Id.'))
|
||||
@utils.arg('--resource',
|
||||
required=True,
|
||||
metavar='<resource>',
|
||||
help=_('Resource name.'))
|
||||
@utils.arg('--hard-limit',
|
||||
metavar='<hard-limit>',
|
||||
type=int,
|
||||
default=1,
|
||||
help=_('Max resource limit.'))
|
||||
def do_quotas_create(cs, args):
|
||||
"""Create a quota."""
|
||||
|
||||
opts = dict()
|
||||
opts['project_id'] = args.project_id
|
||||
opts['resource'] = args.resource
|
||||
opts['hard_limit'] = args.hard_limit
|
||||
|
||||
try:
|
||||
quota = cs.quotas.create(**opts)
|
||||
_show_quota(quota)
|
||||
except Exception as e:
|
||||
print("Create quota for project_id %(id)s resource %(res)s failed: "
|
||||
"%(e)s" % {'id': args.project_id,
|
||||
'res': args.resource,
|
||||
'e': e.details})
|
||||
|
||||
|
||||
@utils.arg('--project-id',
|
||||
required=True,
|
||||
metavar='<project-id>',
|
||||
help=_('Project ID.'))
|
||||
@utils.arg('--resource',
|
||||
required=True,
|
||||
metavar='<resource>',
|
||||
help=_('Resource name'))
|
||||
def do_quotas_delete(cs, args):
|
||||
"""Delete specified resource quota."""
|
||||
try:
|
||||
cs.quotas.delete(args.project_id, args.resource)
|
||||
print("Request to delete quota for project id %(id)s and resource "
|
||||
"%(res)s has been accepted." % {
|
||||
'id': args.project_id, 'res': args.resource})
|
||||
except Exception as e:
|
||||
print("Quota delete failed for project id %(id)s and resource "
|
||||
"%(res)s :%(e)s" % {'id': args.project_id,
|
||||
'res': args.resource,
|
||||
'e': e.details})
|
||||
|
||||
|
||||
@utils.arg('--project-id',
|
||||
required=True,
|
||||
metavar='<project-id>',
|
||||
help=_('Project ID.'))
|
||||
@utils.arg('--resource',
|
||||
required=True,
|
||||
metavar='<resource>',
|
||||
help=_('Resource name'))
|
||||
def do_quotas_show(cs, args):
|
||||
"""Show details about the given project resource quota."""
|
||||
quota = cs.quotas.get(args.project_id, args.resource)
|
||||
_show_quota(quota)
|
||||
|
||||
|
||||
@utils.arg('--project-id',
|
||||
required=True,
|
||||
metavar='<project-id>',
|
||||
help=_('Project Id.'))
|
||||
@utils.arg('--resource',
|
||||
required=True,
|
||||
metavar='<resource>',
|
||||
help=_('Resource name.'))
|
||||
@utils.arg('--hard-limit',
|
||||
metavar='<hard-limit>',
|
||||
type=int,
|
||||
default=1,
|
||||
help=_('Max resource limit.'))
|
||||
def do_quotas_update(cs, args):
|
||||
"""Update information about the given project resource quota."""
|
||||
patch = dict()
|
||||
patch['project_id'] = args.project_id
|
||||
patch['resource'] = args.resource
|
||||
patch['hard_limit'] = args.hard_limit
|
||||
|
||||
quota = cs.quotas.update(args.project_id, args.resource, patch)
|
||||
_show_quota(quota)
|
@ -19,6 +19,7 @@ from magnumclient.v1 import certificates_shell
|
||||
from magnumclient.v1 import cluster_templates_shell
|
||||
from magnumclient.v1 import clusters_shell
|
||||
from magnumclient.v1 import mservices_shell
|
||||
from magnumclient.v1 import quotas_shell
|
||||
from magnumclient.v1 import stats_shell
|
||||
|
||||
COMMAND_MODULES = [
|
||||
@ -29,4 +30,5 @@ COMMAND_MODULES = [
|
||||
cluster_templates_shell,
|
||||
mservices_shell,
|
||||
stats_shell,
|
||||
quotas_shell,
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user