Merge "[S2007220]: Added more image properties to web_image"
This commit is contained in:
commit
4a707e45f5
@ -31,12 +31,12 @@ class GlanceWebImage(resource.Resource):
|
||||
NAME, IMAGE_ID, MIN_DISK, MIN_RAM, PROTECTED,
|
||||
DISK_FORMAT, CONTAINER_FORMAT, LOCATION, TAGS,
|
||||
ARCHITECTURE, KERNEL_ID, OS_DISTRO, OS_VERSION, OWNER,
|
||||
VISIBILITY, RAMDISK_ID
|
||||
VISIBILITY, RAMDISK_ID, ACTIVE, MEMBERS
|
||||
) = (
|
||||
'name', 'id', 'min_disk', 'min_ram', 'protected',
|
||||
'disk_format', 'container_format', 'location', 'tags',
|
||||
'architecture', 'kernel_id', 'os_distro', 'os_version', 'owner',
|
||||
'visibility', 'ramdisk_id'
|
||||
'architecture', 'kernel_id', 'os_distro', 'os_version',
|
||||
'owner', 'visibility', 'ramdisk_id', 'active', 'members'
|
||||
)
|
||||
|
||||
glance_id_pattern = ('^([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}'
|
||||
@ -75,6 +75,7 @@ class GlanceWebImage(resource.Resource):
|
||||
properties.Schema.BOOLEAN,
|
||||
_('Whether the image can be deleted. If the value is True, '
|
||||
'the image is protected and cannot be deleted.'),
|
||||
update_allowed=True,
|
||||
default=False
|
||||
),
|
||||
DISK_FORMAT: properties.Schema(
|
||||
@ -156,6 +157,28 @@ class GlanceWebImage(resource.Resource):
|
||||
constraints=[
|
||||
constraints.AllowedPattern(glance_id_pattern)
|
||||
]
|
||||
),
|
||||
ACTIVE: properties.Schema(
|
||||
properties.Schema.BOOLEAN,
|
||||
_('Activate or deactivate the image. Requires Admin Access.'),
|
||||
default=True,
|
||||
update_allowed=True,
|
||||
support_status=support.SupportStatus(version='16.0.0')
|
||||
),
|
||||
MEMBERS: properties.Schema(
|
||||
properties.Schema.LIST,
|
||||
_('List of additional members that are permitted '
|
||||
'to read the image. This may be a Keystone Project '
|
||||
'IDs or User IDs, depending on the Glance configuration '
|
||||
'in use.'),
|
||||
schema=properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('A member ID. This may be a Keystone Project ID '
|
||||
'or User ID, depending on the Glance configuration '
|
||||
'in use.')
|
||||
),
|
||||
update_allowed=True,
|
||||
support_status=support.SupportStatus(version='16.0.0')
|
||||
)
|
||||
}
|
||||
|
||||
@ -166,42 +189,78 @@ class GlanceWebImage(resource.Resource):
|
||||
def handle_create(self):
|
||||
args = dict((k, v) for k, v in self.properties.items()
|
||||
if v is not None)
|
||||
|
||||
members = args.pop(self.MEMBERS, [])
|
||||
active = args.pop(self.ACTIVE)
|
||||
location = args.pop(self.LOCATION)
|
||||
images = self.client().images
|
||||
image_id = images.create(
|
||||
**args).id
|
||||
image = images.create(**args)
|
||||
image_id = image.id
|
||||
self.resource_id_set(image_id)
|
||||
|
||||
images.image_import(image_id, method='web-download', uri=location)
|
||||
for member in members:
|
||||
self.client().image_members.create(image_id, member)
|
||||
return active
|
||||
|
||||
return image_id
|
||||
|
||||
def check_create_complete(self, image_id):
|
||||
image = self.client().images.get(image_id)
|
||||
return image.status == 'active'
|
||||
def check_create_complete(self, active):
|
||||
image = self.client().images.get(self.resource_id)
|
||||
if image.status == 'killed':
|
||||
raise exception.ResourceInError(
|
||||
resource_status=image.status,
|
||||
)
|
||||
if not active:
|
||||
if image.status == 'active':
|
||||
self.client().images.deactivate(self.resource_id)
|
||||
return True
|
||||
elif image.status == 'deactivated':
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
return image.status == 'active'
|
||||
|
||||
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
|
||||
if prop_diff and self.TAGS in prop_diff:
|
||||
existing_tags = self.properties.get(self.TAGS) or []
|
||||
diff_tags = prop_diff.pop(self.TAGS) or []
|
||||
if prop_diff:
|
||||
active = prop_diff.pop(self.ACTIVE, None)
|
||||
if active is False:
|
||||
self.client().images.deactivate(self.resource_id)
|
||||
|
||||
new_tags = set(diff_tags) - set(existing_tags)
|
||||
for tag in new_tags:
|
||||
self.client().image_tags.update(
|
||||
self.resource_id,
|
||||
tag)
|
||||
if self.TAGS in prop_diff:
|
||||
existing_tags = self.properties.get(self.TAGS) or []
|
||||
diff_tags = prop_diff.pop(self.TAGS) or []
|
||||
|
||||
removed_tags = set(existing_tags) - set(diff_tags)
|
||||
for tag in removed_tags:
|
||||
with self.client_plugin().ignore_not_found:
|
||||
self.client().image_tags.delete(
|
||||
new_tags = set(diff_tags) - set(existing_tags)
|
||||
for tag in new_tags:
|
||||
self.client().image_tags.update(
|
||||
self.resource_id,
|
||||
tag)
|
||||
|
||||
images = self.client().images
|
||||
removed_tags = set(existing_tags) - set(diff_tags)
|
||||
for tag in removed_tags:
|
||||
with self.client_plugin().ignore_not_found:
|
||||
self.client().image_tags.delete(
|
||||
self.resource_id,
|
||||
tag)
|
||||
|
||||
images.update(self.resource_id, **prop_diff)
|
||||
if self.MEMBERS in prop_diff:
|
||||
existing_members = self.properties.get(self.MEMBERS) or []
|
||||
diff_members = prop_diff.pop(self.MEMBERS) or []
|
||||
|
||||
new_members = set(diff_members) - set(existing_members)
|
||||
for _member in new_members:
|
||||
self.glance().image_members.create(
|
||||
self.resource_id, _member)
|
||||
removed_members = set(existing_members) - set(diff_members)
|
||||
for _member in removed_members:
|
||||
self.glance().image_members.delete(
|
||||
self.resource_id, _member)
|
||||
|
||||
self.client().images.update(self.resource_id, **prop_diff)
|
||||
return active
|
||||
|
||||
def check_update_complete(self, active):
|
||||
if active:
|
||||
self.client().images.reactivate(self.resource_id)
|
||||
return True
|
||||
|
||||
def validate(self):
|
||||
super(GlanceWebImage, self).validate()
|
||||
@ -214,6 +273,13 @@ class GlanceWebImage(resource.Resource):
|
||||
"match.")
|
||||
raise exception.StackValidationFailed(message=msg)
|
||||
|
||||
if (self.properties[self.MEMBERS]
|
||||
and self.properties[self.VISIBILITY] != 'shared'):
|
||||
raise exception.ResourcePropertyValueDependency(
|
||||
prop1=self.MEMBERS,
|
||||
prop2=self.VISIBILITY,
|
||||
value='shared')
|
||||
|
||||
def get_live_resource_data(self):
|
||||
image_data = super(GlanceWebImage, self).get_live_resource_data()
|
||||
if image_data.get('status') in ('deleted', 'killed'):
|
||||
|
@ -111,6 +111,7 @@ class GlanceImageTest(common.HeatTestCase):
|
||||
glance.return_value = self.glanceclient
|
||||
self.images = self.glanceclient.images
|
||||
self.image_tags = self.glanceclient.image_tags
|
||||
self.image_members = self.glanceclient.image_members
|
||||
|
||||
def _test_validate(self, resource, error_msg):
|
||||
exc = self.assertRaises(exception.StackValidationFailed,
|
||||
@ -477,6 +478,7 @@ class GlanceWebImageTest(common.HeatTestCase):
|
||||
glance.return_value = self.glanceclient
|
||||
self.images = self.glanceclient.images
|
||||
self.image_tags = self.glanceclient.image_tags
|
||||
self.image_members = self.glanceclient.image_members
|
||||
|
||||
def _test_validate(self, resource, error_msg):
|
||||
exc = self.assertRaises(exception.StackValidationFailed,
|
||||
@ -640,9 +642,49 @@ class GlanceWebImageTest(common.HeatTestCase):
|
||||
name=u'cirros_image',
|
||||
protected=False,
|
||||
owner=u'test_owner',
|
||||
tags=['tag1'],
|
||||
tags=['tag1']
|
||||
)
|
||||
|
||||
def test_image_active_property_image_not_active(self):
|
||||
self.images.reactivate.return_value = None
|
||||
self.images.deactivate.return_value = None
|
||||
value = mock.MagicMock()
|
||||
image_id = '41f0e60c-ebb4-4375-a2b4-845ae8b9c995'
|
||||
value.id = image_id
|
||||
value.status = 'pending'
|
||||
self.images.create.return_value = value
|
||||
self.my_image.handle_create()
|
||||
self.my_image.check_create_complete(image_id)
|
||||
self.images.deactivate.assert_not_called()
|
||||
|
||||
def test_image_active_property_image_active_to_deactivate(self):
|
||||
self.images.reactivate.return_value = None
|
||||
self.images.deactivate.return_value = None
|
||||
value = mock.MagicMock()
|
||||
image_id = '41f0e60c-ebb4-4375-a2b4-845ae8b9c995'
|
||||
value.id = image_id
|
||||
value.status = 'active'
|
||||
self.my_image.resource_id = image_id
|
||||
self.images.create.return_value = value
|
||||
self.images.get.return_value = value
|
||||
self.my_image.check_create_complete(False)
|
||||
self.images.deactivate.assert_called_once_with(
|
||||
self.my_image.resource_id)
|
||||
|
||||
def test_image_active_property_image_status_killed(self):
|
||||
self.images.reactivate.return_value = None
|
||||
self.images.deactivate.return_value = None
|
||||
value = mock.MagicMock()
|
||||
image_id = '41f0e60c-ebb4-4375-a2b4-845ae8b9c995'
|
||||
value.id = image_id
|
||||
value.status = 'killed'
|
||||
self.my_image.resource_id = image_id
|
||||
self.images.create.return_value = value
|
||||
self.images.get.return_value = value
|
||||
ex = self.assertRaises(exception.ResourceInError,
|
||||
self.my_image.check_create_complete, False)
|
||||
self.assertIn('killed', ex.message)
|
||||
|
||||
def _handle_update_tags(self, prop_diff):
|
||||
self.my_image.handle_update(json_snippet=None,
|
||||
tmpl_diff=None,
|
||||
@ -678,6 +720,49 @@ class GlanceWebImageTest(common.HeatTestCase):
|
||||
ramdisk_id='12345678-1234-1234-1234-123456789012'
|
||||
)
|
||||
|
||||
def test_image_handle_update_deactivate(self):
|
||||
self.images.reactivate.return_value = None
|
||||
self.images.deactivate.return_value = None
|
||||
value = mock.MagicMock()
|
||||
image_id = '41f0e60c-ebb4-4375-a2b4-845ae8b9c995'
|
||||
value.id = image_id
|
||||
value.status = 'active'
|
||||
self.my_image.resource_id = image_id
|
||||
props = self.stack.t.t['resources']['my_image']['properties'].copy()
|
||||
props['active'] = False
|
||||
self.my_image.t = self.my_image.t.freeze(properties=props)
|
||||
prop_diff = {'active': False}
|
||||
self.my_image.reparse()
|
||||
self.images.update.return_value = value
|
||||
self.images.get.return_value = value
|
||||
self.my_image.handle_update(json_snippet=None,
|
||||
tmpl_diff=None,
|
||||
prop_diff=prop_diff)
|
||||
self.images.deactivate.assert_called_once_with(
|
||||
self.my_image.resource_id)
|
||||
|
||||
def test_image_handle_update_reactivate(self):
|
||||
self.images.reactivate.return_value = None
|
||||
self.images.deactivate.return_value = None
|
||||
value = mock.MagicMock()
|
||||
image_id = '41f0e60c-ebb4-4375-a2b4-845ae8b9c995'
|
||||
value.id = image_id
|
||||
value.status = 'deactivated'
|
||||
self.my_image.resource_id = image_id
|
||||
props = self.stack.t.t['resources']['my_image']['properties'].copy()
|
||||
props['active'] = True
|
||||
self.my_image.t = self.my_image.t.freeze(properties=props)
|
||||
prop_diff = {'active': True}
|
||||
self.my_image.reparse()
|
||||
self.images.update.return_value = value
|
||||
self.images.get.return_value = value
|
||||
self.my_image.handle_update(json_snippet=None,
|
||||
tmpl_diff=None,
|
||||
prop_diff=prop_diff)
|
||||
self.my_image.check_update_complete(True)
|
||||
self.images.reactivate.assert_called_once_with(
|
||||
self.my_image.resource_id)
|
||||
|
||||
def test_image_handle_update_tags(self):
|
||||
self.my_image.resource_id = '477e8273-60a7-4c41-b683-fdb0bc7cd151'
|
||||
|
||||
@ -707,6 +792,49 @@ class GlanceWebImageTest(common.HeatTestCase):
|
||||
'tag1'
|
||||
)
|
||||
|
||||
def _handle_update_members(self, prop_diff):
|
||||
self.my_image.handle_update(json_snippet=None,
|
||||
tmpl_diff=None,
|
||||
prop_diff=prop_diff)
|
||||
|
||||
self.image_members.create.assert_called_once_with(
|
||||
self.my_image.resource_id,
|
||||
'member2'
|
||||
)
|
||||
self.image_members.delete.assert_called_once_with(
|
||||
self.my_image.resource_id,
|
||||
'member1'
|
||||
)
|
||||
|
||||
def test_image_handle_update_members(self):
|
||||
self.my_image.resource_id = '477e8273-60a7-4c41-b683-fdb0bc7cd151'
|
||||
|
||||
props = self.stack.t.t['resources']['my_image']['properties'].copy()
|
||||
props['members'] = ['member1']
|
||||
self.my_image.t = self.my_image.t.freeze(properties=props)
|
||||
self.my_image.reparse()
|
||||
prop_diff = {'members': ['member2']}
|
||||
|
||||
self._handle_update_members(prop_diff)
|
||||
|
||||
def test_image_handle_update_remove_members(self):
|
||||
self.my_image.resource_id = '477e8273-60a7-4c41-b683-fdb0bc7cd151'
|
||||
|
||||
props = self.stack.t.t['resources']['my_image']['properties'].copy()
|
||||
props['members'] = ['member1']
|
||||
self.my_image.t = self.my_image.t.freeze(properties=props)
|
||||
self.my_image.reparse()
|
||||
prop_diff = {'members': None}
|
||||
|
||||
self.my_image.handle_update(json_snippet=None,
|
||||
tmpl_diff=None,
|
||||
prop_diff=prop_diff)
|
||||
|
||||
self.image_members.delete.assert_called_once_with(
|
||||
self.my_image.resource_id,
|
||||
'member1'
|
||||
)
|
||||
|
||||
def test_image_handle_update_tags_delete_not_found(self):
|
||||
self.my_image.resource_id = '477e8273-60a7-4c41-b683-fdb0bc7cd151'
|
||||
|
||||
@ -750,6 +878,7 @@ class GlanceWebImageTest(common.HeatTestCase):
|
||||
'name': 'test',
|
||||
'disk_format': 'qcow2',
|
||||
'container_format': 'bare',
|
||||
'active': None,
|
||||
'protected': False,
|
||||
'is_public': False,
|
||||
'min_disk': 0,
|
||||
@ -762,6 +891,7 @@ class GlanceWebImageTest(common.HeatTestCase):
|
||||
'os_version': '1.0',
|
||||
'owner': 'test_owner',
|
||||
'ramdisk_id': '12345678-1234-1234-1234-123456789012',
|
||||
'members': None,
|
||||
'visibility': 'private'
|
||||
}
|
||||
image = show_value
|
||||
@ -773,6 +903,7 @@ class GlanceWebImageTest(common.HeatTestCase):
|
||||
'name': 'test',
|
||||
'disk_format': 'qcow2',
|
||||
'container_format': 'bare',
|
||||
'active': None,
|
||||
'protected': False,
|
||||
'min_disk': 0,
|
||||
'min_ram': 0,
|
||||
@ -784,6 +915,7 @@ class GlanceWebImageTest(common.HeatTestCase):
|
||||
'os_version': '1.0',
|
||||
'owner': 'test_owner',
|
||||
'ramdisk_id': '12345678-1234-1234-1234-123456789012',
|
||||
'members': None,
|
||||
'visibility': 'private'
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,10 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
The ``OS::Glance::WebImage`` resource type now supports an
|
||||
``active`` property to allow administrators to deactivate
|
||||
and reactivate the Image. Images remain active by default.
|
||||
- |
|
||||
The ``OS::Glance::WebImage`` resource type now supports a
|
||||
``members`` property for managing a list of other tenants
|
||||
with access to the Image.
|
Loading…
Reference in New Issue
Block a user