Add support for the 2.57 microversion
With the 2.57 microversion, we: * Deprecate the --file option from the nova boot and nova rebuild CLIs and API bindings. * Add --user-data and --user-data-unset to the nova rebuild CLI and API bindings. * Deprecate the maxPersonality and maxPersonalitySize fields from the nova limits and nova absolute-limits CLIs and API bindings. * Deprecate injected_files, injected_file_content_bytes, and injected_file_path_bytes from the nova quota-show, nova quota-update, nova quota-defaults, nova quota-class-show, and nova quota-class-update CLIs and API bindings. Part of blueprint deprecate-file-injection Change-Id: Id13e3eac3ef87d429454042ac7046e865774ff8e
This commit is contained in:
parent
fefc3ba723
commit
038cfdd5b3
@ -25,4 +25,4 @@ API_MIN_VERSION = api_versions.APIVersion("2.1")
|
|||||||
# when client supported the max version, and bumped sequentially, otherwise
|
# when client supported the max version, and bumped sequentially, otherwise
|
||||||
# the client may break due to server side new version may include some
|
# the client may break due to server side new version may include some
|
||||||
# backward incompatible change.
|
# backward incompatible change.
|
||||||
API_MAX_VERSION = api_versions.APIVersion("2.56")
|
API_MAX_VERSION = api_versions.APIVersion("2.57")
|
||||||
|
@ -48,6 +48,7 @@ class UnsupportedAttribute(AttributeError):
|
|||||||
self.message = (
|
self.message = (
|
||||||
"'%(name)s' argument is only allowed since microversion "
|
"'%(name)s' argument is only allowed since microversion "
|
||||||
"%(start)s." % {"name": argument_name, "start": start_version})
|
"%(start)s." % {"name": argument_name, "start": start_version})
|
||||||
|
super(UnsupportedAttribute, self).__init__(self.message)
|
||||||
|
|
||||||
|
|
||||||
class CommandError(Exception):
|
class CommandError(Exception):
|
||||||
|
@ -52,7 +52,7 @@ class TestQuotasNovaClient2_35(test_quotas.TestQuotasNovaClient):
|
|||||||
class TestQuotasNovaClient2_36(TestQuotasNovaClient2_35):
|
class TestQuotasNovaClient2_36(TestQuotasNovaClient2_35):
|
||||||
"""Nova quotas functional tests."""
|
"""Nova quotas functional tests."""
|
||||||
|
|
||||||
COMPUTE_API_VERSION = "2.latest"
|
COMPUTE_API_VERSION = "2.36"
|
||||||
|
|
||||||
# The 2.36 microversion stops proxying network quota resources like
|
# The 2.36 microversion stops proxying network quota resources like
|
||||||
# floating/fixed IPs and security groups/rules.
|
# floating/fixed IPs and security groups/rules.
|
||||||
@ -61,3 +61,14 @@ class TestQuotasNovaClient2_36(TestQuotasNovaClient2_35):
|
|||||||
'injected_file_content_bytes',
|
'injected_file_content_bytes',
|
||||||
'injected_file_path_bytes', 'key_pairs',
|
'injected_file_path_bytes', 'key_pairs',
|
||||||
'server_groups', 'server_group_members']
|
'server_groups', 'server_group_members']
|
||||||
|
|
||||||
|
|
||||||
|
class TestQuotasNovaClient2_57(TestQuotasNovaClient2_35):
|
||||||
|
"""Nova quotas functional tests."""
|
||||||
|
|
||||||
|
COMPUTE_API_VERSION = "2.latest"
|
||||||
|
|
||||||
|
# The 2.57 microversion deprecates injected_file* quotas.
|
||||||
|
_quota_resources = ['instances', 'cores', 'ram',
|
||||||
|
'metadata_items', 'key_pairs',
|
||||||
|
'server_groups', 'server_group_members']
|
||||||
|
@ -16,6 +16,13 @@ from novaclient.tests.unit.fixture_data import base
|
|||||||
class Fixture(base.Fixture):
|
class Fixture(base.Fixture):
|
||||||
|
|
||||||
base_url = 'limits'
|
base_url = 'limits'
|
||||||
|
absolute = {
|
||||||
|
"maxTotalRAMSize": 51200,
|
||||||
|
"maxServerMeta": 5,
|
||||||
|
"maxImageMeta": 5,
|
||||||
|
"maxPersonality": 5,
|
||||||
|
"maxPersonalitySize": 10240
|
||||||
|
}
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(Fixture, self).setUp()
|
super(Fixture, self).setUp()
|
||||||
@ -64,13 +71,7 @@ class Fixture(base.Fixture):
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"absolute": {
|
"absolute": self.absolute,
|
||||||
"maxTotalRAMSize": 51200,
|
|
||||||
"maxServerMeta": 5,
|
|
||||||
"maxImageMeta": 5,
|
|
||||||
"maxPersonality": 5,
|
|
||||||
"maxPersonalitySize": 10240
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,3 +79,13 @@ class Fixture(base.Fixture):
|
|||||||
self.requests_mock.get(self.url(),
|
self.requests_mock.get(self.url(),
|
||||||
json=get_limits,
|
json=get_limits,
|
||||||
headers=headers)
|
headers=headers)
|
||||||
|
|
||||||
|
|
||||||
|
class Fixture2_57(Fixture):
|
||||||
|
"""Fixture data for the 2.57 microversion where personality files are
|
||||||
|
deprecated.
|
||||||
|
"""
|
||||||
|
absolute = {
|
||||||
|
"maxTotalRAMSize": 51200,
|
||||||
|
"maxServerMeta": 5
|
||||||
|
}
|
||||||
|
@ -57,6 +57,7 @@ class V1(base.Fixture):
|
|||||||
'injected_file_content_bytes': 1,
|
'injected_file_content_bytes': 1,
|
||||||
'injected_file_path_bytes': 1,
|
'injected_file_path_bytes': 1,
|
||||||
'ram': 1,
|
'ram': 1,
|
||||||
|
'fixed_ips': -1,
|
||||||
'floating_ips': 1,
|
'floating_ips': 1,
|
||||||
'instances': 1,
|
'instances': 1,
|
||||||
'injected_files': 1,
|
'injected_files': 1,
|
||||||
@ -67,3 +68,20 @@ class V1(base.Fixture):
|
|||||||
'server_groups': 1,
|
'server_groups': 1,
|
||||||
'server_group_members': 1
|
'server_group_members': 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class V2_57(V1):
|
||||||
|
"""2.57 fixture data where there are no injected file or network resources
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_quota(self, tenant_id='test'):
|
||||||
|
return {
|
||||||
|
'id': tenant_id,
|
||||||
|
'metadata_items': 1,
|
||||||
|
'ram': 1,
|
||||||
|
'instances': 1,
|
||||||
|
'cores': 1,
|
||||||
|
'key_pairs': 1,
|
||||||
|
'server_groups': 1,
|
||||||
|
'server_group_members': 1
|
||||||
|
}
|
||||||
|
@ -331,6 +331,14 @@ class FakeSessionClient(base_client.SessionClient):
|
|||||||
#
|
#
|
||||||
|
|
||||||
def get_limits(self, **kw):
|
def get_limits(self, **kw):
|
||||||
|
absolute = {
|
||||||
|
"maxTotalRAMSize": 51200,
|
||||||
|
"maxServerMeta": 5,
|
||||||
|
"maxImageMeta": 5
|
||||||
|
}
|
||||||
|
# 2.57 removes injected_file* entries from the response.
|
||||||
|
if self.api_version < api_versions.APIVersion('2.57'):
|
||||||
|
absolute.update({"maxPersonality": 5, "maxPersonalitySize": 10240})
|
||||||
return (200, {}, {"limits": {
|
return (200, {}, {"limits": {
|
||||||
"rate": [
|
"rate": [
|
||||||
{
|
{
|
||||||
@ -374,13 +382,7 @@ class FakeSessionClient(base_client.SessionClient):
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"absolute": {
|
"absolute": absolute,
|
||||||
"maxTotalRAMSize": 51200,
|
|
||||||
"maxServerMeta": 5,
|
|
||||||
"maxImageMeta": 5,
|
|
||||||
"maxPersonality": 5,
|
|
||||||
"maxPersonalitySize": 10240
|
|
||||||
},
|
|
||||||
}})
|
}})
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -1297,6 +1299,19 @@ class FakeSessionClient(base_client.SessionClient):
|
|||||||
#
|
#
|
||||||
|
|
||||||
def get_os_quota_class_sets_test(self, **kw):
|
def get_os_quota_class_sets_test(self, **kw):
|
||||||
|
# 2.57 removes injected_file* entries from the response.
|
||||||
|
if self.api_version >= api_versions.APIVersion('2.57'):
|
||||||
|
return (200, FAKE_RESPONSE_HEADERS, {
|
||||||
|
'quota_class_set': {
|
||||||
|
'id': 'test',
|
||||||
|
'metadata_items': 1,
|
||||||
|
'ram': 1,
|
||||||
|
'instances': 1,
|
||||||
|
'cores': 1,
|
||||||
|
'key_pairs': 1,
|
||||||
|
'server_groups': 1,
|
||||||
|
'server_group_members': 1}})
|
||||||
|
|
||||||
if self.api_version >= api_versions.APIVersion('2.50'):
|
if self.api_version >= api_versions.APIVersion('2.50'):
|
||||||
return (200, FAKE_RESPONSE_HEADERS, {
|
return (200, FAKE_RESPONSE_HEADERS, {
|
||||||
'quota_class_set': {
|
'quota_class_set': {
|
||||||
@ -1329,6 +1344,18 @@ class FakeSessionClient(base_client.SessionClient):
|
|||||||
|
|
||||||
def put_os_quota_class_sets_test(self, body, **kw):
|
def put_os_quota_class_sets_test(self, body, **kw):
|
||||||
assert list(body) == ['quota_class_set']
|
assert list(body) == ['quota_class_set']
|
||||||
|
# 2.57 removes injected_file* entries from the response.
|
||||||
|
if self.api_version >= api_versions.APIVersion('2.57'):
|
||||||
|
return (200, {}, {
|
||||||
|
'quota_class_set': {
|
||||||
|
'metadata_items': 1,
|
||||||
|
'ram': 1,
|
||||||
|
'instances': 1,
|
||||||
|
'cores': 1,
|
||||||
|
'key_pairs': 1,
|
||||||
|
'server_groups': 1,
|
||||||
|
'server_group_members': 1}})
|
||||||
|
|
||||||
if self.api_version >= api_versions.APIVersion('2.50'):
|
if self.api_version >= api_versions.APIVersion('2.50'):
|
||||||
return (200, {}, {
|
return (200, {}, {
|
||||||
'quota_class_set': {
|
'quota_class_set': {
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from novaclient import api_versions
|
||||||
from novaclient.tests.unit.fixture_data import client
|
from novaclient.tests.unit.fixture_data import client
|
||||||
from novaclient.tests.unit.fixture_data import limits as data
|
from novaclient.tests.unit.fixture_data import limits as data
|
||||||
from novaclient.tests.unit import utils
|
from novaclient.tests.unit import utils
|
||||||
@ -22,6 +23,8 @@ class LimitsTest(utils.FixturedTestCase):
|
|||||||
|
|
||||||
client_fixture_class = client.V1
|
client_fixture_class = client.V1
|
||||||
data_fixture_class = data.Fixture
|
data_fixture_class = data.Fixture
|
||||||
|
supports_image_meta = True # 2.39 deprecates maxImageMeta
|
||||||
|
supports_personality = True # 2.57 deprecates maxPersonality*
|
||||||
|
|
||||||
def test_get_limits(self):
|
def test_get_limits(self):
|
||||||
obj = self.cs.limits.get()
|
obj = self.cs.limits.get()
|
||||||
@ -39,13 +42,16 @@ class LimitsTest(utils.FixturedTestCase):
|
|||||||
obj = self.cs.limits.get(reserved=True)
|
obj = self.cs.limits.get(reserved=True)
|
||||||
self.assert_request_id(obj, fakes.FAKE_REQUEST_ID_LIST)
|
self.assert_request_id(obj, fakes.FAKE_REQUEST_ID_LIST)
|
||||||
|
|
||||||
expected = (
|
expected = [
|
||||||
limits.AbsoluteLimit("maxTotalRAMSize", 51200),
|
limits.AbsoluteLimit("maxTotalRAMSize", 51200),
|
||||||
limits.AbsoluteLimit("maxServerMeta", 5),
|
limits.AbsoluteLimit("maxServerMeta", 5)
|
||||||
limits.AbsoluteLimit("maxImageMeta", 5),
|
]
|
||||||
limits.AbsoluteLimit("maxPersonality", 5),
|
if self.supports_image_meta:
|
||||||
limits.AbsoluteLimit("maxPersonalitySize", 10240),
|
expected.append(limits.AbsoluteLimit("maxImageMeta", 5))
|
||||||
)
|
if self.supports_personality:
|
||||||
|
expected.extend([
|
||||||
|
limits.AbsoluteLimit("maxPersonality", 5),
|
||||||
|
limits.AbsoluteLimit("maxPersonalitySize", 10240)])
|
||||||
|
|
||||||
self.assert_called('GET', '/limits?reserved=1')
|
self.assert_called('GET', '/limits?reserved=1')
|
||||||
abs_limits = list(obj.absolute)
|
abs_limits = list(obj.absolute)
|
||||||
@ -75,16 +81,29 @@ class LimitsTest(utils.FixturedTestCase):
|
|||||||
for limit in rate_limits:
|
for limit in rate_limits:
|
||||||
self.assertIn(limit, expected)
|
self.assertIn(limit, expected)
|
||||||
|
|
||||||
expected = (
|
expected = [
|
||||||
limits.AbsoluteLimit("maxTotalRAMSize", 51200),
|
limits.AbsoluteLimit("maxTotalRAMSize", 51200),
|
||||||
limits.AbsoluteLimit("maxServerMeta", 5),
|
limits.AbsoluteLimit("maxServerMeta", 5)
|
||||||
limits.AbsoluteLimit("maxImageMeta", 5),
|
]
|
||||||
limits.AbsoluteLimit("maxPersonality", 5),
|
if self.supports_image_meta:
|
||||||
limits.AbsoluteLimit("maxPersonalitySize", 10240),
|
expected.append(limits.AbsoluteLimit("maxImageMeta", 5))
|
||||||
)
|
if self.supports_personality:
|
||||||
|
expected.extend([
|
||||||
|
limits.AbsoluteLimit("maxPersonality", 5),
|
||||||
|
limits.AbsoluteLimit("maxPersonalitySize", 10240)])
|
||||||
|
|
||||||
abs_limits = list(obj.absolute)
|
abs_limits = list(obj.absolute)
|
||||||
self.assertEqual(len(abs_limits), len(expected))
|
self.assertEqual(len(abs_limits), len(expected))
|
||||||
|
|
||||||
for limit in abs_limits:
|
for limit in abs_limits:
|
||||||
self.assertIn(limit, expected)
|
self.assertIn(limit, expected)
|
||||||
|
|
||||||
|
|
||||||
|
class LimitsTest2_57(LimitsTest):
|
||||||
|
data_fixture_class = data.Fixture2_57
|
||||||
|
supports_image_meta = False
|
||||||
|
supports_personality = False
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(LimitsTest2_57, self).setUp()
|
||||||
|
self.cs.api_version = api_versions.APIVersion('2.57')
|
||||||
|
@ -49,17 +49,20 @@ class QuotaClassSetsTest(utils.TestCase):
|
|||||||
|
|
||||||
class QuotaClassSetsTest2_50(QuotaClassSetsTest):
|
class QuotaClassSetsTest2_50(QuotaClassSetsTest):
|
||||||
"""Tests the quota classes API binding using the 2.50 microversion."""
|
"""Tests the quota classes API binding using the 2.50 microversion."""
|
||||||
|
api_version = '2.50'
|
||||||
|
invalid_resources = ['floating_ips', 'fixed_ips', 'networks',
|
||||||
|
'security_groups', 'security_group_rules']
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(QuotaClassSetsTest2_50, self).setUp()
|
super(QuotaClassSetsTest2_50, self).setUp()
|
||||||
self.cs = fakes.FakeClient(api_versions.APIVersion("2.50"))
|
self.cs = fakes.FakeClient(api_versions.APIVersion(self.api_version))
|
||||||
|
|
||||||
def test_class_quotas_get(self):
|
def test_class_quotas_get(self):
|
||||||
"""Tests that network-related resources aren't in a 2.50 response
|
"""Tests that network-related resources aren't in a 2.50 response
|
||||||
and server group related resources are in the response.
|
and server group related resources are in the response.
|
||||||
"""
|
"""
|
||||||
q = super(QuotaClassSetsTest2_50, self).test_class_quotas_get()
|
q = super(QuotaClassSetsTest2_50, self).test_class_quotas_get()
|
||||||
for invalid_resource in ('floating_ips', 'fixed_ips', 'networks',
|
for invalid_resource in self.invalid_resources:
|
||||||
'security_groups', 'security_group_rules'):
|
|
||||||
self.assertFalse(hasattr(q, invalid_resource),
|
self.assertFalse(hasattr(q, invalid_resource),
|
||||||
'%s should not be in %s' % (invalid_resource, q))
|
'%s should not be in %s' % (invalid_resource, q))
|
||||||
# Also make sure server_groups and server_group_members are in the
|
# Also make sure server_groups and server_group_members are in the
|
||||||
@ -73,8 +76,7 @@ class QuotaClassSetsTest2_50(QuotaClassSetsTest):
|
|||||||
and server group related resources are in the response.
|
and server group related resources are in the response.
|
||||||
"""
|
"""
|
||||||
q = super(QuotaClassSetsTest2_50, self).test_update_quota()
|
q = super(QuotaClassSetsTest2_50, self).test_update_quota()
|
||||||
for invalid_resource in ('floating_ips', 'fixed_ips', 'networks',
|
for invalid_resource in self.invalid_resources:
|
||||||
'security_groups', 'security_group_rules'):
|
|
||||||
self.assertFalse(hasattr(q, invalid_resource),
|
self.assertFalse(hasattr(q, invalid_resource),
|
||||||
'%s should not be in %s' % (invalid_resource, q))
|
'%s should not be in %s' % (invalid_resource, q))
|
||||||
# Also make sure server_groups and server_group_members are in the
|
# Also make sure server_groups and server_group_members are in the
|
||||||
@ -95,3 +97,27 @@ class QuotaClassSetsTest2_50(QuotaClassSetsTest):
|
|||||||
self.assertRaises(TypeError, q.update, security_groups=1)
|
self.assertRaises(TypeError, q.update, security_groups=1)
|
||||||
self.assertRaises(TypeError, q.update, security_group_rules=1)
|
self.assertRaises(TypeError, q.update, security_group_rules=1)
|
||||||
self.assertRaises(TypeError, q.update, networks=1)
|
self.assertRaises(TypeError, q.update, networks=1)
|
||||||
|
return q
|
||||||
|
|
||||||
|
|
||||||
|
class QuotaClassSetsTest2_57(QuotaClassSetsTest2_50):
|
||||||
|
"""Tests the quota classes API binding using the 2.57 microversion."""
|
||||||
|
api_version = '2.57'
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(QuotaClassSetsTest2_57, self).setUp()
|
||||||
|
self.invalid_resources.extend(['injected_files',
|
||||||
|
'injected_file_content_bytes',
|
||||||
|
'injected_file_path_bytes'])
|
||||||
|
|
||||||
|
def test_update_quota_invalid_resources(self):
|
||||||
|
"""Tests trying to update quota class values for invalid resources.
|
||||||
|
|
||||||
|
This will fail with TypeError because the file-related resource
|
||||||
|
kwargs aren't defined.
|
||||||
|
"""
|
||||||
|
q = super(
|
||||||
|
QuotaClassSetsTest2_57, self).test_update_quota_invalid_resources()
|
||||||
|
self.assertRaises(TypeError, q.update, injected_files=1)
|
||||||
|
self.assertRaises(TypeError, q.update, injected_file_content_bytes=1)
|
||||||
|
self.assertRaises(TypeError, q.update, injected_file_path_bytes=1)
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from novaclient import api_versions
|
||||||
from novaclient.tests.unit.fixture_data import client
|
from novaclient.tests.unit.fixture_data import client
|
||||||
from novaclient.tests.unit.fixture_data import quotas as data
|
from novaclient.tests.unit.fixture_data import quotas as data
|
||||||
from novaclient.tests.unit import utils
|
from novaclient.tests.unit import utils
|
||||||
@ -29,6 +30,7 @@ class QuotaSetsTest(utils.FixturedTestCase):
|
|||||||
q = self.cs.quotas.get(tenant_id)
|
q = self.cs.quotas.get(tenant_id)
|
||||||
self.assert_request_id(q, fakes.FAKE_REQUEST_ID_LIST)
|
self.assert_request_id(q, fakes.FAKE_REQUEST_ID_LIST)
|
||||||
self.assert_called('GET', '/os-quota-sets/%s' % tenant_id)
|
self.assert_called('GET', '/os-quota-sets/%s' % tenant_id)
|
||||||
|
return q
|
||||||
|
|
||||||
def test_user_quotas_get(self):
|
def test_user_quotas_get(self):
|
||||||
tenant_id = 'test'
|
tenant_id = 'test'
|
||||||
@ -65,6 +67,7 @@ class QuotaSetsTest(utils.FixturedTestCase):
|
|||||||
self.assert_called(
|
self.assert_called(
|
||||||
'PUT', '/os-quota-sets/97f4c221bff44578b0300df4ef119353',
|
'PUT', '/os-quota-sets/97f4c221bff44578b0300df4ef119353',
|
||||||
{'quota_set': {'force': True, 'cores': 2}})
|
{'quota_set': {'force': True, 'cores': 2}})
|
||||||
|
return q
|
||||||
|
|
||||||
def test_quotas_delete(self):
|
def test_quotas_delete(self):
|
||||||
tenant_id = 'test'
|
tenant_id = 'test'
|
||||||
@ -79,3 +82,40 @@ class QuotaSetsTest(utils.FixturedTestCase):
|
|||||||
self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST)
|
self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST)
|
||||||
url = '/os-quota-sets/%s?user_id=%s' % (tenant_id, user_id)
|
url = '/os-quota-sets/%s?user_id=%s' % (tenant_id, user_id)
|
||||||
self.assert_called('DELETE', url)
|
self.assert_called('DELETE', url)
|
||||||
|
|
||||||
|
|
||||||
|
class QuotaSetsTest2_57(QuotaSetsTest):
|
||||||
|
"""Tests the quotas API binding using the 2.57 microversion."""
|
||||||
|
data_fixture_class = data.V2_57
|
||||||
|
invalid_resources = ['floating_ips', 'fixed_ips', 'networks',
|
||||||
|
'security_groups', 'security_group_rules',
|
||||||
|
'injected_files', 'injected_file_content_bytes',
|
||||||
|
'injected_file_path_bytes']
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(QuotaSetsTest2_57, self).setUp()
|
||||||
|
self.cs.api_version = api_versions.APIVersion('2.57')
|
||||||
|
|
||||||
|
def test_tenant_quotas_get(self):
|
||||||
|
q = super(QuotaSetsTest2_57, self).test_tenant_quotas_get()
|
||||||
|
for invalid_resource in self.invalid_resources:
|
||||||
|
self.assertFalse(hasattr(q, invalid_resource),
|
||||||
|
'%s should not be in %s' % (invalid_resource, q))
|
||||||
|
|
||||||
|
def test_force_update_quota(self):
|
||||||
|
q = super(QuotaSetsTest2_57, self).test_force_update_quota()
|
||||||
|
for invalid_resource in self.invalid_resources:
|
||||||
|
self.assertFalse(hasattr(q, invalid_resource),
|
||||||
|
'%s should not be in %s' % (invalid_resource, q))
|
||||||
|
|
||||||
|
def test_update_quota_invalid_resources(self):
|
||||||
|
"""Tests trying to update quota values for invalid resources."""
|
||||||
|
q = self.cs.quotas.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)
|
||||||
|
self.assertRaises(TypeError, q.update, injected_files=1)
|
||||||
|
self.assertRaises(TypeError, q.update, injected_file_content_bytes=1)
|
||||||
|
self.assertRaises(TypeError, q.update, injected_file_path_bytes=1)
|
||||||
|
@ -34,6 +34,7 @@ class ServersTest(utils.FixturedTestCase):
|
|||||||
client_fixture_class = client.V1
|
client_fixture_class = client.V1
|
||||||
data_fixture_class = data.V1
|
data_fixture_class = data.V1
|
||||||
api_version = None
|
api_version = None
|
||||||
|
supports_files = True
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(ServersTest, self).setUp()
|
super(ServersTest, self).setUp()
|
||||||
@ -126,6 +127,12 @@ class ServersTest(utils.FixturedTestCase):
|
|||||||
self.assertEqual(s1._info, s2._info)
|
self.assertEqual(s1._info, s2._info)
|
||||||
|
|
||||||
def test_create_server(self):
|
def test_create_server(self):
|
||||||
|
kwargs = {}
|
||||||
|
if self.supports_files:
|
||||||
|
kwargs['files'] = {
|
||||||
|
'/etc/passwd': 'some data', # a file
|
||||||
|
'/tmp/foo.txt': six.StringIO('data'), # a stream
|
||||||
|
}
|
||||||
s = self.cs.servers.create(
|
s = self.cs.servers.create(
|
||||||
name="My server",
|
name="My server",
|
||||||
image=1,
|
image=1,
|
||||||
@ -133,11 +140,8 @@ class ServersTest(utils.FixturedTestCase):
|
|||||||
meta={'foo': 'bar'},
|
meta={'foo': 'bar'},
|
||||||
userdata="hello moto",
|
userdata="hello moto",
|
||||||
key_name="fakekey",
|
key_name="fakekey",
|
||||||
files={
|
nics=self._get_server_create_default_nics(),
|
||||||
'/etc/passwd': 'some data', # a file
|
**kwargs
|
||||||
'/tmp/foo.txt': six.StringIO('data'), # a stream
|
|
||||||
},
|
|
||||||
nics=self._get_server_create_default_nics()
|
|
||||||
)
|
)
|
||||||
self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST)
|
self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST)
|
||||||
self.assert_called('POST', '/servers')
|
self.assert_called('POST', '/servers')
|
||||||
@ -253,23 +257,32 @@ class ServersTest(utils.FixturedTestCase):
|
|||||||
self.assertIsInstance(s, servers.Server)
|
self.assertIsInstance(s, servers.Server)
|
||||||
|
|
||||||
def test_create_server_userdata_file_object(self):
|
def test_create_server_userdata_file_object(self):
|
||||||
|
kwargs = {}
|
||||||
|
if self.supports_files:
|
||||||
|
kwargs['files'] = {
|
||||||
|
'/etc/passwd': 'some data', # a file
|
||||||
|
'/tmp/foo.txt': six.StringIO('data'), # a stream
|
||||||
|
}
|
||||||
s = self.cs.servers.create(
|
s = self.cs.servers.create(
|
||||||
name="My server",
|
name="My server",
|
||||||
image=1,
|
image=1,
|
||||||
flavor=1,
|
flavor=1,
|
||||||
meta={'foo': 'bar'},
|
meta={'foo': 'bar'},
|
||||||
userdata=six.StringIO('hello moto'),
|
userdata=six.StringIO('hello moto'),
|
||||||
files={
|
|
||||||
'/etc/passwd': 'some data', # a file
|
|
||||||
'/tmp/foo.txt': six.StringIO('data'), # a stream
|
|
||||||
},
|
|
||||||
nics=self._get_server_create_default_nics(),
|
nics=self._get_server_create_default_nics(),
|
||||||
|
**kwargs
|
||||||
)
|
)
|
||||||
self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST)
|
self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST)
|
||||||
self.assert_called('POST', '/servers')
|
self.assert_called('POST', '/servers')
|
||||||
self.assertIsInstance(s, servers.Server)
|
self.assertIsInstance(s, servers.Server)
|
||||||
|
|
||||||
def test_create_server_userdata_unicode(self):
|
def test_create_server_userdata_unicode(self):
|
||||||
|
kwargs = {}
|
||||||
|
if self.supports_files:
|
||||||
|
kwargs['files'] = {
|
||||||
|
'/etc/passwd': 'some data', # a file
|
||||||
|
'/tmp/foo.txt': six.StringIO('data'), # a stream
|
||||||
|
}
|
||||||
s = self.cs.servers.create(
|
s = self.cs.servers.create(
|
||||||
name="My server",
|
name="My server",
|
||||||
image=1,
|
image=1,
|
||||||
@ -277,17 +290,20 @@ class ServersTest(utils.FixturedTestCase):
|
|||||||
meta={'foo': 'bar'},
|
meta={'foo': 'bar'},
|
||||||
userdata=six.u('こんにちは'),
|
userdata=six.u('こんにちは'),
|
||||||
key_name="fakekey",
|
key_name="fakekey",
|
||||||
files={
|
|
||||||
'/etc/passwd': 'some data', # a file
|
|
||||||
'/tmp/foo.txt': six.StringIO('data'), # a stream
|
|
||||||
},
|
|
||||||
nics=self._get_server_create_default_nics(),
|
nics=self._get_server_create_default_nics(),
|
||||||
|
**kwargs
|
||||||
)
|
)
|
||||||
self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST)
|
self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST)
|
||||||
self.assert_called('POST', '/servers')
|
self.assert_called('POST', '/servers')
|
||||||
self.assertIsInstance(s, servers.Server)
|
self.assertIsInstance(s, servers.Server)
|
||||||
|
|
||||||
def test_create_server_userdata_utf8(self):
|
def test_create_server_userdata_utf8(self):
|
||||||
|
kwargs = {}
|
||||||
|
if self.supports_files:
|
||||||
|
kwargs['files'] = {
|
||||||
|
'/etc/passwd': 'some data', # a file
|
||||||
|
'/tmp/foo.txt': six.StringIO('data'), # a stream
|
||||||
|
}
|
||||||
s = self.cs.servers.create(
|
s = self.cs.servers.create(
|
||||||
name="My server",
|
name="My server",
|
||||||
image=1,
|
image=1,
|
||||||
@ -295,11 +311,8 @@ class ServersTest(utils.FixturedTestCase):
|
|||||||
meta={'foo': 'bar'},
|
meta={'foo': 'bar'},
|
||||||
userdata='こんにちは',
|
userdata='こんにちは',
|
||||||
key_name="fakekey",
|
key_name="fakekey",
|
||||||
files={
|
|
||||||
'/etc/passwd': 'some data', # a file
|
|
||||||
'/tmp/foo.txt': six.StringIO('data'), # a stream
|
|
||||||
},
|
|
||||||
nics=self._get_server_create_default_nics(),
|
nics=self._get_server_create_default_nics(),
|
||||||
|
**kwargs
|
||||||
)
|
)
|
||||||
self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST)
|
self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST)
|
||||||
self.assert_called('POST', '/servers')
|
self.assert_called('POST', '/servers')
|
||||||
@ -323,6 +336,12 @@ class ServersTest(utils.FixturedTestCase):
|
|||||||
self.assertEqual(test_password, body['server']['adminPass'])
|
self.assertEqual(test_password, body['server']['adminPass'])
|
||||||
|
|
||||||
def test_create_server_userdata_bin(self):
|
def test_create_server_userdata_bin(self):
|
||||||
|
kwargs = {}
|
||||||
|
if self.supports_files:
|
||||||
|
kwargs['files'] = {
|
||||||
|
'/etc/passwd': 'some data', # a file
|
||||||
|
'/tmp/foo.txt': six.StringIO('data'), # a stream
|
||||||
|
}
|
||||||
with tempfile.TemporaryFile(mode='wb+') as bin_file:
|
with tempfile.TemporaryFile(mode='wb+') as bin_file:
|
||||||
original_data = os.urandom(1024)
|
original_data = os.urandom(1024)
|
||||||
bin_file.write(original_data)
|
bin_file.write(original_data)
|
||||||
@ -335,11 +354,8 @@ class ServersTest(utils.FixturedTestCase):
|
|||||||
meta={'foo': 'bar'},
|
meta={'foo': 'bar'},
|
||||||
userdata=bin_file,
|
userdata=bin_file,
|
||||||
key_name="fakekey",
|
key_name="fakekey",
|
||||||
files={
|
|
||||||
'/etc/passwd': 'some data', # a file
|
|
||||||
'/tmp/foo.txt': six.StringIO('data'), # a stream
|
|
||||||
},
|
|
||||||
nics=self._get_server_create_default_nics(),
|
nics=self._get_server_create_default_nics(),
|
||||||
|
**kwargs
|
||||||
)
|
)
|
||||||
self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST)
|
self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST)
|
||||||
self.assert_called('POST', '/servers')
|
self.assert_called('POST', '/servers')
|
||||||
@ -1500,3 +1516,29 @@ class ServersV256Test(ServersV254Test):
|
|||||||
ex = self.assertRaises(TypeError,
|
ex = self.assertRaises(TypeError,
|
||||||
s.migrate, host='target-host')
|
s.migrate, host='target-host')
|
||||||
self.assertIn('host', six.text_type(ex))
|
self.assertIn('host', six.text_type(ex))
|
||||||
|
|
||||||
|
|
||||||
|
class ServersV257Test(ServersV256Test):
|
||||||
|
"""Tests the servers python API bindings with microversion 2.57 where
|
||||||
|
personality files are deprecated.
|
||||||
|
"""
|
||||||
|
api_version = "2.57"
|
||||||
|
supports_files = False
|
||||||
|
|
||||||
|
def test_create_server_with_files_fails(self):
|
||||||
|
ex = self.assertRaises(
|
||||||
|
exceptions.UnsupportedAttribute, self.cs.servers.create,
|
||||||
|
name="My server", image=1, flavor=1,
|
||||||
|
files={
|
||||||
|
'/etc/passwd': 'some data', # a file
|
||||||
|
'/tmp/foo.txt': six.StringIO('data'), # a stream
|
||||||
|
}, nics='auto')
|
||||||
|
self.assertIn('files', six.text_type(ex))
|
||||||
|
|
||||||
|
def test_rebuild_server_name_meta_files(self):
|
||||||
|
files = {'/etc/passwd': 'some data'}
|
||||||
|
s = self.cs.servers.get(1234)
|
||||||
|
ex = self.assertRaises(
|
||||||
|
exceptions.UnsupportedAttribute, s.rebuild, image=1, name='new',
|
||||||
|
meta={'foo': 'bar'}, files=files)
|
||||||
|
self.assertIn('files', six.text_type(ex))
|
||||||
|
@ -36,6 +36,7 @@ from novaclient import exceptions
|
|||||||
import novaclient.shell
|
import novaclient.shell
|
||||||
from novaclient.tests.unit import utils
|
from novaclient.tests.unit import utils
|
||||||
from novaclient.tests.unit.v2 import fakes
|
from novaclient.tests.unit.v2 import fakes
|
||||||
|
from novaclient.v2 import servers
|
||||||
import novaclient.v2.shell
|
import novaclient.v2.shell
|
||||||
|
|
||||||
FAKE_UUID_1 = fakes.FAKE_IMAGE_UUID_1
|
FAKE_UUID_1 = fakes.FAKE_IMAGE_UUID_1
|
||||||
@ -971,6 +972,16 @@ class ShellTest(utils.TestCase):
|
|||||||
' --file /foo=%s' % (FAKE_UUID_1, invalid_file))
|
' --file /foo=%s' % (FAKE_UUID_1, invalid_file))
|
||||||
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
|
self.assertRaises(exceptions.CommandError, self.run_command, cmd)
|
||||||
|
|
||||||
|
def test_boot_files_2_57(self):
|
||||||
|
"""Tests that trying to run the boot command with the --file option
|
||||||
|
after microversion 2.56 fails.
|
||||||
|
"""
|
||||||
|
testfile = os.path.join(os.path.dirname(__file__), 'testfile.txt')
|
||||||
|
cmd = ('boot some-server --flavor 1 --image %s'
|
||||||
|
' --file /tmp/foo=%s')
|
||||||
|
self.assertRaises(SystemExit, self.run_command,
|
||||||
|
cmd % (FAKE_UUID_1, testfile), api_version='2.57')
|
||||||
|
|
||||||
def test_boot_max_min_count(self):
|
def test_boot_max_min_count(self):
|
||||||
self.run_command('boot --image %s --flavor 1 --min-count 1'
|
self.run_command('boot --image %s --flavor 1 --min-count 1'
|
||||||
' --max-count 3 server' % FAKE_UUID_1)
|
' --max-count 3 server' % FAKE_UUID_1)
|
||||||
@ -1570,6 +1581,62 @@ class ShellTest(utils.TestCase):
|
|||||||
expected = "'['foo']' is not in the format of 'key=value'"
|
expected = "'['foo']' is not in the format of 'key=value'"
|
||||||
self.assertEqual(expected, result.args[0])
|
self.assertEqual(expected, result.args[0])
|
||||||
|
|
||||||
|
def test_rebuild_user_data_2_56(self):
|
||||||
|
"""Tests that trying to run the rebuild command with the --user-data*
|
||||||
|
options before microversion 2.57 fails.
|
||||||
|
"""
|
||||||
|
cmd = 'rebuild sample-server %s --user-data test' % FAKE_UUID_1
|
||||||
|
self.assertRaises(SystemExit, self.run_command, cmd,
|
||||||
|
api_version='2.56')
|
||||||
|
cmd = 'rebuild sample-server %s --user-data-unset' % FAKE_UUID_1
|
||||||
|
self.assertRaises(SystemExit, self.run_command, cmd,
|
||||||
|
api_version='2.56')
|
||||||
|
|
||||||
|
def test_rebuild_files_2_57(self):
|
||||||
|
"""Tests that trying to run the rebuild command with the --file option
|
||||||
|
after microversion 2.56 fails.
|
||||||
|
"""
|
||||||
|
testfile = os.path.join(os.path.dirname(__file__), 'testfile.txt')
|
||||||
|
cmd = 'rebuild sample-server %s --file /tmp/foo=%s'
|
||||||
|
self.assertRaises(SystemExit, self.run_command,
|
||||||
|
cmd % (FAKE_UUID_1, testfile), api_version='2.57')
|
||||||
|
|
||||||
|
def test_rebuild_change_user_data(self):
|
||||||
|
self.run_command('rebuild sample-server %s --user-data test' %
|
||||||
|
FAKE_UUID_1, api_version='2.57')
|
||||||
|
user_data = servers.ServerManager.transform_userdata('test')
|
||||||
|
self.assert_called('GET', '/servers?name=sample-server', pos=0)
|
||||||
|
self.assert_called('GET', '/servers/1234', pos=1)
|
||||||
|
self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2)
|
||||||
|
self.assert_called('POST', '/servers/1234/action',
|
||||||
|
{'rebuild': {'imageRef': FAKE_UUID_1,
|
||||||
|
'user_data': user_data,
|
||||||
|
'description': None}}, pos=3)
|
||||||
|
self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=4)
|
||||||
|
|
||||||
|
def test_rebuild_unset_user_data(self):
|
||||||
|
self.run_command('rebuild sample-server %s --user-data-unset' %
|
||||||
|
FAKE_UUID_1, api_version='2.57')
|
||||||
|
self.assert_called('GET', '/servers?name=sample-server', pos=0)
|
||||||
|
self.assert_called('GET', '/servers/1234', pos=1)
|
||||||
|
self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2)
|
||||||
|
self.assert_called('POST', '/servers/1234/action',
|
||||||
|
{'rebuild': {'imageRef': FAKE_UUID_1,
|
||||||
|
'user_data': None,
|
||||||
|
'description': None}}, pos=3)
|
||||||
|
self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=4)
|
||||||
|
|
||||||
|
def test_rebuild_user_data_and_unset_user_data(self):
|
||||||
|
"""Tests that trying to set --user-data and --unset-user-data in the
|
||||||
|
same rebuild call fails.
|
||||||
|
"""
|
||||||
|
cmd = ('rebuild sample-server %s --user-data x --user-data-unset' %
|
||||||
|
FAKE_UUID_1)
|
||||||
|
ex = self.assertRaises(exceptions.CommandError, self.run_command, cmd,
|
||||||
|
api_version='2.57')
|
||||||
|
self.assertIn("Cannot specify '--user-data-unset' with "
|
||||||
|
"'--user-data'.", six.text_type(ex))
|
||||||
|
|
||||||
def test_start(self):
|
def test_start(self):
|
||||||
self.run_command('start sample-server')
|
self.run_command('start sample-server')
|
||||||
self.assert_called('POST', '/servers/1234/action', {'os-start': None})
|
self.assert_called('POST', '/servers/1234/action', {'os-start': None})
|
||||||
@ -2643,6 +2710,17 @@ class ShellTest(utils.TestCase):
|
|||||||
'PUT', '/os-quota-sets/97f4c221bff44578b0300df4ef119353',
|
'PUT', '/os-quota-sets/97f4c221bff44578b0300df4ef119353',
|
||||||
{'quota_set': {'fixed_ips': 5}})
|
{'quota_set': {'fixed_ips': 5}})
|
||||||
|
|
||||||
|
def test_quota_update_injected_file_2_57(self):
|
||||||
|
"""Tests that trying to update injected_file* quota with microversion
|
||||||
|
2.57 fails.
|
||||||
|
"""
|
||||||
|
for quota in ('--injected-files', '--injected-file-content-bytes',
|
||||||
|
'--injected-file-path-bytes'):
|
||||||
|
cmd = ('quota-update 97f4c221bff44578b0300df4ef119353 %s=5' %
|
||||||
|
quota)
|
||||||
|
self.assertRaises(SystemExit, self.run_command, cmd,
|
||||||
|
api_version='2.57')
|
||||||
|
|
||||||
def test_quota_delete(self):
|
def test_quota_delete(self):
|
||||||
self.run_command('quota-delete --tenant '
|
self.run_command('quota-delete --tenant '
|
||||||
'97f4c221bff44578b0300df4ef119353')
|
'97f4c221bff44578b0300df4ef119353')
|
||||||
@ -2680,6 +2758,16 @@ class ShellTest(utils.TestCase):
|
|||||||
'PUT', '/os-quota-class-sets/97f4c221bff44578b0300df4ef119353',
|
'PUT', '/os-quota-class-sets/97f4c221bff44578b0300df4ef119353',
|
||||||
body)
|
body)
|
||||||
|
|
||||||
|
def test_quota_class_update_injected_file_2_57(self):
|
||||||
|
"""Tests that trying to update injected_file* quota with microversion
|
||||||
|
2.57 fails.
|
||||||
|
"""
|
||||||
|
for quota in ('--injected-files', '--injected-file-content-bytes',
|
||||||
|
'--injected-file-path-bytes'):
|
||||||
|
cmd = 'quota-class-update default %s=5' % quota
|
||||||
|
self.assertRaises(SystemExit, self.run_command, cmd,
|
||||||
|
api_version='2.57')
|
||||||
|
|
||||||
def test_backup(self):
|
def test_backup(self):
|
||||||
out, err = self.run_command('backup sample-server back1 daily 1')
|
out, err = self.run_command('backup sample-server back1 daily 1')
|
||||||
# With microversion < 2.45 there is no output from this command.
|
# With microversion < 2.45 there is no output from this command.
|
||||||
@ -2712,8 +2800,9 @@ class ShellTest(utils.TestCase):
|
|||||||
'rotation': '1'}})
|
'rotation': '1'}})
|
||||||
|
|
||||||
def test_limits(self):
|
def test_limits(self):
|
||||||
self.run_command('limits')
|
out = self.run_command('limits')[0]
|
||||||
self.assert_called('GET', '/limits')
|
self.assert_called('GET', '/limits')
|
||||||
|
self.assertIn('Personality', out)
|
||||||
|
|
||||||
self.run_command('limits --reserved')
|
self.run_command('limits --reserved')
|
||||||
self.assert_called('GET', '/limits?reserved=1')
|
self.assert_called('GET', '/limits?reserved=1')
|
||||||
@ -2725,6 +2814,14 @@ class ShellTest(utils.TestCase):
|
|||||||
self.assertIn('Verb', stdout)
|
self.assertIn('Verb', stdout)
|
||||||
self.assertIn('Name', stdout)
|
self.assertIn('Name', stdout)
|
||||||
|
|
||||||
|
def test_limits_2_57(self):
|
||||||
|
"""Tests the limits command at microversion 2.57 where personality
|
||||||
|
size limits should not be shown.
|
||||||
|
"""
|
||||||
|
out = self.run_command('limits', api_version='2.57')[0]
|
||||||
|
self.assert_called('GET', '/limits')
|
||||||
|
self.assertNotIn('Personality', out)
|
||||||
|
|
||||||
def test_evacuate(self):
|
def test_evacuate(self):
|
||||||
self.run_command('evacuate sample-server new_host')
|
self.run_command('evacuate sample-server new_host')
|
||||||
self.assert_called('POST', '/servers/1234/action',
|
self.assert_called('POST', '/servers/1234/action',
|
||||||
@ -3128,6 +3225,7 @@ class ShellTest(utils.TestCase):
|
|||||||
51, # There are no version-wrapped shell method changes for this.
|
51, # There are no version-wrapped shell method changes for this.
|
||||||
52, # There are no version-wrapped shell method changes for this.
|
52, # There are no version-wrapped shell method changes for this.
|
||||||
54, # There are no version-wrapped shell method changes for this.
|
54, # There are no version-wrapped shell method changes for this.
|
||||||
|
57, # There are no version-wrapped shell method changes for this.
|
||||||
])
|
])
|
||||||
versions_supported = set(range(0,
|
versions_supported = set(range(0,
|
||||||
novaclient.API_MAX_VERSION.ver_minor + 1))
|
novaclient.API_MAX_VERSION.ver_minor + 1))
|
||||||
|
@ -50,7 +50,7 @@ class QuotaClassSetManager(base.Manager):
|
|||||||
|
|
||||||
# NOTE(mriedem): 2.50 does strict validation of the resources you can
|
# NOTE(mriedem): 2.50 does strict validation of the resources you can
|
||||||
# specify since the network-related resources are blocked in 2.50.
|
# specify since the network-related resources are blocked in 2.50.
|
||||||
@api_versions.wraps("2.50")
|
@api_versions.wraps("2.50", "2.56")
|
||||||
def update(self, class_name, instances=None, cores=None, ram=None,
|
def update(self, class_name, instances=None, cores=None, ram=None,
|
||||||
metadata_items=None, injected_files=None,
|
metadata_items=None, injected_files=None,
|
||||||
injected_file_content_bytes=None, injected_file_path_bytes=None,
|
injected_file_content_bytes=None, injected_file_path_bytes=None,
|
||||||
@ -81,3 +81,30 @@ class QuotaClassSetManager(base.Manager):
|
|||||||
body = {'quota_class_set': resources}
|
body = {'quota_class_set': resources}
|
||||||
return self._update('/os-quota-class-sets/%s' % class_name, body,
|
return self._update('/os-quota-class-sets/%s' % class_name, body,
|
||||||
'quota_class_set')
|
'quota_class_set')
|
||||||
|
|
||||||
|
# NOTE(mriedem): 2.57 deprecates the usage of injected_files,
|
||||||
|
# injected_file_content_bytes and injected_file_path_bytes so those
|
||||||
|
# kwargs are removed.
|
||||||
|
@api_versions.wraps("2.57")
|
||||||
|
def update(self, class_name, instances=None, cores=None, ram=None,
|
||||||
|
metadata_items=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 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')
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from novaclient import api_versions
|
||||||
from novaclient import base
|
from novaclient import base
|
||||||
|
|
||||||
|
|
||||||
@ -38,6 +39,10 @@ class QuotaSetManager(base.Manager):
|
|||||||
|
|
||||||
return self._get(url % params, "quota_set")
|
return self._get(url % params, "quota_set")
|
||||||
|
|
||||||
|
# NOTE(mriedem): Before 2.57 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.56")
|
||||||
def update(self, tenant_id, **kwargs):
|
def update(self, tenant_id, **kwargs):
|
||||||
|
|
||||||
user_id = kwargs.pop('user_id', None)
|
user_id = kwargs.pop('user_id', None)
|
||||||
@ -53,6 +58,40 @@ class QuotaSetManager(base.Manager):
|
|||||||
url = '/os-quota-sets/%s' % tenant_id
|
url = '/os-quota-sets/%s' % tenant_id
|
||||||
return self._update(url, body, 'quota_set')
|
return self._update(url, body, 'quota_set')
|
||||||
|
|
||||||
|
# NOTE(mriedem): 2.57 does strict validation of the resources you can
|
||||||
|
# specify. 2.36 blocks network-related resources and 2.57 blocks
|
||||||
|
# injected files related quotas.
|
||||||
|
@api_versions.wraps("2.57")
|
||||||
|
def update(self, tenant_id, user_id=None, force=False,
|
||||||
|
instances=None, cores=None, ram=None,
|
||||||
|
metadata_items=None, key_pairs=None, server_groups=None,
|
||||||
|
server_group_members=None):
|
||||||
|
|
||||||
|
resources = {}
|
||||||
|
if force:
|
||||||
|
resources['force'] = force
|
||||||
|
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 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_set': resources}
|
||||||
|
|
||||||
|
if user_id:
|
||||||
|
url = '/os-quota-sets/%s?user_id=%s' % (tenant_id, user_id)
|
||||||
|
else:
|
||||||
|
url = '/os-quota-sets/%s' % tenant_id
|
||||||
|
return self._update(url, body, 'quota_set')
|
||||||
|
|
||||||
def defaults(self, tenant_id):
|
def defaults(self, tenant_id):
|
||||||
return self._get('/os-quota-sets/%s/defaults' % tenant_id,
|
return self._get('/os-quota-sets/%s/defaults' % tenant_id,
|
||||||
'quota_set')
|
'quota_set')
|
||||||
|
@ -621,6 +621,27 @@ class SecurityGroup(base.Resource):
|
|||||||
class ServerManager(base.BootingManagerWithFind):
|
class ServerManager(base.BootingManagerWithFind):
|
||||||
resource_class = Server
|
resource_class = Server
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def transform_userdata(userdata):
|
||||||
|
if hasattr(userdata, 'read'):
|
||||||
|
userdata = userdata.read()
|
||||||
|
|
||||||
|
# NOTE(melwitt): Text file data is converted to bytes prior to
|
||||||
|
# base64 encoding. The utf-8 encoding will fail for binary files.
|
||||||
|
if six.PY3:
|
||||||
|
try:
|
||||||
|
userdata = userdata.encode("utf-8")
|
||||||
|
except AttributeError:
|
||||||
|
# In python 3, 'bytes' object has no attribute 'encode'
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
userdata = encodeutils.safe_encode(userdata)
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return base64.b64encode(userdata).decode('utf-8')
|
||||||
|
|
||||||
def _boot(self, response_key, name, image, flavor,
|
def _boot(self, response_key, name, image, flavor,
|
||||||
meta=None, files=None, userdata=None,
|
meta=None, files=None, userdata=None,
|
||||||
reservation_id=False, return_raw=False, min_count=None,
|
reservation_id=False, return_raw=False, min_count=None,
|
||||||
@ -639,25 +660,7 @@ class ServerManager(base.BootingManagerWithFind):
|
|||||||
"flavorRef": str(base.getid(flavor)),
|
"flavorRef": str(base.getid(flavor)),
|
||||||
}}
|
}}
|
||||||
if userdata:
|
if userdata:
|
||||||
if hasattr(userdata, 'read'):
|
body["server"]["user_data"] = self.transform_userdata(userdata)
|
||||||
userdata = userdata.read()
|
|
||||||
|
|
||||||
# NOTE(melwitt): Text file data is converted to bytes prior to
|
|
||||||
# base64 encoding. The utf-8 encoding will fail for binary files.
|
|
||||||
if six.PY3:
|
|
||||||
try:
|
|
||||||
userdata = userdata.encode("utf-8")
|
|
||||||
except AttributeError:
|
|
||||||
# In python 3, 'bytes' object has no attribute 'encode'
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
userdata = encodeutils.safe_encode(userdata)
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
userdata_b64 = base64.b64encode(userdata).decode('utf-8')
|
|
||||||
body["server"]["user_data"] = userdata_b64
|
|
||||||
if meta:
|
if meta:
|
||||||
body["server"]["metadata"] = meta
|
body["server"]["metadata"] = meta
|
||||||
if reservation_id:
|
if reservation_id:
|
||||||
@ -1204,6 +1207,7 @@ class ServerManager(base.BootingManagerWithFind):
|
|||||||
are the file contents (either as a string or as a
|
are the file contents (either as a string or as a
|
||||||
file-like object). A maximum of five entries is allowed,
|
file-like object). A maximum of five entries is allowed,
|
||||||
and each file must be 10k or less.
|
and each file must be 10k or less.
|
||||||
|
(deprecated starting with microversion 2.57)
|
||||||
:param reservation_id: return a reservation_id for the set of
|
:param reservation_id: return a reservation_id for the set of
|
||||||
servers being requested, boolean.
|
servers being requested, boolean.
|
||||||
:param min_count: (optional extension) The minimum number of
|
:param min_count: (optional extension) The minimum number of
|
||||||
@ -1284,6 +1288,10 @@ class ServerManager(base.BootingManagerWithFind):
|
|||||||
if "tags" in kwargs and self.api_version < boot_tags_microversion:
|
if "tags" in kwargs and self.api_version < boot_tags_microversion:
|
||||||
raise exceptions.UnsupportedAttribute("tags", "2.52")
|
raise exceptions.UnsupportedAttribute("tags", "2.52")
|
||||||
|
|
||||||
|
personality_files_deprecation = api_versions.APIVersion('2.57')
|
||||||
|
if files and self.api_version >= personality_files_deprecation:
|
||||||
|
raise exceptions.UnsupportedAttribute('files', '2.0', '2.56')
|
||||||
|
|
||||||
boot_kwargs = dict(
|
boot_kwargs = dict(
|
||||||
meta=meta, files=files, userdata=userdata,
|
meta=meta, files=files, userdata=userdata,
|
||||||
reservation_id=reservation_id, min_count=min_count,
|
reservation_id=reservation_id, min_count=min_count,
|
||||||
@ -1397,11 +1405,17 @@ class ServerManager(base.BootingManagerWithFind):
|
|||||||
are the file contents (either as a string or as a
|
are the file contents (either as a string or as a
|
||||||
file-like object). A maximum of five entries is allowed,
|
file-like object). A maximum of five entries is allowed,
|
||||||
and each file must be 10k or less.
|
and each file must be 10k or less.
|
||||||
|
(deprecated starting with microversion 2.57)
|
||||||
:param description: optional description of the server (allowed since
|
:param description: optional description of the server (allowed since
|
||||||
microversion 2.19)
|
microversion 2.19)
|
||||||
:param key_name: optional key pair name for rebuild operation; passing
|
:param key_name: optional key pair name for rebuild operation; passing
|
||||||
None will unset the key for the server instance
|
None will unset the key for the server instance
|
||||||
(starting from microversion 2.54)
|
(starting from microversion 2.54)
|
||||||
|
:param userdata: optional user data to pass to be exposed by the
|
||||||
|
metadata server; this can be a file type object as
|
||||||
|
well or a string. If None is specified, the existing
|
||||||
|
user_data is unset.
|
||||||
|
(starting from microversion 2.57)
|
||||||
:returns: :class:`Server`
|
:returns: :class:`Server`
|
||||||
"""
|
"""
|
||||||
descr_microversion = api_versions.APIVersion("2.19")
|
descr_microversion = api_versions.APIVersion("2.19")
|
||||||
@ -1414,6 +1428,14 @@ class ServerManager(base.BootingManagerWithFind):
|
|||||||
self.api_version < api_versions.APIVersion('2.54')):
|
self.api_version < api_versions.APIVersion('2.54')):
|
||||||
raise exceptions.UnsupportedAttribute('key_name', '2.54')
|
raise exceptions.UnsupportedAttribute('key_name', '2.54')
|
||||||
|
|
||||||
|
# Microversion 2.57 deprecates personality files and adds support
|
||||||
|
# for user_data.
|
||||||
|
files_and_userdata = api_versions.APIVersion('2.57')
|
||||||
|
if files and self.api_version >= files_and_userdata:
|
||||||
|
raise exceptions.UnsupportedAttribute('files', '2.0', '2.56')
|
||||||
|
if 'userdata' in kwargs and self.api_version < files_and_userdata:
|
||||||
|
raise exceptions.UnsupportedAttribute('userdata', '2.57')
|
||||||
|
|
||||||
body = {'imageRef': base.getid(image)}
|
body = {'imageRef': base.getid(image)}
|
||||||
if password is not None:
|
if password is not None:
|
||||||
body['adminPass'] = password
|
body['adminPass'] = password
|
||||||
@ -1443,6 +1465,12 @@ class ServerManager(base.BootingManagerWithFind):
|
|||||||
'path': filepath,
|
'path': filepath,
|
||||||
'contents': cont,
|
'contents': cont,
|
||||||
})
|
})
|
||||||
|
if 'userdata' in kwargs:
|
||||||
|
# If userdata is specified but None, it means unset the existing
|
||||||
|
# user_data on the instance.
|
||||||
|
userdata = kwargs['userdata']
|
||||||
|
body['user_data'] = (userdata if userdata is None else
|
||||||
|
self.transform_userdata(userdata))
|
||||||
|
|
||||||
resp, body = self._action_return_resp_and_body('rebuild', server,
|
resp, body = self._action_return_resp_and_body('rebuild', server,
|
||||||
body, **kwargs)
|
body, **kwargs)
|
||||||
|
@ -391,19 +391,22 @@ def _boot(cs, args):
|
|||||||
|
|
||||||
meta = _meta_parsing(args.meta)
|
meta = _meta_parsing(args.meta)
|
||||||
|
|
||||||
files = {}
|
include_files = cs.api_version < api_versions.APIVersion('2.57')
|
||||||
for f in args.files:
|
if include_files:
|
||||||
try:
|
files = {}
|
||||||
dst, src = f.split('=', 1)
|
for f in args.files:
|
||||||
files[dst] = open(src)
|
try:
|
||||||
except IOError as e:
|
dst, src = f.split('=', 1)
|
||||||
raise exceptions.CommandError(_("Can't open '%(src)s': %(exc)s") %
|
files[dst] = open(src)
|
||||||
{'src': src, 'exc': e})
|
except IOError as e:
|
||||||
except ValueError:
|
raise exceptions.CommandError(
|
||||||
raise exceptions.CommandError(_("Invalid file argument '%s'. "
|
_("Can't open '%(src)s': %(exc)s") %
|
||||||
"File arguments must be of the "
|
{'src': src, 'exc': e})
|
||||||
"form '--file "
|
except ValueError:
|
||||||
"<dst-path=src-path>'") % f)
|
raise exceptions.CommandError(
|
||||||
|
_("Invalid file argument '%s'. "
|
||||||
|
"File arguments must be of the "
|
||||||
|
"form '--file <dst-path=src-path>'") % f)
|
||||||
|
|
||||||
# use the os-keypair extension
|
# use the os-keypair extension
|
||||||
key_name = None
|
key_name = None
|
||||||
@ -481,7 +484,6 @@ def _boot(cs, args):
|
|||||||
|
|
||||||
boot_kwargs = dict(
|
boot_kwargs = dict(
|
||||||
meta=meta,
|
meta=meta,
|
||||||
files=files,
|
|
||||||
key_name=key_name,
|
key_name=key_name,
|
||||||
min_count=min_count,
|
min_count=min_count,
|
||||||
max_count=max_count,
|
max_count=max_count,
|
||||||
@ -504,6 +506,9 @@ def _boot(cs, args):
|
|||||||
if 'tags' in args and args.tags:
|
if 'tags' in args and args.tags:
|
||||||
boot_kwargs["tags"] = args.tags.split(',')
|
boot_kwargs["tags"] = args.tags.split(',')
|
||||||
|
|
||||||
|
if include_files:
|
||||||
|
boot_kwargs['files'] = files
|
||||||
|
|
||||||
return boot_args, boot_kwargs
|
return boot_args, boot_kwargs
|
||||||
|
|
||||||
|
|
||||||
@ -563,7 +568,8 @@ def _boot(cs, args):
|
|||||||
"on the new server. More files can be injected using multiple "
|
"on the new server. More files can be injected using multiple "
|
||||||
"'--file' options. Limited by the 'injected_files' quota value. "
|
"'--file' options. Limited by the 'injected_files' quota value. "
|
||||||
"The default value is 5. You can get the current quota value by "
|
"The default value is 5. You can get the current quota value by "
|
||||||
"'Personality' limit from 'nova limits' command."))
|
"'Personality' limit from 'nova limits' command."),
|
||||||
|
start_version='2.0', end_version='2.56')
|
||||||
@utils.arg(
|
@utils.arg(
|
||||||
'--key-name',
|
'--key-name',
|
||||||
default=os.environ.get('NOVACLIENT_DEFAULT_KEY_NAME'),
|
default=os.environ.get('NOVACLIENT_DEFAULT_KEY_NAME'),
|
||||||
@ -1770,7 +1776,8 @@ def do_reboot(cs, args):
|
|||||||
"on the new server. More files can be injected using multiple "
|
"on the new server. More files can be injected using multiple "
|
||||||
"'--file' options. You may store up to 5 files by default. "
|
"'--file' options. You may store up to 5 files by default. "
|
||||||
"The maximum number of files is specified by the 'Personality' "
|
"The maximum number of files is specified by the 'Personality' "
|
||||||
"limit reported by the 'nova limits' command."))
|
"limit reported by the 'nova limits' command."),
|
||||||
|
start_version='2.0', end_version='2.56')
|
||||||
@utils.arg(
|
@utils.arg(
|
||||||
'--key-name',
|
'--key-name',
|
||||||
metavar='<key-name>',
|
metavar='<key-name>',
|
||||||
@ -1785,6 +1792,19 @@ def do_reboot(cs, args):
|
|||||||
help=_("Unset keypair in the server. "
|
help=_("Unset keypair in the server. "
|
||||||
"Cannot be specified with the '--key-name' option."),
|
"Cannot be specified with the '--key-name' option."),
|
||||||
start_version='2.54')
|
start_version='2.54')
|
||||||
|
@utils.arg(
|
||||||
|
'--user-data',
|
||||||
|
default=None,
|
||||||
|
metavar='<user-data>',
|
||||||
|
help=_("User data file to pass to be exposed by the metadata server."),
|
||||||
|
start_version='2.57')
|
||||||
|
@utils.arg(
|
||||||
|
'--user-data-unset',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help=_("Unset user_data in the server. Cannot be specified with the "
|
||||||
|
"'--user-data' option."),
|
||||||
|
start_version='2.57')
|
||||||
def do_rebuild(cs, args):
|
def do_rebuild(cs, args):
|
||||||
"""Shutdown, re-image, and re-boot a server."""
|
"""Shutdown, re-image, and re-boot a server."""
|
||||||
server = _find_server(cs, args.server)
|
server = _find_server(cs, args.server)
|
||||||
@ -1803,21 +1823,34 @@ def do_rebuild(cs, args):
|
|||||||
meta = _meta_parsing(args.meta)
|
meta = _meta_parsing(args.meta)
|
||||||
kwargs['meta'] = meta
|
kwargs['meta'] = meta
|
||||||
|
|
||||||
files = {}
|
# 2.57 deprecates the --file option and adds the --user-data and
|
||||||
for f in args.files:
|
# --user-data-unset options.
|
||||||
try:
|
if cs.api_version < api_versions.APIVersion('2.57'):
|
||||||
dst, src = f.split('=', 1)
|
files = {}
|
||||||
with open(src, 'r') as s:
|
for f in args.files:
|
||||||
files[dst] = s.read()
|
try:
|
||||||
except IOError as e:
|
dst, src = f.split('=', 1)
|
||||||
raise exceptions.CommandError(_("Can't open '%(src)s': %(exc)s") %
|
with open(src, 'r') as s:
|
||||||
{'src': src, 'exc': e})
|
files[dst] = s.read()
|
||||||
except ValueError:
|
except IOError as e:
|
||||||
raise exceptions.CommandError(_("Invalid file argument '%s'. "
|
raise exceptions.CommandError(
|
||||||
"File arguments must be of the "
|
_("Can't open '%(src)s': %(exc)s") %
|
||||||
"form '--file "
|
{'src': src, 'exc': e})
|
||||||
"<dst-path=src-path>'") % f)
|
except ValueError:
|
||||||
kwargs['files'] = files
|
raise exceptions.CommandError(
|
||||||
|
_("Invalid file argument '%s'. "
|
||||||
|
"File arguments must be of the "
|
||||||
|
"form '--file <dst-path=src-path>'") % f)
|
||||||
|
kwargs['files'] = files
|
||||||
|
else:
|
||||||
|
if args.user_data_unset:
|
||||||
|
kwargs['userdata'] = None
|
||||||
|
if args.user_data:
|
||||||
|
raise exceptions.CommandError(
|
||||||
|
_("Cannot specify '--user-data-unset' with "
|
||||||
|
"'--user-data'."))
|
||||||
|
elif args.user_data:
|
||||||
|
kwargs['userdata'] = args.user_data
|
||||||
|
|
||||||
if cs.api_version >= api_versions.APIVersion('2.54'):
|
if cs.api_version >= api_versions.APIVersion('2.54'):
|
||||||
if args.key_unset:
|
if args.key_unset:
|
||||||
@ -3739,6 +3772,10 @@ def do_ssh(cs, args):
|
|||||||
# return floating_ips, fixed_ips, security_groups or security_group_members
|
# return floating_ips, fixed_ips, security_groups or security_group_members
|
||||||
# as those are deprecated as networking service proxies and/or because
|
# as those are deprecated as networking service proxies and/or because
|
||||||
# nova-network is deprecated. Similar to the 2.36 microversion.
|
# nova-network is deprecated. Similar to the 2.36 microversion.
|
||||||
|
# NOTE(mriedem): In the 2.57 microversion, the os-quota-sets and
|
||||||
|
# os-quota-class-sets APIs will no longer return injected_files,
|
||||||
|
# injected_file_content_bytes or injected_file_content_bytes since personality
|
||||||
|
# files (file injection) is deprecated starting with v2.57.
|
||||||
_quota_resources = ['instances', 'cores', 'ram',
|
_quota_resources = ['instances', 'cores', 'ram',
|
||||||
'floating_ips', 'fixed_ips', 'metadata_items',
|
'floating_ips', 'fixed_ips', 'metadata_items',
|
||||||
'injected_files', 'injected_file_content_bytes',
|
'injected_files', 'injected_file_content_bytes',
|
||||||
@ -3942,6 +3979,7 @@ def do_quota_update(cs, args):
|
|||||||
|
|
||||||
# 2.36 does not support updating quota for floating IPs, fixed IPs, security
|
# 2.36 does not support updating quota for floating IPs, fixed IPs, security
|
||||||
# groups or security group rules.
|
# groups or security group rules.
|
||||||
|
# 2.57 does not support updating injected_file* quotas.
|
||||||
@api_versions.wraps("2.36")
|
@api_versions.wraps("2.36")
|
||||||
@utils.arg(
|
@utils.arg(
|
||||||
'tenant',
|
'tenant',
|
||||||
@ -3978,19 +4016,22 @@ def do_quota_update(cs, args):
|
|||||||
metavar='<injected-files>',
|
metavar='<injected-files>',
|
||||||
type=int,
|
type=int,
|
||||||
default=None,
|
default=None,
|
||||||
help=_('New value for the "injected-files" quota.'))
|
help=_('New value for the "injected-files" quota.'),
|
||||||
|
start_version='2.36', end_version='2.56')
|
||||||
@utils.arg(
|
@utils.arg(
|
||||||
'--injected-file-content-bytes',
|
'--injected-file-content-bytes',
|
||||||
metavar='<injected-file-content-bytes>',
|
metavar='<injected-file-content-bytes>',
|
||||||
type=int,
|
type=int,
|
||||||
default=None,
|
default=None,
|
||||||
help=_('New value for the "injected-file-content-bytes" quota.'))
|
help=_('New value for the "injected-file-content-bytes" quota.'),
|
||||||
|
start_version='2.36', end_version='2.56')
|
||||||
@utils.arg(
|
@utils.arg(
|
||||||
'--injected-file-path-bytes',
|
'--injected-file-path-bytes',
|
||||||
metavar='<injected-file-path-bytes>',
|
metavar='<injected-file-path-bytes>',
|
||||||
type=int,
|
type=int,
|
||||||
default=None,
|
default=None,
|
||||||
help=_('New value for the "injected-file-path-bytes" quota.'))
|
help=_('New value for the "injected-file-path-bytes" quota.'),
|
||||||
|
start_version='2.36', end_version='2.56')
|
||||||
@utils.arg(
|
@utils.arg(
|
||||||
'--key-pairs',
|
'--key-pairs',
|
||||||
metavar='<key-pairs>',
|
metavar='<key-pairs>',
|
||||||
@ -4147,6 +4188,7 @@ def do_quota_class_update(cs, args):
|
|||||||
|
|
||||||
# 2.50 does not support updating quota class values for floating IPs,
|
# 2.50 does not support updating quota class values for floating IPs,
|
||||||
# fixed IPs, security groups or security group rules.
|
# fixed IPs, security groups or security group rules.
|
||||||
|
# 2.57 does not support updating injected_file* quotas.
|
||||||
@api_versions.wraps("2.50")
|
@api_versions.wraps("2.50")
|
||||||
@utils.arg(
|
@utils.arg(
|
||||||
'class_name',
|
'class_name',
|
||||||
@ -4178,19 +4220,22 @@ def do_quota_class_update(cs, args):
|
|||||||
metavar='<injected-files>',
|
metavar='<injected-files>',
|
||||||
type=int,
|
type=int,
|
||||||
default=None,
|
default=None,
|
||||||
help=_('New value for the "injected-files" quota.'))
|
help=_('New value for the "injected-files" quota.'),
|
||||||
|
start_version='2.50', end_version='2.56')
|
||||||
@utils.arg(
|
@utils.arg(
|
||||||
'--injected-file-content-bytes',
|
'--injected-file-content-bytes',
|
||||||
metavar='<injected-file-content-bytes>',
|
metavar='<injected-file-content-bytes>',
|
||||||
type=int,
|
type=int,
|
||||||
default=None,
|
default=None,
|
||||||
help=_('New value for the "injected-file-content-bytes" quota.'))
|
help=_('New value for the "injected-file-content-bytes" quota.'),
|
||||||
|
start_version='2.50', end_version='2.56')
|
||||||
@utils.arg(
|
@utils.arg(
|
||||||
'--injected-file-path-bytes',
|
'--injected-file-path-bytes',
|
||||||
metavar='<injected-file-path-bytes>',
|
metavar='<injected-file-path-bytes>',
|
||||||
type=int,
|
type=int,
|
||||||
default=None,
|
default=None,
|
||||||
help=_('New value for the "injected-file-path-bytes" quota.'))
|
help=_('New value for the "injected-file-path-bytes" quota.'),
|
||||||
|
start_version='2.50', end_version='2.56')
|
||||||
@utils.arg(
|
@utils.arg(
|
||||||
'--key-pairs',
|
'--key-pairs',
|
||||||
metavar='<key-pairs>',
|
metavar='<key-pairs>',
|
||||||
|
31
releasenotes/notes/microversion-v2_57-acae2ee11ddae4fb.yaml
Normal file
31
releasenotes/notes/microversion-v2_57-acae2ee11ddae4fb.yaml
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Support is added for the 2.57 microversion:
|
||||||
|
|
||||||
|
* A ``userdata`` keyword argument can be passed to the ``Server.rebuild``
|
||||||
|
python API binding. If set to None, it will unset any existing userdata
|
||||||
|
on the server.
|
||||||
|
* The ``--user-data`` and ``--user-data-unset`` options are added to the
|
||||||
|
``nova rebuild`` CLI. The options are mutually exclusive. Specifying
|
||||||
|
``--user-data`` will overwrite the existing userdata in the server, and
|
||||||
|
``--user-data-unset`` will unset any existing userdata on the server.
|
||||||
|
upgrade:
|
||||||
|
- |
|
||||||
|
Support is added for the 2.57 microversion:
|
||||||
|
|
||||||
|
* The ``--file`` option for the ``nova boot`` and ``nova rebuild`` CLIs is
|
||||||
|
capped at the 2.56 microversion. Similarly, the ``file`` parameter to
|
||||||
|
the ``Server.create`` and ``Server.rebuild`` python API binding methods
|
||||||
|
is capped at 2.56. Users are recommended to use the ``--user-data``
|
||||||
|
option instead.
|
||||||
|
* The ``--injected-files``, ``--injected-file-content-bytes`` and
|
||||||
|
``--injected-file-path-bytes`` options are capped at the 2.56
|
||||||
|
microversion in the ``nova quota-update`` and ``nova quota-class-update``
|
||||||
|
commands.
|
||||||
|
* The ``maxPersonality`` and ``maxPersonalitySize`` fields are capped at
|
||||||
|
the 2.56 microversion in the ``nova limits`` command and API binding.
|
||||||
|
* The ``injected_files``, ``injected_file_content_bytes`` and
|
||||||
|
``injected_file_path_bytes`` entries are capped at version 2.56 from
|
||||||
|
the output of the ``nova quota-show`` and ``nova quota-class-show``
|
||||||
|
commands and related python API bindings.
|
Loading…
x
Reference in New Issue
Block a user