diff --git a/doc/source/cli/nova.rst b/doc/source/cli/nova.rst index b94e52afb..fd8dbc4d4 100644 --- a/doc/source/cli/nova.rst +++ b/doc/source/cli/nova.rst @@ -73,6 +73,9 @@ nova usage ``aggregate-add-host`` Add the host to the specified aggregate. +``aggregate-cache-images`` + Request images be pre-cached on hosts within an aggregate. + ``aggregate-create`` Create a new aggregate with the specified details. @@ -756,6 +759,25 @@ Add the host to the specified aggregate. ``<host>`` The host to add to the aggregate. +.. _nova_aggregate-cache-images: + +nova aggregate-cache-images +--------------------------- + +.. code-block:: console + + usage: nova aggregate-cache-images <aggregate> <image> [<image> ..] + +Request image(s) be pre-cached on hosts within the aggregate. + +**Positional arguments:** + +``<aggregate>`` + Name or ID of aggregate. + +``<image>`` + Name or ID of image(s) to cache. + .. _nova_aggregate-create: nova aggregate-create diff --git a/novaclient/__init__.py b/novaclient/__init__.py index c4f56fba0..6855e2f21 100644 --- a/novaclient/__init__.py +++ b/novaclient/__init__.py @@ -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.80") +API_MAX_VERSION = api_versions.APIVersion("2.81") diff --git a/novaclient/tests/unit/fixture_data/aggregates.py b/novaclient/tests/unit/fixture_data/aggregates.py index 3ea64b8b7..b3ab88c5f 100644 --- a/novaclient/tests/unit/fixture_data/aggregates.py +++ b/novaclient/tests/unit/fixture_data/aggregates.py @@ -51,3 +51,10 @@ class Fixture(base.Fixture): self.requests_mock.delete(self.url(1), status_code=202, headers=self.json_headers) + + self.requests_mock.register_uri('POST', self.url(1), + json={}, + headers=self.json_headers) + self.requests_mock.post(self.url(1, 'images'), + json={}, + headers=self.json_headers) diff --git a/novaclient/tests/unit/v2/fakes.py b/novaclient/tests/unit/v2/fakes.py index 4cb4025ea..62d5e727e 100644 --- a/novaclient/tests/unit/v2/fakes.py +++ b/novaclient/tests/unit/v2/fakes.py @@ -1734,6 +1734,9 @@ class FakeSessionClient(base_client.SessionClient): def delete_os_aggregates_1(self, **kw): return (202, {}, None) + def post_os_aggregates_1_images(self, body, **kw): + return (202, {}, None) + # # Services # diff --git a/novaclient/tests/unit/v2/test_aggregates.py b/novaclient/tests/unit/v2/test_aggregates.py index 1de128238..4f3eecdf5 100644 --- a/novaclient/tests/unit/v2/test_aggregates.py +++ b/novaclient/tests/unit/v2/test_aggregates.py @@ -13,11 +13,14 @@ # License for the specific language governing permissions and limitations # under the License. +from novaclient import api_versions +from novaclient import exceptions from novaclient.tests.unit.fixture_data import aggregates as data from novaclient.tests.unit.fixture_data import client from novaclient.tests.unit import utils from novaclient.tests.unit.v2 import fakes from novaclient.v2 import aggregates +from novaclient.v2 import images class AggregatesTest(utils.FixturedTestCase): @@ -161,3 +164,40 @@ class AggregatesTest(utils.FixturedTestCase): result3 = self.cs.aggregates.delete(aggregate) self.assert_request_id(result3, fakes.FAKE_REQUEST_ID_LIST) self.assert_called('DELETE', '/os-aggregates/1') + + +class AggregatesV281Test(utils.FixturedTestCase): + api_version = "2.81" + data_fixture_class = data.Fixture + + scenarios = [('original', {'client_fixture_class': client.V1}), + ('session', {'client_fixture_class': client.SessionV1})] + + def setUp(self): + super(AggregatesV281Test, self).setUp() + self.cs.api_version = api_versions.APIVersion(self.api_version) + + def test_cache_images(self): + aggregate = self.cs.aggregates.list()[0] + _images = [images.Image(self.cs.aggregates, {'id': '1'}), + images.Image(self.cs.aggregates, {'id': '2'})] + aggregate.cache_images(_images) + expected_body = {'cache': [{'id': image.id} + for image in _images]} + self.assert_called('POST', '/os-aggregates/1/images', + expected_body) + + def test_cache_images_just_ids(self): + aggregate = self.cs.aggregates.list()[0] + _images = ['1'] + aggregate.cache_images(_images) + expected_body = {'cache': [{'id': '1'}]} + self.assert_called('POST', '/os-aggregates/1/images', + expected_body) + + def test_cache_images_pre281(self): + self.cs.api_version = api_versions.APIVersion('2.80') + aggregate = self.cs.aggregates.list()[0] + _images = [images.Image(self.cs.aggregates, {'id': '1'})] + self.assertRaises(exceptions.VersionNotFoundForAPIMethod, + aggregate.cache_images, _images) diff --git a/novaclient/tests/unit/v2/test_shell.py b/novaclient/tests/unit/v2/test_shell.py index 56fa3544b..b68254a21 100644 --- a/novaclient/tests/unit/v2/test_shell.py +++ b/novaclient/tests/unit/v2/test_shell.py @@ -2884,6 +2884,30 @@ class ShellTest(utils.TestCase): self.run_command('aggregate-show test') self.assert_called('GET', '/os-aggregates') + def test_aggregate_cache_images(self): + self.run_command( + 'aggregate-cache-images 1 %s %s' % ( + FAKE_UUID_1, FAKE_UUID_2), + api_version='2.81') + body = { + 'cache': [{'id': FAKE_UUID_1}, + {'id': FAKE_UUID_2}], + } + self.assert_called('POST', '/os-aggregates/1/images', body) + + def test_aggregate_cache_images_no_images(self): + self.assertRaises(SystemExit, + self.run_command, + 'aggregate-cache-images 1', + api_version='2.81') + + def test_aggregate_cache_images_pre281(self): + self.assertRaises(SystemExit, + self.run_command, + 'aggregate-cache-images 1 %s %s' % ( + FAKE_UUID_1, FAKE_UUID_2), + api_version='2.80') + def test_live_migration(self): self.run_command('live-migration sample-server hostname') self.assert_called('POST', '/servers/1234/action', diff --git a/novaclient/v2/aggregates.py b/novaclient/v2/aggregates.py index 9d4dff822..d2cbaa858 100644 --- a/novaclient/v2/aggregates.py +++ b/novaclient/v2/aggregates.py @@ -15,6 +15,7 @@ """Aggregate interface.""" +from novaclient import api_versions from novaclient import base @@ -45,6 +46,10 @@ class Aggregate(base.Resource): """ return self.manager.delete(self) + @api_versions.wraps("2.81") + def cache_images(self, images): + return self.manager.cache_images(self, images) + class AggregateManager(base.ManagerWithFind): resource_class = Aggregate @@ -103,3 +108,20 @@ class AggregateManager(base.ManagerWithFind): :returns: An instance of novaclient.base.TupleWithMeta """ return self._delete('/os-aggregates/%s' % (base.getid(aggregate))) + + @api_versions.wraps("2.81") + def cache_images(self, aggregate, images): + """ + Request images be cached on a given aggregate. + + :param aggregate: The aggregate to target + :param images: A list of image IDs to request caching + :returns: An instance of novaclient.base.TupleWithMeta + """ + body = { + 'cache': [{'id': base.getid(image)} for image in images], + } + resp, body = self.api.client.post( + "/os-aggregates/%s/images" % base.getid(aggregate), + body=body) + return self.convert_into_with_meta(body, resp) diff --git a/novaclient/v2/shell.py b/novaclient/v2/shell.py index 818006b98..0f8b5ce6f 100644 --- a/novaclient/v2/shell.py +++ b/novaclient/v2/shell.py @@ -3526,6 +3526,21 @@ def _print_aggregate_details(cs, aggregate): utils.print_list([aggregate], columns, formatters=formatters) +@api_versions.wraps("2.81") +@utils.arg( + 'aggregate', metavar='<aggregate>', + help=_('Name or ID of the aggregate.')) +@utils.arg( + 'images', metavar='<image>', nargs='+', + help=_('Name or ID of image(s) to cache on the hosts within ' + 'the aggregate.')) +def do_aggregate_cache_images(cs, args): + """Request images be cached.""" + aggregate = _find_aggregate(cs, args.aggregate) + images = _find_images(cs, args.images) + cs.aggregates.cache_images(aggregate, images) + + @utils.arg('server', metavar='<server>', help=_('Name or ID of server.')) @utils.arg( 'host', metavar='<host>', default=None, nargs='?', diff --git a/releasenotes/notes/microversion-v2_81-3ddd8e2fc7e45030.yaml b/releasenotes/notes/microversion-v2_81-3ddd8e2fc7e45030.yaml new file mode 100644 index 000000000..a51336d60 --- /dev/null +++ b/releasenotes/notes/microversion-v2_81-3ddd8e2fc7e45030.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + Added support for `microversion 2.81`_ which adds image pre-caching support by + aggregate. + + - The ``aggregate-cache-images`` command is added to the CLI + - The ``cache_images()`` method is added to the python API binding + + .. _microversion 2.81: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id73