Add checksum to image LU identification

The SSP disk driver cleverly avoids uploading a particular glance image
to the SSP more than once by naming it such that it can be recognized
subsequently.  If the image LU thus named already exists in the SSP, we
reuse it (creating a linked clone for use by the instance being
deployed).

Previously, the naming convention for an image LU was
'image_%(sanitized_image_name)s', where the sanitized_image_name was
generated from the first 73 characters of the glance image name (79,
which is the default max filename size, minus the length of 'image_'),
scrubbed to replace any non-alphanumeric/period/underscore characters
with underscores.

This will cause problems whenever an image name is reused while the SSP
still contains the original image LU: the SSP disk driver will generate
the same name, find an image LU of that name in the SSP, and blithely
attach the deploying instance to a linked clone of that image, even
though it's not the same as the new one in glance.

To remedy this problem, with this change set we are now generating the
image LU name as 'image_%(sanitized_image_name)_%(image_checksum)s'.
The image_checksum is a 32-character hash that comes from ImageMeta.
The total length is still 79; so the 'sanitized_image_name' is now
limited to 40 characters.

Now, even when an image name is reused, the LU name will (with extremely
high probability, like reaching into your average star twice and getting
two different milligrams of matter) be different because the image's
checksum will be different; so we'll upload and use the new image
instead of the old one.

Change-Id: I465a90e7105d7d8a7ed1e0b30e96ee98a1d7dfdb
This commit is contained in:
Eric Fried 2016-01-22 11:23:37 -06:00
parent 35bc4447db
commit edf7367ed5
3 changed files with 35 additions and 11 deletions

View File

@ -14,8 +14,9 @@
# under the License.
from nova.compute import task_states
from nova import objects
from nova.objects import flavor
from nova.objects import image_meta
from nova.objects import instance
import os
import sys
@ -46,8 +47,8 @@ TEST_INSTANCE = {
TEST_INST_SPAWNING = dict(TEST_INSTANCE, task_state=task_states.SPAWNING)
TEST_INST1 = objects.Instance(**TEST_INSTANCE)
TEST_INST2 = objects.Instance(**TEST_INST_SPAWNING)
TEST_INST1 = instance.Instance(**TEST_INSTANCE)
TEST_INST2 = instance.Instance(**TEST_INST_SPAWNING)
TEST_MIGRATION = {
'id': 1,
@ -64,11 +65,11 @@ IMAGE1 = {
'name': 'image1',
'size': 300,
'container_format': 'bare',
'disk_format': 'raw'
'disk_format': 'raw',
'checksum': 'b518a8ba2b152b5607aceb5703fac072',
}
TEST_IMAGE1 = objects.ImageMeta.from_dict(IMAGE1)
EMPTY_IMAGE = objects.ImageMeta.from_dict({})
TEST_IMAGE1 = image_meta.ImageMeta.from_dict(IMAGE1)
EMPTY_IMAGE = image_meta.ImageMeta.from_dict({})
# NOTE(mikal): All of this is because if dnspython is present in your
# environment then eventlet monkeypatches socket.getaddrinfo() with an

View File

@ -19,8 +19,10 @@ import fixtures
import mock
import copy
from nova.objects import image_meta
from nova import test
import pypowervm.adapter as pvm_adp
from pypowervm import const
import pypowervm.entities as pvm_ent
from pypowervm.tests import test_fixtures as pvm_fx
from pypowervm.tests.test_utils import pvmhttp
@ -311,7 +313,8 @@ class TestSSPDiskAdapter(test.TestCase):
self.assertIn(vios_uuid, ssp_stor.vios_uuids)
self.assertEqual(ssp_stor._ssp_wrap, ssp1)
# 'image' + '_' + s/-/_/g(image.name), per _get_image_name
self.assertEqual('image_' + powervm.TEST_IMAGE1.name, lu_name)
self.assertEqual('image_' + powervm.TEST_IMAGE1.name + '_' +
powervm.TEST_IMAGE1.checksum, lu_name)
self.assertEqual(powervm.TEST_IMAGE1.size, f_size)
return 'image_lu', None
@ -334,7 +337,8 @@ class TestSSPDiskAdapter(test.TestCase):
mock_crt_lnk_cln):
ssp_stor = self._get_ssp_stor()
# Mock the 'existing' image LU
img_lu = pvm_stg.LU.bld(None, 'image_' + powervm.TEST_IMAGE1.name, 123,
img_lu = pvm_stg.LU.bld(None, 'image_' + powervm.TEST_IMAGE1.name + '_'
+ powervm.TEST_IMAGE1.checksum, 123,
typ=pvm_stg.LUType.IMAGE)
ssp_stor._ssp_wrap.logical_units.append(img_lu)
@ -353,6 +357,24 @@ class TestSSPDiskAdapter(test.TestCase):
None, Instance(), powervm.TEST_IMAGE1, 1)
self.assertEqual('new_lu', lu)
def test_get_image_name(self):
"""Generate image name from ImageMeta."""
ssp = self._get_ssp_stor()
def verify_image_name(name, checksum, expected):
img_meta = image_meta.ImageMeta(name=name, checksum=checksum)
self.assertEqual(expected, ssp._get_image_name(img_meta))
self.assertTrue(len(expected) <= const.MaxLen.FILENAME_DEFAULT)
verify_image_name('foo', 'bar', 'image_foo_bar')
# Ensure a really long name gets truncated properly. Note also '-'
# chars are sanitized.
verify_image_name(
'Template_zw82enbix_PowerVM-CI-18y2385y9123785192364',
'b518a8ba2b152b5607aceb5703fac072',
'image_Template_zw82enbix_PowerVM_CI_18y2385y91'
'_b518a8ba2b152b5607aceb5703fac072')
def test_find_lu(self):
# Bad path, lu not found, None returned
ssp = self._get_ssp_stor()

View File

@ -266,8 +266,9 @@ class DiskAdapter(object):
@staticmethod
def _get_image_name(image_meta):
"""Generate a name for a virtual storage copy of an image."""
return pvm_util.sanitize_file_name_for_api(image_meta.name,
prefix=DiskType.IMAGE + '_')
return pvm_util.sanitize_file_name_for_api(
image_meta.name, prefix=DiskType.IMAGE + '_',
suffix='_' + image_meta.checksum)
@staticmethod
def _disk_gb_to_bytes(size_gb, floor=None):