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