Expose "protected" attribute of images.
Adds support for displaying and setting the protected attribute on images. If images are protected, delete actions are not enabled Fixes bug 1168067 Change-Id: Iedf4fa8840c713333679762ac4fc06fd0cf1a322
This commit is contained in:
parent
e4541a6175
commit
4a19755899
@ -29,7 +29,10 @@ class AdminCreateImage(CreateImage):
|
||||
|
||||
class AdminDeleteImage(DeleteImage):
|
||||
def allowed(self, request, image=None):
|
||||
return True
|
||||
if image and image.protected:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
class AdminEditImage(EditImage):
|
||||
|
@ -86,6 +86,7 @@ class CreateImageForm(forms.SelfHandlingForm):
|
||||
' minimum).'),
|
||||
required=False)
|
||||
is_public = forms.BooleanField(label=_("Public"), required=False)
|
||||
protected = forms.BooleanField(label=_("Protected"), required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CreateImageForm, self).__init__(*args, **kwargs)
|
||||
@ -115,6 +116,7 @@ class CreateImageForm(forms.SelfHandlingForm):
|
||||
container_format = 'bare'
|
||||
|
||||
meta = {'is_public': data['is_public'],
|
||||
'protected': data['protected'],
|
||||
'disk_format': data['disk_format'],
|
||||
'container_format': container_format,
|
||||
'min_disk': (data['minimum_disk'] or 0),
|
||||
@ -158,6 +160,7 @@ class UpdateImageForm(forms.SelfHandlingForm):
|
||||
attrs={'readonly': 'readonly'}
|
||||
))
|
||||
public = forms.BooleanField(label=_("Public"), required=False)
|
||||
protected = forms.BooleanField(label=_("Protected"), required=False)
|
||||
|
||||
def handle(self, request, data):
|
||||
image_id = data['image_id']
|
||||
@ -169,6 +172,7 @@ class UpdateImageForm(forms.SelfHandlingForm):
|
||||
container_format = 'bare'
|
||||
|
||||
meta = {'is_public': data['public'],
|
||||
'protected': data['protected'],
|
||||
'disk_format': data['disk_format'],
|
||||
'container_format': container_format,
|
||||
'name': data['name'],
|
||||
|
@ -50,6 +50,9 @@ class DeleteImage(tables.DeleteAction):
|
||||
data_type_plural = _("Images")
|
||||
|
||||
def allowed(self, request, image=None):
|
||||
# Protected images can not be deleted.
|
||||
if image and image.protected:
|
||||
return False
|
||||
if image:
|
||||
return image.owner == request.user.tenant_id
|
||||
# Return True to allow table-level bulk delete action to appear.
|
||||
@ -181,6 +184,10 @@ class ImagesTable(tables.DataTable):
|
||||
verbose_name=_("Public"),
|
||||
empty_value=False,
|
||||
filters=(filters.yesno, filters.capfirst))
|
||||
protected = tables.Column("protected",
|
||||
verbose_name=_("Protected"),
|
||||
empty_value=False,
|
||||
filters=(filters.yesno, filters.capfirst))
|
||||
disk_format = tables.Column(get_format, verbose_name=_("Format"))
|
||||
|
||||
class Meta:
|
||||
@ -190,7 +197,7 @@ class ImagesTable(tables.DataTable):
|
||||
verbose_name = _("Images")
|
||||
# Hide the image_type column. Done this way so subclasses still get
|
||||
# all the columns by default.
|
||||
columns = ["name", "status", "public", "disk_format"]
|
||||
columns = ["name", "status", "public", "protected", "disk_format"]
|
||||
table_actions = (OwnerFilter, CreateImage, DeleteImage,)
|
||||
row_actions = (LaunchImage, EditImage, DeleteImage,)
|
||||
pagination_param = "image_marker"
|
||||
|
@ -85,6 +85,7 @@ class ImageViewTests(test.TestCase):
|
||||
'minimum_disk': 15,
|
||||
'minimum_ram': 512,
|
||||
'is_public': 1,
|
||||
'protected': 0,
|
||||
'method': 'CreateImageForm'}
|
||||
|
||||
api.glance.image_create(IsA(http.HttpRequest),
|
||||
@ -92,6 +93,7 @@ class ImageViewTests(test.TestCase):
|
||||
copy_from=data['copy_from'],
|
||||
disk_format=data['disk_format'],
|
||||
is_public=True,
|
||||
protected=False,
|
||||
min_disk=data['minimum_disk'],
|
||||
min_ram=data['minimum_ram'],
|
||||
name=data['name']). \
|
||||
@ -117,12 +119,14 @@ class ImageViewTests(test.TestCase):
|
||||
'minimum_disk': 15,
|
||||
'minimum_ram': 512,
|
||||
'is_public': 1,
|
||||
'protected': 0,
|
||||
'method': 'CreateImageForm'}
|
||||
|
||||
api.glance.image_create(IsA(http.HttpRequest),
|
||||
container_format="bare",
|
||||
disk_format=data['disk_format'],
|
||||
is_public=True,
|
||||
protected=False,
|
||||
min_disk=data['minimum_disk'],
|
||||
min_ram=data['minimum_ram'],
|
||||
name=data['name'],
|
||||
@ -150,6 +154,22 @@ class ImageViewTests(test.TestCase):
|
||||
self.assertTemplateUsed(res,
|
||||
'project/images_and_snapshots/images/detail.html')
|
||||
self.assertEqual(res.context['image'].name, image.name)
|
||||
self.assertEqual(res.context['image'].protected, image.protected)
|
||||
|
||||
@test.create_stubs({api.glance: ('image_get',)})
|
||||
def test_protected_image_detail_get(self):
|
||||
image = self.images.list()[2]
|
||||
|
||||
api.glance.image_get(IsA(http.HttpRequest), str(image.id)) \
|
||||
.AndReturn(image)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(
|
||||
reverse('horizon:project:images_and_snapshots:images:detail',
|
||||
args=[image.id]))
|
||||
self.assertTemplateUsed(res,
|
||||
'project/images_and_snapshots/images/detail.html')
|
||||
self.assertEqual(res.context['image'].protected, image.protected)
|
||||
|
||||
@test.create_stubs({api.glance: ('image_get',)})
|
||||
def test_image_detail_get_with_exception(self):
|
||||
|
@ -76,7 +76,8 @@ class UpdateView(forms.ModalFormView):
|
||||
'ramdisk': image.properties.get('ramdisk_id', ''),
|
||||
'architecture': image.properties.get('architecture', ''),
|
||||
'disk_format': image.disk_format,
|
||||
'public': image.is_public}
|
||||
'public': image.is_public,
|
||||
'protected': image.protected}
|
||||
|
||||
|
||||
class DetailView(tabs.TabView):
|
||||
|
@ -14,6 +14,8 @@
|
||||
<dd>{{ image.status|default:_("Unknown")|title }}</dd>
|
||||
<dt>{% trans "Public" %}</dt>
|
||||
<dd>{{ image.is_public }}</dd>
|
||||
<dt>{% trans "Protected" %}</dt>
|
||||
<dd>{{ image.protected }}</dd>
|
||||
<dt>{% trans "Checksum" %}</dt>
|
||||
<dd>{{ image.checksum }}</dd>
|
||||
<dt>{% trans "Created" %}</dt>
|
||||
|
@ -61,11 +61,22 @@ class ImagesAndSnapshotsTests(test.TestCase):
|
||||
res = self.client.get(INDEX_URL)
|
||||
self.assertTemplateUsed(res, 'project/images_and_snapshots/index.html')
|
||||
self.assertIn('images_table', res.context)
|
||||
images = res.context['images_table'].data
|
||||
images_table = res.context['images_table']
|
||||
images = images_table.data
|
||||
filter_func = lambda im: im.container_format not in ['aki', 'ari']
|
||||
filtered_images = filter(filter_func, images)
|
||||
self.assertItemsEqual(images, filtered_images)
|
||||
|
||||
self.assertTrue(len(images), 3)
|
||||
row_actions = images_table.get_row_actions(images[0])
|
||||
self.assertTrue(len(row_actions), 3)
|
||||
row_actions = images_table.get_row_actions(images[1])
|
||||
self.assertTrue(len(row_actions), 2)
|
||||
self.assertTrue('delete_image' not in
|
||||
[a.name for a in row_actions])
|
||||
row_actions = images_table.get_row_actions(images[2])
|
||||
self.assertTrue(len(row_actions), 3)
|
||||
|
||||
@test.create_stubs({api.glance: ('image_list_detailed',
|
||||
'snapshot_list_detailed'),
|
||||
api.cinder: ('volume_snapshot_list', 'volume_get')})
|
||||
|
@ -28,21 +28,24 @@ def data(TEST):
|
||||
'status': "active",
|
||||
'owner': TEST.tenant.id,
|
||||
'properties': {'image_type': u'snapshot'},
|
||||
'is_public': False}
|
||||
'is_public': False,
|
||||
'protected': False}
|
||||
snapshot_dict_no_owner = {'name': u'snapshot 2',
|
||||
'container_format': u'ami',
|
||||
'id': 4,
|
||||
'status': "active",
|
||||
'owner': None,
|
||||
'properties': {'image_type': u'snapshot'},
|
||||
'is_public': False}
|
||||
'is_public': False,
|
||||
'protected': False}
|
||||
snapshot_dict_queued = {'name': u'snapshot 2',
|
||||
'container_format': u'ami',
|
||||
'id': 5,
|
||||
'status': "queued",
|
||||
'owner': TEST.tenant.id,
|
||||
'properties': {'image_type': u'snapshot'},
|
||||
'is_public': False}
|
||||
'is_public': False,
|
||||
'protected': False}
|
||||
snapshot = Image(ImageManager(None), snapshot_dict)
|
||||
TEST.snapshots.add(snapshot)
|
||||
snapshot = Image(ImageManager(None), snapshot_dict_no_owner)
|
||||
@ -57,7 +60,8 @@ def data(TEST):
|
||||
'owner': TEST.tenant.id,
|
||||
'container_format': 'novaImage',
|
||||
'properties': {'image_type': u'image'},
|
||||
'is_public': True}
|
||||
'is_public': True,
|
||||
'protected': False}
|
||||
public_image = Image(ImageManager(None), image_dict)
|
||||
|
||||
image_dict = {'id': 'a001c047-22f8-47d0-80a1-8ec94a9524fe',
|
||||
@ -65,16 +69,28 @@ def data(TEST):
|
||||
'status': "active",
|
||||
'owner': TEST.tenant.id,
|
||||
'container_format': 'aki',
|
||||
'is_public': False}
|
||||
'is_public': False,
|
||||
'protected': False}
|
||||
private_image = Image(ImageManager(None), image_dict)
|
||||
|
||||
image_dict = {'id': 'd6936c86-7fec-474a-85c5-5e467b371c3c',
|
||||
'name': 'protected_images',
|
||||
'status': "active",
|
||||
'owner': TEST.tenant.id,
|
||||
'container_format': 'novaImage',
|
||||
'properties': {'image_type': u'image'},
|
||||
'is_public': True,
|
||||
'protected': True}
|
||||
protected_image = Image(ImageManager(None), image_dict)
|
||||
|
||||
image_dict = {'id': '278905a6-4b52-4d1e-98f9-8c57bb25ba32',
|
||||
'name': 'public_image 2',
|
||||
'status': "active",
|
||||
'owner': TEST.tenant.id,
|
||||
'container_format': 'novaImage',
|
||||
'properties': {'image_type': u'image'},
|
||||
'is_public': True}
|
||||
'is_public': True,
|
||||
'protected': False}
|
||||
public_image2 = Image(ImageManager(None), image_dict)
|
||||
|
||||
image_dict = {'id': '710a1acf-a3e3-41dd-a32d-5d6b6c86ea10',
|
||||
@ -82,7 +98,8 @@ def data(TEST):
|
||||
'status': "active",
|
||||
'owner': TEST.tenant.id,
|
||||
'container_format': 'aki',
|
||||
'is_public': False}
|
||||
'is_public': False,
|
||||
'protected': False}
|
||||
private_image2 = Image(ImageManager(None), image_dict)
|
||||
|
||||
image_dict = {'id': '7cd892fd-5652-40f3-a450-547615680132',
|
||||
@ -90,7 +107,8 @@ def data(TEST):
|
||||
'status': "active",
|
||||
'owner': TEST.tenant.id,
|
||||
'container_format': 'aki',
|
||||
'is_public': False}
|
||||
'is_public': False,
|
||||
'protected': False}
|
||||
private_image3 = Image(ImageManager(None), image_dict)
|
||||
|
||||
# A shared image. Not public and not local tenant.
|
||||
@ -99,7 +117,8 @@ def data(TEST):
|
||||
'status': "active",
|
||||
'owner': 'someothertenant',
|
||||
'container_format': 'aki',
|
||||
'is_public': False}
|
||||
'is_public': False,
|
||||
'protected': False}
|
||||
shared_image1 = Image(ImageManager(None), image_dict)
|
||||
|
||||
# "Official" image. Public and tenant matches an entry
|
||||
@ -109,9 +128,10 @@ def data(TEST):
|
||||
'status': "active",
|
||||
'owner': 'officialtenant',
|
||||
'container_format': 'aki',
|
||||
'is_public': True}
|
||||
'is_public': True,
|
||||
'protected': False}
|
||||
official_image1 = Image(ImageManager(None), image_dict)
|
||||
|
||||
TEST.images.add(public_image, private_image,
|
||||
TEST.images.add(public_image, private_image, protected_image,
|
||||
public_image2, private_image2, private_image3,
|
||||
shared_image1, official_image1)
|
||||
|
Loading…
x
Reference in New Issue
Block a user