From e2a4aa0e1d8b1b5ef4424857586f98501db6b13b Mon Sep 17 00:00:00 2001 From: Antonin Ruan Date: Mon, 2 Feb 2026 17:36:33 +0100 Subject: [PATCH] PCI-32289 Direct download from object storage --- nova/conf/glance.py | 8 ++++++ nova/image/glance.py | 7 ++++- nova/tests/unit/image/test_glance.py | 42 ++++++++++++++++++++++++---- requirements.txt | 2 +- 4 files changed, 51 insertions(+), 8 deletions(-) diff --git a/nova/conf/glance.py b/nova/conf/glance.py index 46f1ed9b4e..8ecf76d5bf 100644 --- a/nova/conf/glance.py +++ b/nova/conf/glance.py @@ -187,7 +187,15 @@ Related options: is set to ``True``. """), + cfg.BoolOpt('direct_download', + default=False, + help=""" +Try to download image through the /v2/images/{image_id}/direct-download API + route. +Image download will fallback to /v2/images/{image_id}/file route + if direct download is either not implemented or fails +"""), cfg.BoolOpt('debug', default=False, help='Enable or disable debug logging with glanceclient.') diff --git a/nova/image/glance.py b/nova/image/glance.py index e7a117d244..c8ea9dd4fe 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -372,7 +372,12 @@ class GlanceImageServiceV2(object): # to fetch the image and fill image_chunks try: image_chunks = self._client.call( - context, 2, 'data', args=(image_id,)) + context, + 2, + 'data', + args=(image_id,), + kwargs={'direct_download': CONF.glance.direct_download}, + ) except Exception: _reraise_translated_image_exception(image_id) diff --git a/nova/tests/unit/image/test_glance.py b/nova/tests/unit/image/test_glance.py index a155bbe1cc..2160dddc8a 100644 --- a/nova/tests/unit/image/test_glance.py +++ b/nova/tests/unit/image/test_glance.py @@ -570,7 +570,12 @@ class TestDownloadNoDirectUri(test.NoDBTestCase): self.assertFalse(show_mock.called) self.assertFalse(open_mock.called) client.call.assert_called_once_with( - ctx, 2, 'data', args=(mock.sentinel.image_id,)) + ctx, + 2, + 'data', + args=(mock.sentinel.image_id,), + kwargs={'direct_download': CONF.glance.direct_download}, + ) self.assertEqual(mock.sentinel.image_chunks, res) @mock.patch('builtins.open') @@ -605,7 +610,12 @@ class TestDownloadNoDirectUri(test.NoDBTestCase): self.assertFalse(show_mock.called) self.assertFalse(open_mock.called) client.call.assert_called_once_with( - ctx, 2, 'data', args=(mock.sentinel.image_id,)) + ctx, + 2, + 'data', + args=(mock.sentinel.image_id,), + kwargs={'direct_download': CONF.glance.direct_download}, + ) self.assertIsNone(res) data.write.assert_has_calls( [ @@ -632,7 +642,12 @@ class TestDownloadNoDirectUri(test.NoDBTestCase): self.assertFalse(show_mock.called) client.call.assert_called_once_with( - ctx, 2, 'data', args=(mock.sentinel.image_id,)) + ctx, + 2, + 'data', + args=(mock.sentinel.image_id,), + kwargs={'direct_download': CONF.glance.direct_download}, + ) open_mock.assert_called_once_with(mock.sentinel.dst_path, 'wb') fsync_mock.assert_called_once_with(writer) self.assertIsNone(res) @@ -663,7 +678,12 @@ class TestDownloadNoDirectUri(test.NoDBTestCase): self.assertFalse(show_mock.called) self.assertFalse(open_mock.called) client.call.assert_called_once_with( - ctx, 2, 'data', args=(mock.sentinel.image_id,)) + ctx, + 2, + 'data', + args=(mock.sentinel.image_id,), + kwargs={'direct_download': CONF.glance.direct_download}, + ) self.assertIsNone(res) data.write.assert_has_calls( [ @@ -828,7 +848,12 @@ class TestDownloadNoDirectUri(test.NoDBTestCase): mock.sentinel.dst_path, mock.sentinel.loc_meta) client.call.assert_called_once_with( - ctx, 2, 'data', args=(mock.sentinel.image_id,)) + ctx, + 2, + 'data', + args=(mock.sentinel.image_id,), + kwargs={'direct_download': CONF.glance.direct_download}, + ) fsync_mock.assert_called_once_with(writer) # NOTE(jaypipes): log messages call open() in part of the # download path, so here, we just check that the last open() @@ -878,7 +903,12 @@ class TestDownloadNoDirectUri(test.NoDBTestCase): include_locations=True) get_tran_mock.assert_called_once_with('funky') client.call.assert_called_once_with( - ctx, 2, 'data', args=(mock.sentinel.image_id,)) + ctx, + 2, + 'data', + args=(mock.sentinel.image_id,), + kwargs={'direct_download': CONF.glance.direct_download}, + ) fsync_mock.assert_called_once_with(writer) # NOTE(jaypipes): log messages call open() in part of the # download path, so here, we just check that the last open() diff --git a/requirements.txt b/requirements.txt index 0b802bf533..c944f0196e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,7 +23,7 @@ jsonschema>=4.0.0 # MIT python-cinderclient>=4.0.1 # Apache-2.0 keystoneauth1>=3.16.0 # Apache-2.0 python-neutronclient>=7.1.0 # Apache-2.0 -python-glanceclient>=4.7.0 # Apache-2.0 +python-glanceclient @ git+https://git.ruan.fr/womax/python-glanceclient.git@gazpacho_temp_url requests>=2.25.1 # Apache-2.0 stevedore>=1.20.0 # Apache-2.0 websockify>=0.9.0 # LGPLv3