Merge "Add shared image support"
This commit is contained in:
commit
c525a16b06
@ -33,6 +33,13 @@ the Image service for each one as it is generated.
|
|||||||
Images from Glance used by Ironic must be flagged as ``public``, which
|
Images from Glance used by Ironic must be flagged as ``public``, which
|
||||||
requires administrative privileges with the Glance image service to set.
|
requires administrative privileges with the Glance image service to set.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Starting with the 2025.2 release, the Ironic conductor can access images that
|
||||||
|
are shared with its project, in addition to those it owns.
|
||||||
|
|
||||||
|
To use this feature, ensure the required images are shared with the project
|
||||||
|
associated with the conductor's credentials.
|
||||||
|
|
||||||
- For *whole disk images* just upload the image:
|
- For *whole disk images* just upload the image:
|
||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
@ -22,6 +22,7 @@ from oslo_utils import timeutils
|
|||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
|
|
||||||
from ironic.common import exception
|
from ironic.common import exception
|
||||||
|
from ironic.common import image_service as service
|
||||||
from ironic.common import keystone
|
from ironic.common import keystone
|
||||||
from ironic.conf import CONF
|
from ironic.conf import CONF
|
||||||
|
|
||||||
@ -140,10 +141,13 @@ def is_image_available(context, image):
|
|||||||
image_visibility = getattr(image, 'visibility', None)
|
image_visibility = getattr(image, 'visibility', None)
|
||||||
image_owner = getattr(image, 'owner', None)
|
image_owner = getattr(image, 'owner', None)
|
||||||
image_id = getattr(image, 'id', 'unknown')
|
image_id = getattr(image, 'id', 'unknown')
|
||||||
|
image_shared_member_list = get_image_member_list(image_id, context)
|
||||||
is_admin = 'admin' in getattr(context, 'roles', [])
|
is_admin = 'admin' in getattr(context, 'roles', [])
|
||||||
project = getattr(context, 'project', 'unknown')
|
project = getattr(context, 'project', 'unknown')
|
||||||
|
|
||||||
# If an auth token is present and the config allows access via auth token,
|
# If an auth token is present and the config allows access via auth token,
|
||||||
# allow image access.
|
# allow image access.
|
||||||
|
# NOTE(satoshi): This config should be removed in the H (2026.2) cycle
|
||||||
if CONF.allow_image_access_via_auth_token and auth_token:
|
if CONF.allow_image_access_via_auth_token and auth_token:
|
||||||
# We return true here since we want the *user* request context to
|
# We return true here since we want the *user* request context to
|
||||||
# be able to be used.
|
# be able to be used.
|
||||||
@ -159,7 +163,11 @@ def is_image_available(context, image):
|
|||||||
# allow access.
|
# allow access.
|
||||||
if image_visibility == 'private' and image_owner == conductor_project_id:
|
if image_visibility == 'private' and image_owner == conductor_project_id:
|
||||||
return True
|
return True
|
||||||
|
# If the image is shared and the conductor_project_id is in the shared
|
||||||
|
# member list, allow access
|
||||||
|
if image_visibility == 'shared'\
|
||||||
|
and conductor_project_id in image_shared_member_list:
|
||||||
|
return True
|
||||||
LOG.info(
|
LOG.info(
|
||||||
'Access to %s owned by %s denied to requester %s',
|
'Access to %s owned by %s denied to requester %s',
|
||||||
image_id, image_owner, project
|
image_id, image_owner, project
|
||||||
@ -198,3 +206,17 @@ def get_conductor_project_id():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.debug("Error getting conductor project ID: %s", str(e))
|
LOG.debug("Error getting conductor project ID: %s", str(e))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_image_member_list(image_id, context):
|
||||||
|
try:
|
||||||
|
glance_service = service.GlanceImageService(context=context)
|
||||||
|
members = glance_service.client.image.members(image_id)
|
||||||
|
return [
|
||||||
|
member['member_id']
|
||||||
|
for member in members
|
||||||
|
]
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error("Unable to retrieve image members for image %s: %s",
|
||||||
|
image_id, e)
|
||||||
|
return []
|
||||||
|
@ -1041,8 +1041,24 @@ class TestIsImageAvailable(base.TestCase):
|
|||||||
self.assertTrue(service_utils.is_image_available(
|
self.assertTrue(service_utils.is_image_available(
|
||||||
self.context, self.image))
|
self.context, self.image))
|
||||||
|
|
||||||
|
@mock.patch.object(service_utils, 'get_image_member_list', autospec=True)
|
||||||
|
def test_allow_shared_image_if_member(self, mock_get_members):
|
||||||
|
self.image.visibility = 'shared'
|
||||||
|
self.image.id = 'shared-image-id'
|
||||||
|
self.image.owner = 'some-other-project'
|
||||||
|
|
||||||
|
self.context.project = 'test-project'
|
||||||
|
|
||||||
|
# Mock the conductor project ID and the shared member list
|
||||||
|
conductor_id = service_utils.get_conductor_project_id()
|
||||||
|
mock_get_members.return_value = [conductor_id]
|
||||||
|
|
||||||
|
self.assertTrue(service_utils.is_image_available(
|
||||||
|
self.context, self.image))
|
||||||
|
mock_get_members.assert_called_once_with('shared-image-id',
|
||||||
|
self.context)
|
||||||
|
|
||||||
def test_deny_private_image_different_owner(self):
|
def test_deny_private_image_different_owner(self):
|
||||||
self.config(allow_image_access_via_auth_token=False)
|
|
||||||
self.config(ignore_project_check_for_admin_tasks=False)
|
self.config(ignore_project_check_for_admin_tasks=False)
|
||||||
|
|
||||||
self.image.visibility = 'private'
|
self.image.visibility = 'private'
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
The Ironic conductor can now access images that are
|
||||||
|
shared with its project, in addition to those it owns.
|
||||||
|
|
||||||
|
To use the feature, ensure the images are shared with
|
||||||
|
the project associated with the conductor's credentials.
|
Loading…
x
Reference in New Issue
Block a user