Add magnum client support for resource quotas

Change-Id: I5fd050a4deedb84c7287f75cf8c606e8c0d4db9a
Closes-Bug: #1659125
This commit is contained in:
Vijendar Komalla 2017-01-27 09:08:55 -06:00 committed by Vijendar Komalla
parent 01b130f255
commit 7937f06a64
6 changed files with 494 additions and 0 deletions

View 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)

View 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)

View File

@ -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
View 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)

View 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)

View File

@ -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,
]