Microversion 2.50 - fix quota class sets resource usage
This adds support for the 2.50 microversion which does the following: * Adds the server_groups and server_groups_members resources to the output for the 'nova quota-class-show' and 'nova quota-class-update' CLIs. * Removes the ability to show or update network-related resource quota class values, specifically floating_ips, fixed_ips, security_groups and security_group_members. * Defines explicit kwargs for the update() method in the python API binding. This also fixes a problem where the 'nova quota-class-update' CLI was incorrectly capped at the 2.35 microversion for updating network-related resources. That was true for the os-quota-sets API which is tenant-specific, but not for the os-quota-class-sets API which is global. Functional tests are added for the 2.1 and 2.50 microversion behavior for both commands. Part of blueprint fix-quota-classes-api Change-Id: I2531f9094d92e1b9ed36ab03bc43ae1be5290790
This commit is contained in:
parent
77f940c534
commit
5bfa57a433
@ -25,4 +25,4 @@ API_MIN_VERSION = api_versions.APIVersion("2.1")
|
||||
# when client supported the max version, and bumped sequentially, otherwise
|
||||
# the client may break due to server side new version may include some
|
||||
# backward incompatible change.
|
||||
API_MAX_VERSION = api_versions.APIVersion("2.49")
|
||||
API_MAX_VERSION = api_versions.APIVersion("2.50")
|
||||
|
133
novaclient/tests/functional/v2/test_quota_classes.py
Normal file
133
novaclient/tests/functional/v2/test_quota_classes.py
Normal file
@ -0,0 +1,133 @@
|
||||
# Copyright 2017 Huawei Technologies Co.,LTD.
|
||||
#
|
||||
# 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 tempest.lib import exceptions
|
||||
|
||||
from novaclient.tests.functional import base
|
||||
|
||||
|
||||
class TestQuotaClassesNovaClient(base.ClientTestBase):
|
||||
"""Nova quota classes functional tests for the v2.1 microversion."""
|
||||
|
||||
COMPUTE_API_VERSION = '2.1'
|
||||
|
||||
# The list of quota class resources we expect in the output table.
|
||||
_included_resources = ['instances', 'cores', 'ram',
|
||||
'floating_ips', 'fixed_ips', 'metadata_items',
|
||||
'injected_files', 'injected_file_content_bytes',
|
||||
'injected_file_path_bytes', 'key_pairs',
|
||||
'security_groups', 'security_group_rules']
|
||||
|
||||
# The list of quota class resources we do not expect in the output table.
|
||||
_excluded_resources = ['server_groups', 'server_group_members']
|
||||
|
||||
# Any resources that are not shown but can be updated. For example, before
|
||||
# microversion 2.50 you can update server_groups and server_groups_members
|
||||
# quota class values but they are not shown in the GET response.
|
||||
_extra_update_resources = _excluded_resources
|
||||
|
||||
# The list of resources which are blocked from being updated.
|
||||
_blocked_update_resources = []
|
||||
|
||||
def _get_quota_class_name(self):
|
||||
"""Returns a fake quota class name specific to this test class."""
|
||||
return 'fake-class-%s' % self.COMPUTE_API_VERSION.replace('.', '-')
|
||||
|
||||
def _verify_qouta_class_show_output(self, output, expected_values):
|
||||
# Assert that the expected key/value pairs are in the output table
|
||||
for quota_name in self._included_resources:
|
||||
# First make sure the resource is actually in expected quota.
|
||||
self.assertIn(quota_name, expected_values)
|
||||
expected_value = expected_values[quota_name]
|
||||
actual_value = self._get_value_from_the_table(output, quota_name)
|
||||
self.assertEqual(expected_value, actual_value)
|
||||
|
||||
# Now make sure anything that we don't expect in the output table is
|
||||
# actually not showing up.
|
||||
for quota_name in self._excluded_resources:
|
||||
# ValueError is raised when the key isn't found in the table.
|
||||
self.assertRaises(ValueError,
|
||||
self._get_value_from_the_table,
|
||||
output, quota_name)
|
||||
|
||||
def test_quota_class_show(self):
|
||||
"""Tests showing quota class values for a fake non-existing quota
|
||||
class. The API will return the defaults if the quota class does not
|
||||
actually exist. We use a fake class to avoid any interaction with the
|
||||
real default quota class values.
|
||||
"""
|
||||
default_quota_class_set = self.client.quota_classes.get('default')
|
||||
default_values = {
|
||||
quota_name: str(getattr(default_quota_class_set, quota_name))
|
||||
for quota_name in self._included_resources
|
||||
}
|
||||
output = self.nova('quota-class-show %s' %
|
||||
self._get_quota_class_name())
|
||||
self._verify_qouta_class_show_output(output, default_values)
|
||||
|
||||
def test_quota_class_update(self):
|
||||
"""Tests updating a fake quota class. The way this works in the API
|
||||
is that if the quota class is not found, it is created. So in this
|
||||
test we can use a fake quota class with fake values and they will all
|
||||
get set. We don't use the default quota class because it is global
|
||||
and we don't want to interfere with other tests.
|
||||
"""
|
||||
class_name = self._get_quota_class_name()
|
||||
params = [class_name]
|
||||
expected_values = {}
|
||||
for quota_name in (
|
||||
self._included_resources + self._extra_update_resources):
|
||||
params.append("--%s 99" % quota_name.replace("_", "-"))
|
||||
expected_values[quota_name] = '99'
|
||||
|
||||
# Note that the quota-class-update CLI doesn't actually output any
|
||||
# information from the response.
|
||||
self.nova("quota-class-update", params=" ".join(params))
|
||||
# Assert the results using the quota-class-show output.
|
||||
output = self.nova('quota-class-show %s' % class_name)
|
||||
self._verify_qouta_class_show_output(output, expected_values)
|
||||
|
||||
# Assert that attempting to update resources that are blocked will
|
||||
# result in a failure.
|
||||
for quota_name in self._blocked_update_resources:
|
||||
self.assertRaises(
|
||||
exceptions.CommandFailed,
|
||||
self.nova, "quota-class-update %s --%s 99" %
|
||||
(class_name, quota_name.replace("_", "-")))
|
||||
|
||||
|
||||
class TestQuotasNovaClient2_50(TestQuotaClassesNovaClient):
|
||||
"""Nova quota classes functional tests for the v2.50 microversion."""
|
||||
|
||||
COMPUTE_API_VERSION = '2.50'
|
||||
|
||||
# The 2.50 microversion added the server_groups and server_group_members
|
||||
# to the response, and filtered out floating_ips, fixed_ips,
|
||||
# security_groups and security_group_members, similar to the 2.36
|
||||
# microversion in the os-qouta-sets API.
|
||||
_included_resources = ['instances', 'cores', 'ram', 'metadata_items',
|
||||
'injected_files', 'injected_file_content_bytes',
|
||||
'injected_file_path_bytes', 'key_pairs',
|
||||
'server_groups', 'server_group_members']
|
||||
|
||||
# The list of quota class resources we do not expect in the output table.
|
||||
_excluded_resources = ['floating_ips', 'fixed_ips',
|
||||
'security_groups', 'security_group_rules']
|
||||
|
||||
# In 2.50, server_groups and server_group_members can be both updated
|
||||
# in a PUT request and shown in a GET response.
|
||||
_extra_update_resources = []
|
||||
|
||||
# In 2.50, you can't update the network-related resources.
|
||||
_blocked_update_resources = _excluded_resources
|
@ -1289,6 +1289,20 @@ class FakeSessionClient(base_client.SessionClient):
|
||||
#
|
||||
|
||||
def get_os_quota_class_sets_test(self, **kw):
|
||||
if self.api_version >= api_versions.APIVersion('2.50'):
|
||||
return (200, FAKE_RESPONSE_HEADERS, {
|
||||
'quota_class_set': {
|
||||
'id': 'test',
|
||||
'metadata_items': 1,
|
||||
'injected_file_content_bytes': 1,
|
||||
'injected_file_path_bytes': 1,
|
||||
'ram': 1,
|
||||
'instances': 1,
|
||||
'injected_files': 1,
|
||||
'cores': 1,
|
||||
'key_pairs': 1,
|
||||
'server_groups': 1,
|
||||
'server_group_members': 1}})
|
||||
return (200, FAKE_RESPONSE_HEADERS, {
|
||||
'quota_class_set': {
|
||||
'id': 'test',
|
||||
@ -1297,6 +1311,7 @@ class FakeSessionClient(base_client.SessionClient):
|
||||
'injected_file_path_bytes': 1,
|
||||
'ram': 1,
|
||||
'floating_ips': 1,
|
||||
'fixed_ips': -1,
|
||||
'instances': 1,
|
||||
'injected_files': 1,
|
||||
'cores': 1,
|
||||
@ -1306,6 +1321,19 @@ class FakeSessionClient(base_client.SessionClient):
|
||||
|
||||
def put_os_quota_class_sets_test(self, body, **kw):
|
||||
assert list(body) == ['quota_class_set']
|
||||
if self.api_version >= api_versions.APIVersion('2.50'):
|
||||
return (200, {}, {
|
||||
'quota_class_set': {
|
||||
'metadata_items': 1,
|
||||
'injected_file_content_bytes': 1,
|
||||
'injected_file_path_bytes': 1,
|
||||
'ram': 1,
|
||||
'instances': 1,
|
||||
'injected_files': 1,
|
||||
'cores': 1,
|
||||
'key_pairs': 1,
|
||||
'server_groups': 1,
|
||||
'server_group_members': 1}})
|
||||
return (200, {}, {
|
||||
'quota_class_set': {
|
||||
'metadata_items': 1,
|
||||
@ -1313,6 +1341,7 @@ class FakeSessionClient(base_client.SessionClient):
|
||||
'injected_file_path_bytes': 1,
|
||||
'ram': 1,
|
||||
'floating_ips': 1,
|
||||
'fixed_ips': -1,
|
||||
'instances': 1,
|
||||
'injected_files': 1,
|
||||
'cores': 1,
|
||||
|
@ -28,12 +28,14 @@ class QuotaClassSetsTest(utils.TestCase):
|
||||
q = self.cs.quota_classes.get(class_name)
|
||||
self.assert_request_id(q, fakes.FAKE_REQUEST_ID_LIST)
|
||||
self.cs.assert_called('GET', '/os-quota-class-sets/%s' % class_name)
|
||||
return q
|
||||
|
||||
def test_update_quota(self):
|
||||
q = self.cs.quota_classes.get('test')
|
||||
self.assert_request_id(q, fakes.FAKE_REQUEST_ID_LIST)
|
||||
q.update(cores=2)
|
||||
self.cs.assert_called('PUT', '/os-quota-class-sets/test')
|
||||
return q
|
||||
|
||||
def test_refresh_quota(self):
|
||||
q = self.cs.quota_classes.get('test')
|
||||
@ -43,3 +45,53 @@ class QuotaClassSetsTest(utils.TestCase):
|
||||
self.assertNotEqual(q.cores, q2.cores)
|
||||
q2.get()
|
||||
self.assertEqual(q.cores, q2.cores)
|
||||
|
||||
|
||||
class QuotaClassSetsTest2_50(QuotaClassSetsTest):
|
||||
"""Tests the quota classes API binding using the 2.50 microversion."""
|
||||
def setUp(self):
|
||||
super(QuotaClassSetsTest2_50, self).setUp()
|
||||
self.cs = fakes.FakeClient(api_versions.APIVersion("2.50"))
|
||||
|
||||
def test_class_quotas_get(self):
|
||||
"""Tests that network-related resources aren't in a 2.50 response
|
||||
and server group related resources are in the response.
|
||||
"""
|
||||
q = super(QuotaClassSetsTest2_50, self).test_class_quotas_get()
|
||||
for invalid_resource in ('floating_ips', 'fixed_ips', 'networks',
|
||||
'security_groups', 'security_group_rules'):
|
||||
self.assertFalse(hasattr(q, invalid_resource),
|
||||
'%s should not be in %s' % (invalid_resource, q))
|
||||
# Also make sure server_groups and server_group_members are in the
|
||||
# response.
|
||||
for valid_resource in ('server_groups', 'server_group_members'):
|
||||
self.assertTrue(hasattr(q, valid_resource),
|
||||
'%s should be in %s' % (invalid_resource, q))
|
||||
|
||||
def test_update_quota(self):
|
||||
"""Tests that network-related resources aren't in a 2.50 response
|
||||
and server group related resources are in the response.
|
||||
"""
|
||||
q = super(QuotaClassSetsTest2_50, self).test_update_quota()
|
||||
for invalid_resource in ('floating_ips', 'fixed_ips', 'networks',
|
||||
'security_groups', 'security_group_rules'):
|
||||
self.assertFalse(hasattr(q, invalid_resource),
|
||||
'%s should not be in %s' % (invalid_resource, q))
|
||||
# Also make sure server_groups and server_group_members are in the
|
||||
# response.
|
||||
for valid_resource in ('server_groups', 'server_group_members'):
|
||||
self.assertTrue(hasattr(q, valid_resource),
|
||||
'%s should be in %s' % (invalid_resource, q))
|
||||
|
||||
def test_update_quota_invalid_resources(self):
|
||||
"""Tests trying to update quota class values for invalid resources.
|
||||
|
||||
This will fail with TypeError because the network-related resource
|
||||
kwargs aren't defined.
|
||||
"""
|
||||
q = self.cs.quota_classes.get('test')
|
||||
self.assertRaises(TypeError, q.update, floating_ips=1)
|
||||
self.assertRaises(TypeError, q.update, fixed_ips=1)
|
||||
self.assertRaises(TypeError, q.update, security_groups=1)
|
||||
self.assertRaises(TypeError, q.update, security_group_rules=1)
|
||||
self.assertRaises(TypeError, q.update, networks=1)
|
||||
|
@ -13,6 +13,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from novaclient import api_versions
|
||||
from novaclient import base
|
||||
|
||||
|
||||
@ -32,6 +33,10 @@ class QuotaClassSetManager(base.Manager):
|
||||
def _update_body(self, **kwargs):
|
||||
return {'quota_class_set': kwargs}
|
||||
|
||||
# NOTE(mriedem): Before 2.50 the resources you could update was just a
|
||||
# kwargs dict and not validated on the client-side, only on the API server
|
||||
# side.
|
||||
@api_versions.wraps("2.0", "2.49")
|
||||
def update(self, class_name, **kwargs):
|
||||
body = self._update_body(**kwargs)
|
||||
|
||||
@ -42,3 +47,37 @@ class QuotaClassSetManager(base.Manager):
|
||||
return self._update('/os-quota-class-sets/%s' % (class_name),
|
||||
body,
|
||||
'quota_class_set')
|
||||
|
||||
# NOTE(mriedem): 2.50 does strict validation of the resources you can
|
||||
# specify since the network-related resources are blocked in 2.50.
|
||||
@api_versions.wraps("2.50")
|
||||
def update(self, class_name, instances=None, cores=None, ram=None,
|
||||
metadata_items=None, injected_files=None,
|
||||
injected_file_content_bytes=None, injected_file_path_bytes=None,
|
||||
key_pairs=None, server_groups=None, server_group_members=None):
|
||||
resources = {}
|
||||
if instances is not None:
|
||||
resources['instances'] = instances
|
||||
if cores is not None:
|
||||
resources['cores'] = cores
|
||||
if ram is not None:
|
||||
resources['ram'] = ram
|
||||
if metadata_items is not None:
|
||||
resources['metadata_items'] = metadata_items
|
||||
if injected_files is not None:
|
||||
resources['injected_files'] = injected_files
|
||||
if injected_file_content_bytes is not None:
|
||||
resources['injected_file_content_bytes'] = (
|
||||
injected_file_content_bytes)
|
||||
if injected_file_path_bytes is not None:
|
||||
resources['injected_file_path_bytes'] = injected_file_path_bytes
|
||||
if key_pairs is not None:
|
||||
resources['key_pairs'] = key_pairs
|
||||
if server_groups is not None:
|
||||
resources['server_groups'] = server_groups
|
||||
if server_group_members is not None:
|
||||
resources['server_group_members'] = server_group_members
|
||||
|
||||
body = {'quota_class_set': resources}
|
||||
return self._update('/os-quota-class-sets/%s' % class_name, body,
|
||||
'quota_class_set')
|
||||
|
@ -3827,6 +3827,11 @@ def do_ssh(cs, args):
|
||||
os.system(cmd)
|
||||
|
||||
|
||||
# NOTE(mriedem): In the 2.50 microversion, the os-quota-class-sets API
|
||||
# will return the server_groups and server_group_members, but no longer
|
||||
# return floating_ips, fixed_ips, security_groups or security_group_members
|
||||
# as those are deprecated as networking service proxies and/or because
|
||||
# nova-network is deprecated. Similar to the 2.36 microversion.
|
||||
_quota_resources = ['instances', 'cores', 'ram',
|
||||
'floating_ips', 'fixed_ips', 'metadata_items',
|
||||
'injected_files', 'injected_file_content_bytes',
|
||||
@ -4137,7 +4142,7 @@ def do_quota_class_show(cs, args):
|
||||
_quota_show(cs.quota_classes.get(args.class_name))
|
||||
|
||||
|
||||
@api_versions.wraps("2.0", "2.35")
|
||||
@api_versions.wraps("2.0", "2.49")
|
||||
@utils.arg(
|
||||
'class_name',
|
||||
metavar='<class>',
|
||||
@ -4233,9 +4238,9 @@ def do_quota_class_update(cs, args):
|
||||
_quota_update(cs.quota_classes, args.class_name, args)
|
||||
|
||||
|
||||
# 2.36 does not support updating quota for floating IPs, fixed IPs, security
|
||||
# groups or security group rules.
|
||||
@api_versions.wraps("2.36")
|
||||
# 2.50 does not support updating quota class values for floating IPs,
|
||||
# fixed IPs, security groups or security group rules.
|
||||
@api_versions.wraps("2.50")
|
||||
@utils.arg(
|
||||
'class_name',
|
||||
metavar='<class>',
|
||||
|
32
releasenotes/notes/microversion-v2_50-4f484658d66d01aa.yaml
Normal file
32
releasenotes/notes/microversion-v2_50-4f484658d66d01aa.yaml
Normal file
@ -0,0 +1,32 @@
|
||||
---
|
||||
fixes:
|
||||
- |
|
||||
Adds support for the ``2.50`` microversion which fixes the
|
||||
``nova quota-class-show`` and ``nova quota-class-update`` commands in the
|
||||
following ways:
|
||||
|
||||
* The ``server_groups`` and ``server_group_members`` quota resources will
|
||||
now be shown in the output table for ``nova quota-class-show``.
|
||||
* The ``floating_ips``, ``fixed_ips``, ``security_groups`` and
|
||||
``security_group_rules`` quota resources will no longer be able to
|
||||
be updated using ``nova quota-class-update`` nor will they be shown in
|
||||
the output of ``nova quota-class-show``. Use python-openstackclient or
|
||||
python-neutronclient to work with quotas for network resources.
|
||||
|
||||
In addition, the ``nova quota-class-update`` CLI was previously incorrectly
|
||||
limiting the ability to update quota class values for ``floating_ips``,
|
||||
``fixed_ips``, ``security_groups`` and ``security_group_rules`` based on
|
||||
the 2.36 microversion. That has been changed to limit based on the ``2.50``
|
||||
microversion.
|
||||
upgrade:
|
||||
- |
|
||||
The ``novaclient.v2.quota_classes.QuotaClassSetManager.update`` method
|
||||
now defines specific kwargs starting with microversion ``2.50`` since
|
||||
updating network-related resource quota class values is not supported on
|
||||
the server with microversion ``2.50``. The list of excluded resources is:
|
||||
|
||||
- ``fixed_ips``
|
||||
- ``floating_ips``
|
||||
- ``networks``
|
||||
- ``security_groups``
|
||||
- ``security_group_rules``
|
Loading…
Reference in New Issue
Block a user