diff --git a/shade/openstackcloud.py b/shade/openstackcloud.py index 28737975e..332ea5167 100644 --- a/shade/openstackcloud.py +++ b/shade/openstackcloud.py @@ -2546,7 +2546,8 @@ class OpenStackCloud(object): md5=None, sha256=None, disk_format=None, container_format=None, disable_vendor_agent=True, - wait=False, timeout=3600, **kwargs): + wait=False, timeout=3600, + allow_duplicates=False, **kwargs): """Upload an image to Glance. :param str name: Name of the image to create. If it is a pathname @@ -2578,6 +2579,8 @@ class OpenStackCloud(object): true - however, be aware that one of the upload methods is always synchronous. :param timeout: Seconds to wait for image creation. None is forever. + :param allow_duplicates: If true, skips checks that enforce unique + image name. (optional, defaults to False) Additional kwargs will be passed to the image creation as additional metadata for the image. @@ -2603,12 +2606,15 @@ class OpenStackCloud(object): container_format = 'bare' if not md5 or not sha256: (md5, sha256) = self._get_file_hashes(filename) - current_image = self.get_image(name) - if (current_image and current_image.get(IMAGE_MD5_KEY, '') == md5 - and current_image.get(IMAGE_SHA256_KEY, '') == sha256): - self.log.debug( - "image {name} exists and is up to date".format(name=name)) - return current_image + if allow_duplicates: + current_image = None + else: + current_image = self.get_image(name) + if (current_image and current_image.get(IMAGE_MD5_KEY, '') == md5 + and current_image.get(IMAGE_SHA256_KEY, '') == sha256): + self.log.debug( + "image {name} exists and is up to date".format(name=name)) + return current_image kwargs[IMAGE_MD5_KEY] = md5 kwargs[IMAGE_SHA256_KEY] = sha256 kwargs[IMAGE_OBJECT_KEY] = '/'.join([container, name]) diff --git a/shade/tests/functional/test_image.py b/shade/tests/functional/test_image.py index 8a08abbf4..a2f17a2f9 100644 --- a/shade/tests/functional/test_image.py +++ b/shade/tests/functional/test_image.py @@ -69,3 +69,60 @@ class TestImage(base.BaseFunctionalTestCase): self.addCleanup(os.remove, output) self.assertTrue(filecmp.cmp(test_image.name, output), "Downloaded contents don't match created image") + + def test_create_image_skip_duplicate(self): + test_image = tempfile.NamedTemporaryFile(delete=False) + test_image.write('\0' * 1024 * 1024) + test_image.close() + image_name = self.getUniqueString('image') + try: + first_image = self.demo_cloud.create_image( + name=image_name, + filename=test_image.name, + disk_format='raw', + container_format='bare', + min_disk=10, + min_ram=1024, + wait=True) + second_image = self.demo_cloud.create_image( + name=image_name, + filename=test_image.name, + disk_format='raw', + container_format='bare', + min_disk=10, + min_ram=1024, + wait=True) + self.assertEqual(first_image.id, second_image.id) + finally: + self.demo_cloud.delete_image(image_name, wait=True) + + def test_create_image_force_duplicate(self): + test_image = tempfile.NamedTemporaryFile(delete=False) + test_image.write('\0' * 1024 * 1024) + test_image.close() + image_name = self.getUniqueString('image') + second_image = None + try: + first_image = self.demo_cloud.create_image( + name=image_name, + filename=test_image.name, + disk_format='raw', + container_format='bare', + min_disk=10, + min_ram=1024, + wait=True) + second_image = self.demo_cloud.create_image( + name=image_name, + filename=test_image.name, + disk_format='raw', + container_format='bare', + min_disk=10, + min_ram=1024, + allow_duplicates=True, + wait=True) + self.assertNotEqual(first_image.id, second_image.id) + finally: + if first_image: + self.demo_cloud.delete_image(first_image.id, wait=True) + if second_image: + self.demo_cloud.delete_image(second_image.id, wait=True)