diff --git a/doc/source/user/proxies/block_storage_v3.rst b/doc/source/user/proxies/block_storage_v3.rst
index 65b5bb919..d91c5ea5e 100644
--- a/doc/source/user/proxies/block_storage_v3.rst
+++ b/doc/source/user/proxies/block_storage_v3.rst
@@ -17,8 +17,8 @@ Volume Operations
 
 .. autoclass:: openstack.block_storage.v3._proxy.Proxy
   :noindex:
-  :members: create_volume, delete_volume, get_volume, find_volume,
-            volumes, get_volume_metadata, set_volume_metadata,
+  :members: create_volume, delete_volume, update_volume, get_volume,
+            find_volume, volumes, get_volume_metadata, set_volume_metadata,
             delete_volume_metadata, extend_volume, set_volume_readonly,
             retype_volume, set_volume_bootable_status, reset_volume_status,
             revert_volume_to_snapshot, attach_volume, detach_volume,
diff --git a/openstack/block_storage/v3/_proxy.py b/openstack/block_storage/v3/_proxy.py
index 0da1d97e9..3643113ea 100644
--- a/openstack/block_storage/v3/_proxy.py
+++ b/openstack/block_storage/v3/_proxy.py
@@ -519,6 +519,18 @@ class Proxy(_base_proxy.BaseBlockStorageProxy):
             volume = self._get_resource(_volume.Volume, volume)
             volume.force_delete(self)
 
+    def update_volume(self, volume, **attrs):
+        """Update a volume
+
+        :param volume: Either the ID of a volume or a
+            :class:`~openstack.block_storage.v3.volume.Volume` instance.
+        :param dict attrs: The attributes to update on the volume.
+
+        :returns: The updated volume
+        :rtype: :class:`~openstack.block_storage.v3.volume.Volume`
+        """
+        return self._update(_volume.Volume, volume, **attrs)
+
     def get_volume_metadata(self, volume):
         """Return a dictionary of metadata for a volume
 
diff --git a/openstack/tests/functional/block_storage/v3/test_volume.py b/openstack/tests/functional/block_storage/v3/test_volume.py
index 155461aca..c38ca1e6a 100644
--- a/openstack/tests/functional/block_storage/v3/test_volume.py
+++ b/openstack/tests/functional/block_storage/v3/test_volume.py
@@ -17,34 +17,55 @@ from openstack.tests.functional.block_storage.v3 import base
 class TestVolume(base.BaseBlockStorageTest):
 
     def setUp(self):
-        super(TestVolume, self).setUp()
+        super().setUp()
 
         if not self.user_cloud.has_service('block-storage'):
             self.skipTest('block-storage service not supported by cloud')
 
-        self.VOLUME_NAME = self.getUniqueString()
-        self.VOLUME_ID = None
+        volume_name = self.getUniqueString()
 
-        volume = self.user_cloud.block_storage.create_volume(
-            name=self.VOLUME_NAME,
-            size=1)
+        self.volume = self.user_cloud.block_storage.create_volume(
+            name=volume_name,
+            size=1,
+        )
         self.user_cloud.block_storage.wait_for_status(
-            volume,
+            self.volume,
             status='available',
             failures=['error'],
             interval=2,
-            wait=self._wait_for_timeout)
-        assert isinstance(volume, _volume.Volume)
-        self.assertEqual(self.VOLUME_NAME, volume.name)
-        self.VOLUME_ID = volume.id
+            wait=self._wait_for_timeout,
+        )
+        self.assertIsInstance(self.volume, _volume.Volume)
+        self.assertEqual(volume_name, self.volume.name)
 
     def tearDown(self):
-        sot = self.user_cloud.block_storage.delete_volume(
-            self.VOLUME_ID,
-            ignore_missing=False)
-        self.assertIsNone(sot)
-        super(TestVolume, self).tearDown()
+        self.user_cloud.block_storage.delete_volume(self.volume)
+        super().tearDown()
 
-    def test_get(self):
-        sot = self.user_cloud.block_storage.get_volume(self.VOLUME_ID)
-        self.assertEqual(self.VOLUME_NAME, sot.name)
+    def test_volume(self):
+        # get
+        volume = self.user_cloud.block_storage.get_volume(self.volume.id)
+        self.assertEqual(self.volume.name, volume.name)
+
+        # find
+        volume = self.user_cloud.block_storage.find_volume(self.volume.name)
+        self.assertEqual(self.volume.id, volume.id)
+
+        # list
+        volumes = self.user_cloud.block_storage.volumes()
+        # other tests may have created volumes so we don't assert that this is
+        # the *only* volume present
+        self.assertIn(self.volume.id, {v.id for v in volumes})
+
+        # update
+        volume_name = self.getUniqueString()
+        volume_description = self.getUniqueString()
+        volume = self.user_cloud.block_storage.update_volume(
+            self.volume,
+            name=volume_name,
+            description=volume_description,
+        )
+        self.assertIsInstance(volume, _volume.Volume)
+        volume = self.user_cloud.block_storage.get_volume(self.volume.id)
+        self.assertEqual(volume_name, volume.name)
+        self.assertEqual(volume_description, volume.description)
diff --git a/releasenotes/notes/volume-update-876e6540c8471440.yaml b/releasenotes/notes/volume-update-876e6540c8471440.yaml
new file mode 100644
index 000000000..18ac0ed22
--- /dev/null
+++ b/releasenotes/notes/volume-update-876e6540c8471440.yaml
@@ -0,0 +1,4 @@
+---
+features:
+  - |
+    Added ``update_volume`` to the block storage proxy.