hguemar pushed to openstack-glance (f21). "Update to upstream 2014.1.4 (..more)"
notifications at fedoraproject.org
notifications at fedoraproject.org
Thu Mar 26 22:30:04 UTC 2015
>From 3bdbdafa726555e9b2059cac8898c1526c473f22 Mon Sep 17 00:00:00 2001
From: Haikel Guemar <hguemar at fedoraproject.org>
Date: Thu, 26 Mar 2015 23:15:01 +0100
Subject: Update to upstream 2014.1.4
Change-Id: I486c85edf96199c44beac36a097e9264189baaf8
diff --git a/.gitignore b/.gitignore
index 4e74255..f70028c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,4 @@
/glance-2014.1.1.tar.gz
/glance-2014.1.2.tar.gz
/glance-2014.1.3.tar.gz
+/glance-2014.1.4.tar.gz
diff --git a/0001-Remove-runtime-dep-on-python-pbr.patch b/0001-Remove-runtime-dep-on-python-pbr.patch
index 4a8761a..723710c 100644
--- a/0001-Remove-runtime-dep-on-python-pbr.patch
+++ b/0001-Remove-runtime-dep-on-python-pbr.patch
@@ -1,4 +1,4 @@
-From 5c6c96b1d6a60056c44ba7917ed0529cf5667cf3 Mon Sep 17 00:00:00 2001
+From 7a8f58f9aca5241c0608d8407c89333ec3b961a2 Mon Sep 17 00:00:00 2001
From: John Bresnahan <jbresnah at redhat.com>
Date: Wed, 18 Sep 2013 16:43:45 -1000
Subject: [PATCH] Remove runtime dep on python pbr
diff --git a/0002-Don-t-access-the-net-while-building-docs.patch b/0002-Don-t-access-the-net-while-building-docs.patch
index 284446c..4635c0f 100644
--- a/0002-Don-t-access-the-net-while-building-docs.patch
+++ b/0002-Don-t-access-the-net-while-building-docs.patch
@@ -1,4 +1,4 @@
-From 31f45f617f9dcc4992418fce0e494bf0ad9115dd Mon Sep 17 00:00:00 2001
+From eb6f5cd5c34cd59b08dcb33bd306ae6a69133a47 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A1draig=20Brady?= <pbrady at redhat.com>
Date: Fri, 6 Jan 2012 17:12:54 +0000
Subject: [PATCH] Don't access the net while building docs
diff --git a/0003-avoid-unsupported-storage-drivers.patch b/0003-avoid-unsupported-storage-drivers.patch
index abbbf49..d1005fa 100644
--- a/0003-avoid-unsupported-storage-drivers.patch
+++ b/0003-avoid-unsupported-storage-drivers.patch
@@ -1,4 +1,4 @@
-From 2cc03f67361507d8c467f7719a2ba3c1fe6e8345 Mon Sep 17 00:00:00 2001
+From 6c1f50eb5e5e74066b9876b21142775ae9753ce3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A1draig=20Brady?= <P at draigBrady.com>
Date: Wed, 9 Apr 2014 10:31:27 +0100
Subject: [PATCH] avoid unsupported storage drivers
@@ -39,10 +39,10 @@ index 0246b67..04e5623 100644
# ============ Filesystem Store Options ========================
diff --git a/glance/store/__init__.py b/glance/store/__init__.py
-index 273b7c7..c73d3da 100644
+index aa11487..3093dc4 100644
--- a/glance/store/__init__.py
+++ b/glance/store/__init__.py
-@@ -69,10 +69,8 @@ _ALL_STORES = [
+@@ -70,10 +70,8 @@ _ALL_STORES = [
'glance.store.rbd.Store',
'glance.store.s3.Store',
'glance.store.swift.Store',
@@ -52,4 +52,4 @@ index 273b7c7..c73d3da 100644
- 'glance.store.vmware_datastore.Store'
]
-
+ RESTRICTED_URI_SCHEMAS = frozenset(['file', 'filesystem', 'swift+config'])
diff --git a/0004-notify-calling-process-we-are-ready-to-serve.patch b/0004-notify-calling-process-we-are-ready-to-serve.patch
index b058ed3..aa8bbed 100644
--- a/0004-notify-calling-process-we-are-ready-to-serve.patch
+++ b/0004-notify-calling-process-we-are-ready-to-serve.patch
@@ -1,4 +1,4 @@
-From 2a9d2997d3bcd42ce0721ab1736b37df6a0bda53 Mon Sep 17 00:00:00 2001
+From 1416aae53fc8c8e170cf02b3b40335584ec77466 Mon Sep 17 00:00:00 2001
From: Alan Pevec <apevec at redhat.com>
Date: Tue, 11 Feb 2014 22:36:00 +0100
Subject: [PATCH] notify calling process we are ready to serve
diff --git a/0005-Make-rbd-store-s-pool-handling-more-universal.patch b/0005-Make-rbd-store-s-pool-handling-more-universal.patch
deleted file mode 100644
index 6c49551..0000000
--- a/0005-Make-rbd-store-s-pool-handling-more-universal.patch
+++ /dev/null
@@ -1,156 +0,0 @@
-From ec4a8ccef8340939867a14872d98539ae653587e Mon Sep 17 00:00:00 2001
-From: Flavio Percoco <flavio at redhat.com>
-Date: Tue, 23 Sep 2014 17:55:13 +0200
-Subject: [PATCH] Make rbd store's pool handling more universal
-
-Currently we ignore the pool part of the location throughout the rbd store code.
-If there is a pool specified, use that. Otherwise we can still fall back to the
-configured pool.
-
-This is a required change if we want to support ephemeral disk snapshotting later on
-as in that scenario the ephemeral pool might be a different pool from images pool,
-yet we're going to need to reference the snapshot of a disk in the ephemeral pool.
-
-Resolves rhbz: 1148199
-Upstream-Closes-Bug: 1368128
-Upstream-Icehouse: https://review.openstack.org/#/c/125341/
-Upstream-Juno: https://review.openstack.org/#/c/125341/ (glance_store)
-
-Change-Id: Ie415667a809975948c8cfb71ec63a0905995fa67
-(cherry picked from commit 312e93eb165d66821a9f24c66a507e63e5fda9ab)
-Reviewed-on: https://code.engineering.redhat.com/gerrit/35437
-Reviewed-by: Jon Bernard <jobernar at redhat.com>
-Reviewed-by: Flavio Percoco <fpercoco at redhat.com>
-Tested-by: Flavio Percoco <fpercoco at redhat.com>
----
- glance/store/rbd.py | 23 +++++++++++++++--------
- glance/tests/unit/test_rbd_store.py | 12 +++++++-----
- 2 files changed, 22 insertions(+), 13 deletions(-)
-
-diff --git a/glance/store/rbd.py b/glance/store/rbd.py
-index 75f0270..2974a71 100644
---- a/glance/store/rbd.py
-+++ b/glance/store/rbd.py
-@@ -144,9 +144,10 @@ class ImageIterator(object):
- Reads data from an RBD image, one chunk at a time.
- """
-
-- def __init__(self, name, store):
-+ def __init__(self, pool, name, snapshot, store):
-+ self.pool = pool or store.pool
- self.name = name
-- self.pool = store.pool
-+ self.snapshot = snapshot
- self.user = store.user
- self.conf_file = store.conf_file
- self.chunk_size = store.chunk_size
-@@ -156,7 +157,8 @@ class ImageIterator(object):
- with rados.Rados(conffile=self.conf_file,
- rados_id=self.user) as conn:
- with conn.open_ioctx(self.pool) as ioctx:
-- with rbd.Image(ioctx, self.name) as image:
-+ with rbd.Image(ioctx, self.name,
-+ snapshot=self.snapshot) as image:
- img_info = image.stat()
- size = img_info['size']
- bytes_left = size
-@@ -211,7 +213,8 @@ class Store(glance.store.base.Store):
- :raises `glance.exception.NotFound` if image does not exist
- """
- loc = location.store_location
-- return (ImageIterator(loc.image, self), self.get_size(location))
-+ return (ImageIterator(loc.pool, loc.image, loc.snapshot, self),
-+ self.get_size(location))
-
- def get_size(self, location):
- """
-@@ -223,9 +226,12 @@ class Store(glance.store.base.Store):
- :raises `glance.exception.NotFound` if image does not exist
- """
- loc = location.store_location
-+ # if there is a pool specific in the location, use it; otherwise
-+ # we fall back to the default pool specified in the config
-+ target_pool = loc.pool or self.pool
- with rados.Rados(conffile=self.conf_file,
- rados_id=self.user) as conn:
-- with conn.open_ioctx(self.pool) as ioctx:
-+ with conn.open_ioctx(target_pool) as ioctx:
- try:
- with rbd.Image(ioctx, loc.image,
- snapshot=loc.snapshot) as image:
-@@ -260,7 +266,7 @@ class Store(glance.store.base.Store):
- librbd.create(ioctx, image_name, size, order, old_format=True)
- return StoreLocation({'image': image_name})
-
-- def _delete_image(self, image_name, snapshot_name=None):
-+ def _delete_image(self, target_pool, image_name, snapshot_name=None):
- """
- Delete RBD image and snapshot.
-
-@@ -271,7 +277,7 @@ class Store(glance.store.base.Store):
- InUseByStore if image is in use or snapshot unprotect failed
- """
- with rados.Rados(conffile=self.conf_file, rados_id=self.user) as conn:
-- with conn.open_ioctx(self.pool) as ioctx:
-+ with conn.open_ioctx(target_pool) as ioctx:
- try:
- # First remove snapshot.
- if snapshot_name is not None:
-@@ -387,4 +393,5 @@ class Store(glance.store.base.Store):
- InUseByStore if image is in use or snapshot unprotect failed
- """
- loc = location.store_location
-- self._delete_image(loc.image, loc.snapshot)
-+ target_pool = loc.pool or self.pool
-+ self._delete_image(target_pool, loc.image, loc.snapshot)
-diff --git a/glance/tests/unit/test_rbd_store.py b/glance/tests/unit/test_rbd_store.py
-index f21cba5..828fee9 100644
---- a/glance/tests/unit/test_rbd_store.py
-+++ b/glance/tests/unit/test_rbd_store.py
-@@ -37,7 +37,8 @@ class TestStore(base.StoreClearingUnitTest):
- self.store.chunk_size = 2
- self.called_commands_actual = []
- self.called_commands_expected = []
-- self.store_specs = {'image': 'fake_image',
-+ self.store_specs = {'pool': 'fake_pool',
-+ 'image': 'fake_image',
- 'snapshot': 'fake_snapshot'}
- self.location = StoreLocation(self.store_specs)
- # Provide enough data to get more than one chunk iteration.
-@@ -99,7 +100,7 @@ class TestStore(base.StoreClearingUnitTest):
- self.called_commands_actual.append('remove')
-
- self.stubs.Set(mock_rbd.RBD, 'remove', _fake_remove)
-- self.store._delete_image(self.location)
-+ self.store._delete_image('fake_pool', self.location)
- self.called_commands_expected = ['remove']
-
- def test__delete_image_w_snap(self):
-@@ -115,7 +116,8 @@ class TestStore(base.StoreClearingUnitTest):
- self.stubs.Set(mock_rbd.RBD, 'remove', _fake_remove)
- self.stubs.Set(mock_rbd.Image, 'unprotect_snap', _fake_unprotect_snap)
- self.stubs.Set(mock_rbd.Image, 'remove_snap', _fake_remove_snap)
-- self.store._delete_image(self.location, snapshot_name='snap')
-+ self.store._delete_image('fake_pool', self.location,
-+ snapshot_name='snap')
-
- self.called_commands_expected = ['unprotect_snap', 'remove_snap',
- 'remove']
-@@ -127,7 +129,7 @@ class TestStore(base.StoreClearingUnitTest):
-
- self.stubs.Set(mock_rbd.Image, 'unprotect_snap', _fake_unprotect_snap)
- self.assertRaises(exception.NotFound, self.store._delete_image,
-- self.location, snapshot_name='snap')
-+ 'fake_pool', self.location, snapshot_name='snap')
-
- self.called_commands_expected = ['unprotect_snap']
-
-@@ -138,7 +140,7 @@ class TestStore(base.StoreClearingUnitTest):
-
- self.stubs.Set(mock_rbd.RBD, 'remove', _fake_remove)
- self.assertRaises(exception.NotFound, self.store._delete_image,
-- self.location, snapshot_name='snap')
-+ 'fake_pool', self.location, snapshot_name='snap')
-
- self.called_commands_expected = ['remove']
-
diff --git a/0006-To-prevent-client-use-v2-patch-api-to-handle-file-an.patch b/0006-To-prevent-client-use-v2-patch-api-to-handle-file-an.patch
deleted file mode 100644
index 1ee6607..0000000
--- a/0006-To-prevent-client-use-v2-patch-api-to-handle-file-an.patch
+++ /dev/null
@@ -1,668 +0,0 @@
-From 4c60b6f023e0f6cd1f59760e046885dad71c8518 Mon Sep 17 00:00:00 2001
-From: Zhi Yan Liu <zhiyanl at cn.ibm.com>
-Date: Mon, 15 Dec 2014 12:29:55 +0800
-Subject: [PATCH] To prevent client use v2 patch api to handle file and swift
- location
-
-The change will be used to restrict client to download and delete any
-file in glance-api server. The same resone and logic as what we did in
-v1:
-https://github.com/openstack/glance/blob/master/glance/api/v1/images.py#L429
-
-Closes-Bug: bug/1400966
-DocImpact
-
-Note: Even this change could fully resolve the problem for Glance, but
-we still need to fix this issue from glance_store perspective
-separatelly due to other projects can use the lib directly.
-
-Conflicts:
- glance/api/v1/images.py
- glance/common/store_utils.py
- glance/location.py
- glance/tests/functional/v1/test_copy_to_file.py
- glance/tests/functional/v2/test_images.py
- glance/tests/unit/test_store_image.py
- glance/tests/unit/test_store_location.py
- glance/tests/unit/utils.py
- glance/tests/unit/v1/test_api.py
-
-(cherry picked from commit 4afdb017aa1ccef01482f117cb8d0736a6da38ed)
-Signed-off-by: Zhi Yan Liu <zhiyanl at cn.ibm.com>
-
-Change-Id: Id04c05b997ff93f302bafc7af8ebb27a54a84f91
-Resolves: rhbz #1174483
-Resolves: rhbz #1174484
-Upstream-Closes-Bug: #1400966
-Upstream-Kilo: https://review.openstack.org/#/c/141706/
-Upstream-Juno: https://review.openstack.org/#/c/142373/
-Upstream-Icehouse: https://review.openstack.org/#/c/142703/
-Upstream-change-Id: I72dbead3cb2dcb87f52658ddb880e26880cc229b
-Reviewed-on: https://code.engineering.redhat.com/gerrit/38836
-Reviewed-by: Eoghan Glynn <eglynn at redhat.com>
-Tested-by: Eoghan Glynn <eglynn at redhat.com>
----
- glance/api/v1/images.py | 27 ++--
- glance/store/__init__.py | 30 ++++-
- glance/tests/functional/v1/test_copy_to_file.py | 30 ++++-
- glance/tests/functional/v2/test_images.py | 169 +++++++++++-------------
- glance/tests/unit/test_store_image.py | 3 +-
- glance/tests/unit/test_store_location.py | 30 ++++-
- glance/tests/unit/utils.py | 9 +-
- glance/tests/unit/v1/test_api.py | 77 ++++++++++-
- 8 files changed, 252 insertions(+), 123 deletions(-)
-
-diff --git a/glance/api/v1/images.py b/glance/api/v1/images.py
-index dd6ec06..2f8afa0 100644
---- a/glance/api/v1/images.py
-+++ b/glance/api/v1/images.py
-@@ -48,6 +48,7 @@ from glance.store import get_known_schemes
- from glance.store import get_size_from_backend
- from glance.store import get_store_from_location
- from glance.store import get_store_from_scheme
-+from glance.store import validate_external_location
-
- LOG = logging.getLogger(__name__)
- SUPPORTED_PARAMS = glance.api.v1.SUPPORTED_PARAMS
-@@ -404,23 +405,19 @@ class Controller(controller.BaseController):
- @staticmethod
- def _validate_source(source, req):
- """
-- External sources (as specified via the location or copy-from headers)
-- are supported only over non-local store types, i.e. S3, Swift, HTTP.
-- Note the absence of file:// for security reasons, see LP bug #942118.
-- If the above constraint is violated, we reject with 400 "Bad Request".
-+ To validate if external sources (as specified via the location
-+ or copy-from headers) are supported. Otherwise we reject
-+ with 400 "Bad Request".
- """
- if source:
-- pieces = urlparse.urlparse(source)
-- schemes = [scheme for scheme in get_known_schemes()
-- if scheme != 'file']
-- for scheme in schemes:
-- if pieces.scheme == scheme:
-- return source
-- msg = _("External sourcing not supported for store %s") % source
-- LOG.debug(msg)
-- raise HTTPBadRequest(explanation=msg,
-- request=req,
-- content_type="text/plain")
-+ if validate_external_location(source):
-+ return source
-+ else:
-+ msg = _("External source are not supported: '%s'") % source
-+ LOG.debug(msg)
-+ raise HTTPBadRequest(explanation=msg,
-+ request=req,
-+ content_type="text/plain")
-
- @staticmethod
- def _copy_from(req):
-diff --git a/glance/store/__init__.py b/glance/store/__init__.py
-index c73d3da..2ae8caa 100644
---- a/glance/store/__init__.py
-+++ b/glance/store/__init__.py
-@@ -19,6 +19,7 @@ import sys
-
- from oslo.config import cfg
- import six
-+import six.moves.urllib.parse as urlparse
-
- from glance.common import exception
- from glance.common import utils
-@@ -419,6 +420,24 @@ def set_acls(context, location_uri, public=False, read_tenants=[],
- LOG.debug(_("Skipping store.set_acls... not implemented."))
-
-
-+def validate_external_location(uri):
-+ """
-+ Validate if URI of external location are supported.
-+
-+ Only over non-local store types are OK, i.e. S3, Swift,
-+ HTTP. Note the absence of 'file://' for security reasons,
-+ see LP bug #942118, 1400966, 'swift+config://' is also
-+ absent for security reasons, see LP bug #1334196.
-+
-+ :param uri: The URI of external image location.
-+ :return: Whether given URI of external image location are OK.
-+ """
-+ pieces = urlparse.urlparse(uri)
-+ valid_schemes = [scheme for scheme in get_known_schemes()
-+ if scheme != 'file' and scheme != 'swift+config']
-+ return pieces.scheme in valid_schemes
-+
-+
- class ImageRepoProxy(glance.domain.proxy.Repo):
-
- def __init__(self, image_repo, context, store_api):
-@@ -451,22 +470,23 @@ class ImageRepoProxy(glance.domain.proxy.Repo):
-
-
- def _check_location_uri(context, store_api, uri):
-- """
-- Check if an image location uri is valid.
-+ """Check if an image location is valid.
-
- :param context: Glance request context
- :param store_api: store API module
- :param uri: location's uri string
- """
-+
- is_ok = True
- try:
-- size = store_api.get_size_from_backend(context, uri)
- # NOTE(zhiyan): Some stores return zero when it catch exception
-- is_ok = size > 0
-+ is_ok = (store_api.validate_external_location(uri) and
-+ store_api.get_size_from_backend(uri, context=context) > 0)
- except (exception.UnknownScheme, exception.NotFound):
- is_ok = False
- if not is_ok:
-- raise exception.BadStoreUri(_('Invalid location: %s') % uri)
-+ reason = _('Invalid location')
-+ raise exception.BadStoreUri(message=reason)
-
-
- def _check_image_location(context, store_api, location):
-diff --git a/glance/tests/functional/v1/test_copy_to_file.py b/glance/tests/functional/v1/test_copy_to_file.py
-index ae2c320..2c5d833 100644
---- a/glance/tests/functional/v1/test_copy_to_file.py
-+++ b/glance/tests/functional/v1/test_copy_to_file.py
-@@ -248,9 +248,35 @@ class TestCopyToFile(functional.FunctionalTest):
- path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
- http = httplib2.Http()
- response, content = http.request(path, 'POST', headers=headers)
-- self.assertEqual(response.status, 400, content)
-+ self.assertEqual(400, response.status, content)
-
-- expected = 'External sourcing not supported for store ' + copy_from
-+ expected = 'External source are not supported: \'%s\'' % copy_from
-+ msg = 'expected "%s" in "%s"' % (expected, content)
-+ self.assertTrue(expected in content, msg)
-+
-+ self.stop_servers()
-+
-+ @skip_if_disabled
-+ def test_copy_from_swift_config(self):
-+ """
-+ Ensure we can't copy from swift+config
-+ """
-+ self.cleanup()
-+
-+ self.start_servers(**self.__dict__.copy())
-+
-+ # POST /images with public image copied from file (to file)
-+ headers = {'X-Image-Meta-Name': 'copied',
-+ 'X-Image-Meta-disk_format': 'raw',
-+ 'X-Image-Meta-container_format': 'ovf',
-+ 'X-Image-Meta-Is-Public': 'True',
-+ 'X-Glance-API-Copy-From': 'swift+config://xxx'}
-+ path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
-+ http = httplib2.Http()
-+ response, content = http.request(path, 'POST', headers=headers)
-+ self.assertEqual(400, response.status, content)
-+
-+ expected = 'External source are not supported: \'swift+config://xxx\''
- msg = 'expected "%s" in "%s"' % (expected, content)
- self.assertTrue(expected in content, msg)
-
-diff --git a/glance/tests/functional/v2/test_images.py b/glance/tests/functional/v2/test_images.py
-index 4247434..1bb0e56 100644
---- a/glance/tests/functional/v2/test_images.py
-+++ b/glance/tests/functional/v2/test_images.py
-@@ -15,7 +15,6 @@
-
- import os
- import signal
--import tempfile
- import uuid
-
- import requests
-@@ -38,6 +37,19 @@ class TestImages(functional.FunctionalTest):
- self.cleanup()
- self.api_server.deployment_flavor = 'noauth'
- self.start_servers(**self.__dict__.copy())
-+ for i in range(3):
-+ ret = test_http.http_server("foo_image_id%d" % i,
-+ "foo_image%d" % i)
-+ setattr(self, 'http_server%d_pid' % i, ret[0])
-+ setattr(self, 'http_port%d' % i, ret[1])
-+
-+ def tearDown(self):
-+ for i in range(3):
-+ pid = getattr(self, 'http_server%d_pid' % i, None)
-+ if pid:
-+ os.kill(pid, signal.SIGKILL)
-+
-+ super(TestImages, self).tearDown()
-
- def _url(self, path):
- return 'http://127.0.0.1:%d%s' % (self.api_port, path)
-@@ -282,21 +294,15 @@ class TestImages(functional.FunctionalTest):
- self.assertEqual(413, response.status_code, response.text)
-
- # Adding 3 image locations should fail since configured limit is 2
-- for i in range(3):
-- file_path = os.path.join(self.test_dir, 'fake_image_%i' % i)
-- with open(file_path, 'w') as fap:
-- fap.write('glance')
--
- path = self._url('/v2/images/%s' % image_id)
- media_type = 'application/openstack-images-v2.1-json-patch'
- headers = self._headers({'content-type': media_type})
- changes = []
- for i in range(3):
-+ url = ('http://127.0.0.1:%s/foo_image' %
-+ getattr(self, 'http_port%d' % i))
- changes.append({'op': 'add', 'path': '/locations/-',
-- 'value': {'url': 'file://{0}'.format(
-- os.path.join(self.test_dir,
-- 'fake_image_%i' % i)),
-- 'metadata': {}},
-+ 'value': {'url': url, 'metadata': {}},
- })
-
- data = jsonutils.dumps(changes)
-@@ -1811,17 +1817,14 @@ class TestImages(functional.FunctionalTest):
- self.assertNotIn('size', image)
- self.assertNotIn('virtual_size', image)
-
-- file_path = os.path.join(self.test_dir, 'fake_image')
-- with open(file_path, 'w') as fap:
-- fap.write('glance')
--
- # Update locations for the queued image
- path = self._url('/v2/images/%s' % image_id)
- media_type = 'application/openstack-images-v2.1-json-patch'
- headers = self._headers({'content-type': media_type})
-+ url = 'http://127.0.0.1:%s/foo_image' % self.http_port0
- data = jsonutils.dumps([{'op': 'replace', 'path': '/locations',
-- 'value': [{'url': 'file://' + file_path,
-- 'metadata': {}}]}])
-+ 'value': [{'url': url, 'metadata': {}}]
-+ }])
- response = requests.patch(path, headers=headers, data=data)
- self.assertEqual(200, response.status_code, response.text)
-
-@@ -1830,7 +1833,51 @@ class TestImages(functional.FunctionalTest):
- response = requests.get(path, headers=headers)
- self.assertEqual(200, response.status_code)
- image = jsonutils.loads(response.text)
-- self.assertEqual(image['size'], 6)
-+ self.assertEqual(10, image['size'])
-+
-+ def test_update_locations_with_restricted_sources(self):
-+ self.start_servers(**self.__dict__.copy())
-+ # Create an image
-+ path = self._url('/v2/images')
-+ headers = self._headers({'content-type': 'application/json'})
-+ data = jsonutils.dumps({'name': 'image-1', 'disk_format': 'aki',
-+ 'container_format': 'aki'})
-+ response = requests.post(path, headers=headers, data=data)
-+ self.assertEqual(201, response.status_code)
-+
-+ # Returned image entity should have a generated id and status
-+ image = jsonutils.loads(response.text)
-+ image_id = image['id']
-+ self.assertEqual('queued', image['status'])
-+ self.assertIsNone(image['size'])
-+ self.assertIsNone(image['virtual_size'])
-+
-+ # Update locations for the queued image
-+ path = self._url('/v2/images/%s' % image_id)
-+ media_type = 'application/openstack-images-v2.1-json-patch'
-+ headers = self._headers({'content-type': media_type})
-+ data = jsonutils.dumps([{'op': 'replace', 'path': '/locations',
-+ 'value': [{'url': 'file:///foo_image',
-+ 'metadata': {}}]
-+ }])
-+ response = requests.patch(path, headers=headers, data=data)
-+ self.assertEqual(400, response.status_code, response.text)
-+
-+ data = jsonutils.dumps([{'op': 'replace', 'path': '/locations',
-+ 'value': [{'url': 'swift+config:///foo_image',
-+ 'metadata': {}}]
-+ }])
-+ response = requests.patch(path, headers=headers, data=data)
-+ self.assertEqual(400, response.status_code, response.text)
-+
-+
-+class TestImagesWithRegistry(TestImages):
-+ def setUp(self):
-+ super(TestImagesWithRegistry, self).setUp()
-+ self.api_server.data_api = (
-+ 'glance.tests.functional.v2.registry_data_api')
-+ self.registry_server.deployment_flavor = 'trusted-auth'
-+>>>>>>> 4afdb01... To prevent client use v2 patch api to handle file and swift location
-
-
- class TestImageDirectURLVisibility(functional.FunctionalTest):
-@@ -2040,16 +2087,17 @@ class TestImageLocationSelectionStrategy(functional.FunctionalTest):
- super(TestImageLocationSelectionStrategy, self).setUp()
- self.cleanup()
- self.api_server.deployment_flavor = 'noauth'
-- self.foo_image_file = tempfile.NamedTemporaryFile()
-- self.foo_image_file.write("foo image file")
-- self.foo_image_file.flush()
-- self.addCleanup(self.foo_image_file.close)
-- ret = test_http.http_server("foo_image_id", "foo_image")
-- self.http_server_pid, self.http_port = ret
-+ for i in range(3):
-+ ret = test_http.http_server("foo_image_id%d" % i,
-+ "foo_image%d" % i)
-+ setattr(self, 'http_server%d_pid' % i, ret[0])
-+ setattr(self, 'http_port%d' % i, ret[1])
-
- def tearDown(self):
-- if self.http_server_pid is not None:
-- os.kill(self.http_server_pid, signal.SIGKILL)
-+ for i in range(3):
-+ pid = getattr(self, 'http_server%d_pid' % i, None)
-+ if pid:
-+ os.kill(pid, signal.SIGKILL)
-
- super(TestImageLocationSelectionStrategy, self).tearDown()
-
-@@ -2098,73 +2146,14 @@ class TestImageLocationSelectionStrategy(functional.FunctionalTest):
- self.assertTrue('locations' in image)
- self.assertTrue(image["locations"] == [])
-
-- # Update image locations via PATCH
-- path = self._url('/v2/images/%s' % image_id)
-- media_type = 'application/openstack-images-v2.1-json-patch'
-- headers = self._headers({'content-type': media_type})
-- values = [{'url': 'file://%s' % self.foo_image_file.name,
-- 'metadata': {'idx': '1'}},
-- {'url': 'http://127.0.0.1:%s/foo_image' % self.http_port,
-- 'metadata': {'idx': '0'}}]
-- doc = [{'op': 'replace',
-- 'path': '/locations',
-- 'value': values}]
-- data = jsonutils.dumps(doc)
-- response = requests.patch(path, headers=headers, data=data)
-- self.assertEqual(200, response.status_code)
--
-- # Image locations should be visible
-- path = self._url('/v2/images/%s' % image_id)
-- headers = self._headers({'Content-Type': 'application/json'})
-- response = requests.get(path, headers=headers)
-- self.assertEqual(200, response.status_code)
-- image = jsonutils.loads(response.text)
-- self.assertTrue('locations' in image)
-- self.assertEqual(image['locations'], values)
-- self.assertTrue('direct_url' in image)
-- self.assertEqual(image['direct_url'], values[0]['url'])
--
-- self.stop_servers()
--
-- def test_image_locatons_with_store_type_strategy(self):
-- self.api_server.show_image_direct_url = True
-- self.api_server.show_multiple_locations = True
-- self.image_location_quota = 10
-- self.api_server.location_strategy = 'store_type'
-- preference = "http, swift, filesystem"
-- self.api_server.store_type_location_strategy_preference = preference
-- self.start_servers(**self.__dict__.copy())
--
-- # Create an image
-- path = self._url('/v2/images')
-- headers = self._headers({'content-type': 'application/json'})
-- data = jsonutils.dumps({'name': 'image-1', 'type': 'kernel',
-- 'foo': 'bar', 'disk_format': 'aki',
-- 'container_format': 'aki'})
-- response = requests.post(path, headers=headers, data=data)
-- self.assertEqual(201, response.status_code)
--
-- # Get the image id
-- image = jsonutils.loads(response.text)
-- image_id = image['id']
--
-- # Image locations should not be visible before location is set
-- path = self._url('/v2/images/%s' % image_id)
-- headers = self._headers({'Content-Type': 'application/json'})
-- response = requests.get(path, headers=headers)
-- self.assertEqual(200, response.status_code)
-- image = jsonutils.loads(response.text)
-- self.assertTrue('locations' in image)
-- self.assertTrue(image["locations"] == [])
--
-- # Update image locations via PATCH
-+ # Update image locations via PATCH
- path = self._url('/v2/images/%s' % image_id)
- media_type = 'application/openstack-images-v2.1-json-patch'
- headers = self._headers({'content-type': media_type})
-- values = [{'url': 'file://%s' % self.foo_image_file.name,
-- 'metadata': {'idx': '1'}},
-- {'url': 'http://127.0.0.1:%s/foo_image' % self.http_port,
-- 'metadata': {'idx': '0'}}]
-+ values = [{'url': 'http://127.0.0.1:%s/foo_image' % self.http_port0,
-+ 'metadata': {}},
-+ {'url': 'http://127.0.0.1:%s/foo_image' % self.http_port1,
-+ 'metadata': {}}]
- doc = [{'op': 'replace',
- 'path': '/locations',
- 'value': values}]
-@@ -2172,8 +2161,6 @@ class TestImageLocationSelectionStrategy(functional.FunctionalTest):
- response = requests.patch(path, headers=headers, data=data)
- self.assertEqual(200, response.status_code)
-
-- values.sort(key=lambda loc: int(loc['metadata']['idx']))
--
- # Image locations should be visible
- path = self._url('/v2/images/%s' % image_id)
- headers = self._headers({'Content-Type': 'application/json'})
-diff --git a/glance/tests/unit/test_store_image.py b/glance/tests/unit/test_store_image.py
-index 424915b..9d6cc4e 100644
---- a/glance/tests/unit/test_store_image.py
-+++ b/glance/tests/unit/test_store_image.py
-@@ -16,6 +16,7 @@ import mox
-
- from glance.common import exception
- import glance.store
-+from glance.tests.unit import base as unit_test_base
- from glance.tests.unit import utils as unit_test_utils
- from glance.tests import utils
-
-@@ -731,7 +732,7 @@ class TestStoreImageRepo(utils.BaseTestCase):
- self.assertEqual(acls['read'], [TENANT2])
-
-
--class TestImageFactory(utils.BaseTestCase):
-+class TestImageFactory(unit_test_base.StoreClearingUnitTest):
-
- def setUp(self):
- super(TestImageFactory, self).setUp()
-diff --git a/glance/tests/unit/test_store_location.py b/glance/tests/unit/test_store_location.py
-index df8d5d7..a19a33a 100644
---- a/glance/tests/unit/test_store_location.py
-+++ b/glance/tests/unit/test_store_location.py
-@@ -488,11 +488,12 @@ class TestStoreLocation(base.StoreClearingUnitTest):
- ctx,
- store)
-
-+ class FakeImageProxy():
-+ size = None
-+ context = None
-+ store_api = mock.Mock()
-+
- def test_add_location_for_image_without_size(self):
-- class FakeImageProxy():
-- size = None
-- context = None
-- store_api = mock.Mock()
-
- def fake_get_size_from_backend(context, uri):
- return 1
-@@ -504,14 +505,31 @@ class TestStoreLocation(base.StoreClearingUnitTest):
- loc2 = {'url': 'file:///fake2.img.tar.gz', 'metadata': {}}
-
- # Test for insert location
-- image1 = FakeImageProxy()
-+ image1 = TestStoreLocation.FakeImageProxy()
- locations = glance.store.StoreLocations(image1, [])
- locations.insert(0, loc2)
- self.assertEqual(image1.size, 1)
-
- # Test for set_attr of _locations_proxy
-- image2 = FakeImageProxy()
-+ image2 = TestStoreLocation.FakeImageProxy()
- locations = glance.store.StoreLocations(image2, [loc1])
- locations[0] = loc2
- self.assertTrue(loc2 in locations)
- self.assertEqual(image2.size, 1)
-+
-+ def test_add_location_with_restricted_sources(self):
-+
-+ loc1 = {'url': 'file:///fake1.img.tar.gz', 'metadata': {}}
-+ loc2 = {'url': 'swift+config:///xxx', 'metadata': {}}
-+
-+ # Test for insert location
-+ image1 = TestStoreLocation.FakeImageProxy()
-+ locations = glance.store.StoreLocations(image1, [])
-+ self.assertRaises(exception.BadStoreUri, locations.insert, 0, loc1)
-+ self.assertNotIn(loc1, locations)
-+
-+ # Test for set_attr of _locations_proxy
-+ image2 = TestStoreLocation.FakeImageProxy()
-+ locations = glance.store.StoreLocations(image2, [loc1])
-+ self.assertRaises(exception.BadStoreUri, locations.insert, 0, loc2)
-+ self.assertNotIn(loc2, locations)
-diff --git a/glance/tests/unit/utils.py b/glance/tests/unit/utils.py
-index 1c4e16a..e0dfc43 100644
---- a/glance/tests/unit/utils.py
-+++ b/glance/tests/unit/utils.py
-@@ -14,9 +14,9 @@
- # under the License.
-
- import urllib
--import urlparse
-
- from oslo.config import cfg
-+import six.moves.urllib.parse as urlparse
-
- from glance.common import exception
- from glance.common import wsgi
-@@ -113,7 +113,6 @@ class FakeDB(object):
- def __getattr__(self, key):
- return getattr(simple_db, key)
-
--
- class FakeStoreAPI(object):
- def __init__(self, store_metadata=None):
- self.data = {
-@@ -188,6 +187,12 @@ class FakeStoreAPI(object):
- def check_location_metadata(self, val, key=''):
- glance.store.check_location_metadata(val)
-
-+ def validate_external_location(self, uri):
-+ if uri and urlparse.urlparse(uri).scheme:
-+ return glance.store.validate_external_location(uri)
-+ else:
-+ return True
-+
-
- class FakePolicyEnforcer(object):
- def __init__(self, *_args, **kwargs):
-diff --git a/glance/tests/unit/v1/test_api.py b/glance/tests/unit/v1/test_api.py
-index 5618cb0..9856487 100644
---- a/glance/tests/unit/v1/test_api.py
-+++ b/glance/tests/unit/v1/test_api.py
-@@ -379,7 +379,7 @@ class TestGlanceAPI(base.IsolatedUnitTest):
-
- res = req.get_response(self.api)
- self.assertEqual(res.status_int, 400)
-- self.assertTrue('External sourcing not supported' in res.body)
-+ self.assertIn('External source are not supported', res.body)
-
- def test_create_with_location_bad_store_uri(self):
- fixture_headers = {
-@@ -962,6 +962,53 @@ class TestGlanceAPI(base.IsolatedUnitTest):
- res = req.get_response(self.api)
- self.assertEqual(res.status_int, 409)
-
-+ def test_add_location_with_invalid_location_on_conflict_image_size(self):
-+ """Tests creates an image from location and conflict image size"""
-+ fixture_headers = {'x-image-meta-store': 'file',
-+ 'x-image-meta-disk-format': 'vhd',
-+ 'x-image-meta-location': 'http://a/b/c.tar.gz',
-+ 'x-image-meta-container-format': 'ovf',
-+ 'x-image-meta-name': 'fake image #F',
-+ 'x-image-meta-size': '1'}
-+
-+ req = webob.Request.blank("/images")
-+ req.headers['Content-Type'] = 'application/octet-stream'
-+ req.method = 'POST'
-+ for k, v in fixture_headers.iteritems():
-+ req.headers[k] = v
-+ res = req.get_response(self.api)
-+ self.assertEqual(400, res.status_int)
-+
-+ def test_add_location_with_invalid_location_on_restricted_sources(self):
-+ """Tests creates an image from location and restricted sources"""
-+ fixture_headers = {'x-image-meta-store': 'file',
-+ 'x-image-meta-disk-format': 'vhd',
-+ 'x-image-meta-location': 'file:///etc/passwd',
-+ 'x-image-meta-container-format': 'ovf',
-+ 'x-image-meta-name': 'fake image #F'}
-+
-+ req = webob.Request.blank("/images")
-+ req.headers['Content-Type'] = 'application/octet-stream'
-+ req.method = 'POST'
-+ for k, v in fixture_headers.iteritems():
-+ req.headers[k] = v
-+ res = req.get_response(self.api)
-+ self.assertEqual(400, res.status_int)
-+
-+ fixture_headers = {'x-image-meta-store': 'file',
-+ 'x-image-meta-disk-format': 'vhd',
-+ 'x-image-meta-location': 'swift+config://xxx',
-+ 'x-image-meta-container-format': 'ovf',
-+ 'x-image-meta-name': 'fake image #F'}
-+
-+ req = webob.Request.blank("/images")
-+ req.headers['Content-Type'] = 'application/octet-stream'
-+ req.method = 'POST'
-+ for k, v in fixture_headers.iteritems():
-+ req.headers[k] = v
-+ res = req.get_response(self.api)
-+ self.assertEqual(400, res.status_int)
-+
- def test_add_copy_from_with_location(self):
- """Tests creates an image from copy-from and location"""
- fixture_headers = {'x-image-meta-store': 'file',
-@@ -978,6 +1025,34 @@ class TestGlanceAPI(base.IsolatedUnitTest):
- res = req.get_response(self.api)
- self.assertEqual(res.status_int, 400)
-
-+ def test_add_copy_from_with_restricted_sources(self):
-+ """Tests creates an image from copy-from with restricted sources"""
-+ fixture_headers = {'x-image-meta-store': 'file',
-+ 'x-image-meta-disk-format': 'vhd',
-+ 'x-glance-api-copy-from': 'file:///etc/passwd',
-+ 'x-image-meta-container-format': 'ovf',
-+ 'x-image-meta-name': 'fake image #F'}
-+
-+ req = webob.Request.blank("/images")
-+ req.method = 'POST'
-+ for k, v in six.iteritems(fixture_headers):
-+ req.headers[k] = v
-+ res = req.get_response(self.api)
-+ self.assertEqual(400, res.status_int)
-+
-+ fixture_headers = {'x-image-meta-store': 'file',
-+ 'x-image-meta-disk-format': 'vhd',
-+ 'x-glance-api-copy-from': 'swift+config://xxx',
-+ 'x-image-meta-container-format': 'ovf',
-+ 'x-image-meta-name': 'fake image #F'}
-+
-+ req = webob.Request.blank("/images")
-+ req.method = 'POST'
-+ for k, v in six.iteritems(fixture_headers):
-+ req.headers[k] = v
-+ res = req.get_response(self.api)
-+ self.assertEqual(400, res.status_int)
-+
- def test_add_copy_from_upload_image_unauthorized_with_body(self):
- rules = {"upload_image": '!', "modify_image": '@',
- "add_image": '@'}
diff --git a/0007-Revert-To-prevent-client-use-v2-patch-api-to-handle-.patch b/0007-Revert-To-prevent-client-use-v2-patch-api-to-handle-.patch
deleted file mode 100644
index 5726722..0000000
--- a/0007-Revert-To-prevent-client-use-v2-patch-api-to-handle-.patch
+++ /dev/null
@@ -1,637 +0,0 @@
-From ebdddcd75040467447ec27f6ad28f4dfd757206a Mon Sep 17 00:00:00 2001
-From: Lon Hohberger <lhh at redhat.com>
-Date: Tue, 23 Dec 2014 13:42:45 -0500
-Subject: [PATCH] Revert "To prevent client use v2 patch api to handle file and
- swift location"
-
-This reverts commit 8b22786cd7d5e50235389548cf5e9f15aa0f06de.
-
-Change-Id: Ide10810280aa0f37cf45e9aeee60caa8829b5003
-Reviewed-on: https://code.engineering.redhat.com/gerrit/39076
-Reviewed-by: Lon Hohberger <lhh at redhat.com>
-Tested-by: Lon Hohberger <lhh at redhat.com>
----
- glance/api/v1/images.py | 27 ++--
- glance/store/__init__.py | 30 +----
- glance/tests/functional/v1/test_copy_to_file.py | 30 +----
- glance/tests/functional/v2/test_images.py | 169 +++++++++++++-----------
- glance/tests/unit/test_store_image.py | 3 +-
- glance/tests/unit/test_store_location.py | 30 +----
- glance/tests/unit/utils.py | 9 +-
- glance/tests/unit/v1/test_api.py | 77 +----------
- 8 files changed, 123 insertions(+), 252 deletions(-)
-
-diff --git a/glance/api/v1/images.py b/glance/api/v1/images.py
-index 2f8afa0..dd6ec06 100644
---- a/glance/api/v1/images.py
-+++ b/glance/api/v1/images.py
-@@ -48,7 +48,6 @@ from glance.store import get_known_schemes
- from glance.store import get_size_from_backend
- from glance.store import get_store_from_location
- from glance.store import get_store_from_scheme
--from glance.store import validate_external_location
-
- LOG = logging.getLogger(__name__)
- SUPPORTED_PARAMS = glance.api.v1.SUPPORTED_PARAMS
-@@ -405,19 +404,23 @@ class Controller(controller.BaseController):
- @staticmethod
- def _validate_source(source, req):
- """
-- To validate if external sources (as specified via the location
-- or copy-from headers) are supported. Otherwise we reject
-- with 400 "Bad Request".
-+ External sources (as specified via the location or copy-from headers)
-+ are supported only over non-local store types, i.e. S3, Swift, HTTP.
-+ Note the absence of file:// for security reasons, see LP bug #942118.
-+ If the above constraint is violated, we reject with 400 "Bad Request".
- """
- if source:
-- if validate_external_location(source):
-- return source
-- else:
-- msg = _("External source are not supported: '%s'") % source
-- LOG.debug(msg)
-- raise HTTPBadRequest(explanation=msg,
-- request=req,
-- content_type="text/plain")
-+ pieces = urlparse.urlparse(source)
-+ schemes = [scheme for scheme in get_known_schemes()
-+ if scheme != 'file']
-+ for scheme in schemes:
-+ if pieces.scheme == scheme:
-+ return source
-+ msg = _("External sourcing not supported for store %s") % source
-+ LOG.debug(msg)
-+ raise HTTPBadRequest(explanation=msg,
-+ request=req,
-+ content_type="text/plain")
-
- @staticmethod
- def _copy_from(req):
-diff --git a/glance/store/__init__.py b/glance/store/__init__.py
-index 2ae8caa..c73d3da 100644
---- a/glance/store/__init__.py
-+++ b/glance/store/__init__.py
-@@ -19,7 +19,6 @@ import sys
-
- from oslo.config import cfg
- import six
--import six.moves.urllib.parse as urlparse
-
- from glance.common import exception
- from glance.common import utils
-@@ -420,24 +419,6 @@ def set_acls(context, location_uri, public=False, read_tenants=[],
- LOG.debug(_("Skipping store.set_acls... not implemented."))
-
-
--def validate_external_location(uri):
-- """
-- Validate if URI of external location are supported.
--
-- Only over non-local store types are OK, i.e. S3, Swift,
-- HTTP. Note the absence of 'file://' for security reasons,
-- see LP bug #942118, 1400966, 'swift+config://' is also
-- absent for security reasons, see LP bug #1334196.
--
-- :param uri: The URI of external image location.
-- :return: Whether given URI of external image location are OK.
-- """
-- pieces = urlparse.urlparse(uri)
-- valid_schemes = [scheme for scheme in get_known_schemes()
-- if scheme != 'file' and scheme != 'swift+config']
-- return pieces.scheme in valid_schemes
--
--
- class ImageRepoProxy(glance.domain.proxy.Repo):
-
- def __init__(self, image_repo, context, store_api):
-@@ -470,23 +451,22 @@ class ImageRepoProxy(glance.domain.proxy.Repo):
-
-
- def _check_location_uri(context, store_api, uri):
-- """Check if an image location is valid.
-+ """
-+ Check if an image location uri is valid.
-
- :param context: Glance request context
- :param store_api: store API module
- :param uri: location's uri string
- """
--
- is_ok = True
- try:
-+ size = store_api.get_size_from_backend(context, uri)
- # NOTE(zhiyan): Some stores return zero when it catch exception
-- is_ok = (store_api.validate_external_location(uri) and
-- store_api.get_size_from_backend(uri, context=context) > 0)
-+ is_ok = size > 0
- except (exception.UnknownScheme, exception.NotFound):
- is_ok = False
- if not is_ok:
-- reason = _('Invalid location')
-- raise exception.BadStoreUri(message=reason)
-+ raise exception.BadStoreUri(_('Invalid location: %s') % uri)
-
-
- def _check_image_location(context, store_api, location):
-diff --git a/glance/tests/functional/v1/test_copy_to_file.py b/glance/tests/functional/v1/test_copy_to_file.py
-index 2c5d833..ae2c320 100644
---- a/glance/tests/functional/v1/test_copy_to_file.py
-+++ b/glance/tests/functional/v1/test_copy_to_file.py
-@@ -248,35 +248,9 @@ class TestCopyToFile(functional.FunctionalTest):
- path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
- http = httplib2.Http()
- response, content = http.request(path, 'POST', headers=headers)
-- self.assertEqual(400, response.status, content)
-+ self.assertEqual(response.status, 400, content)
-
-- expected = 'External source are not supported: \'%s\'' % copy_from
-- msg = 'expected "%s" in "%s"' % (expected, content)
-- self.assertTrue(expected in content, msg)
--
-- self.stop_servers()
--
-- @skip_if_disabled
-- def test_copy_from_swift_config(self):
-- """
-- Ensure we can't copy from swift+config
-- """
-- self.cleanup()
--
-- self.start_servers(**self.__dict__.copy())
--
-- # POST /images with public image copied from file (to file)
-- headers = {'X-Image-Meta-Name': 'copied',
-- 'X-Image-Meta-disk_format': 'raw',
-- 'X-Image-Meta-container_format': 'ovf',
-- 'X-Image-Meta-Is-Public': 'True',
-- 'X-Glance-API-Copy-From': 'swift+config://xxx'}
-- path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
-- http = httplib2.Http()
-- response, content = http.request(path, 'POST', headers=headers)
-- self.assertEqual(400, response.status, content)
--
-- expected = 'External source are not supported: \'swift+config://xxx\''
-+ expected = 'External sourcing not supported for store ' + copy_from
- msg = 'expected "%s" in "%s"' % (expected, content)
- self.assertTrue(expected in content, msg)
-
-diff --git a/glance/tests/functional/v2/test_images.py b/glance/tests/functional/v2/test_images.py
-index 1bb0e56..4247434 100644
---- a/glance/tests/functional/v2/test_images.py
-+++ b/glance/tests/functional/v2/test_images.py
-@@ -15,6 +15,7 @@
-
- import os
- import signal
-+import tempfile
- import uuid
-
- import requests
-@@ -37,19 +38,6 @@ class TestImages(functional.FunctionalTest):
- self.cleanup()
- self.api_server.deployment_flavor = 'noauth'
- self.start_servers(**self.__dict__.copy())
-- for i in range(3):
-- ret = test_http.http_server("foo_image_id%d" % i,
-- "foo_image%d" % i)
-- setattr(self, 'http_server%d_pid' % i, ret[0])
-- setattr(self, 'http_port%d' % i, ret[1])
--
-- def tearDown(self):
-- for i in range(3):
-- pid = getattr(self, 'http_server%d_pid' % i, None)
-- if pid:
-- os.kill(pid, signal.SIGKILL)
--
-- super(TestImages, self).tearDown()
-
- def _url(self, path):
- return 'http://127.0.0.1:%d%s' % (self.api_port, path)
-@@ -294,15 +282,21 @@ class TestImages(functional.FunctionalTest):
- self.assertEqual(413, response.status_code, response.text)
-
- # Adding 3 image locations should fail since configured limit is 2
-+ for i in range(3):
-+ file_path = os.path.join(self.test_dir, 'fake_image_%i' % i)
-+ with open(file_path, 'w') as fap:
-+ fap.write('glance')
-+
- path = self._url('/v2/images/%s' % image_id)
- media_type = 'application/openstack-images-v2.1-json-patch'
- headers = self._headers({'content-type': media_type})
- changes = []
- for i in range(3):
-- url = ('http://127.0.0.1:%s/foo_image' %
-- getattr(self, 'http_port%d' % i))
- changes.append({'op': 'add', 'path': '/locations/-',
-- 'value': {'url': url, 'metadata': {}},
-+ 'value': {'url': 'file://{0}'.format(
-+ os.path.join(self.test_dir,
-+ 'fake_image_%i' % i)),
-+ 'metadata': {}},
- })
-
- data = jsonutils.dumps(changes)
-@@ -1817,14 +1811,17 @@ class TestImages(functional.FunctionalTest):
- self.assertNotIn('size', image)
- self.assertNotIn('virtual_size', image)
-
-+ file_path = os.path.join(self.test_dir, 'fake_image')
-+ with open(file_path, 'w') as fap:
-+ fap.write('glance')
-+
- # Update locations for the queued image
- path = self._url('/v2/images/%s' % image_id)
- media_type = 'application/openstack-images-v2.1-json-patch'
- headers = self._headers({'content-type': media_type})
-- url = 'http://127.0.0.1:%s/foo_image' % self.http_port0
- data = jsonutils.dumps([{'op': 'replace', 'path': '/locations',
-- 'value': [{'url': url, 'metadata': {}}]
-- }])
-+ 'value': [{'url': 'file://' + file_path,
-+ 'metadata': {}}]}])
- response = requests.patch(path, headers=headers, data=data)
- self.assertEqual(200, response.status_code, response.text)
-
-@@ -1833,51 +1830,7 @@ class TestImages(functional.FunctionalTest):
- response = requests.get(path, headers=headers)
- self.assertEqual(200, response.status_code)
- image = jsonutils.loads(response.text)
-- self.assertEqual(10, image['size'])
--
-- def test_update_locations_with_restricted_sources(self):
-- self.start_servers(**self.__dict__.copy())
-- # Create an image
-- path = self._url('/v2/images')
-- headers = self._headers({'content-type': 'application/json'})
-- data = jsonutils.dumps({'name': 'image-1', 'disk_format': 'aki',
-- 'container_format': 'aki'})
-- response = requests.post(path, headers=headers, data=data)
-- self.assertEqual(201, response.status_code)
--
-- # Returned image entity should have a generated id and status
-- image = jsonutils.loads(response.text)
-- image_id = image['id']
-- self.assertEqual('queued', image['status'])
-- self.assertIsNone(image['size'])
-- self.assertIsNone(image['virtual_size'])
--
-- # Update locations for the queued image
-- path = self._url('/v2/images/%s' % image_id)
-- media_type = 'application/openstack-images-v2.1-json-patch'
-- headers = self._headers({'content-type': media_type})
-- data = jsonutils.dumps([{'op': 'replace', 'path': '/locations',
-- 'value': [{'url': 'file:///foo_image',
-- 'metadata': {}}]
-- }])
-- response = requests.patch(path, headers=headers, data=data)
-- self.assertEqual(400, response.status_code, response.text)
--
-- data = jsonutils.dumps([{'op': 'replace', 'path': '/locations',
-- 'value': [{'url': 'swift+config:///foo_image',
-- 'metadata': {}}]
-- }])
-- response = requests.patch(path, headers=headers, data=data)
-- self.assertEqual(400, response.status_code, response.text)
--
--
--class TestImagesWithRegistry(TestImages):
-- def setUp(self):
-- super(TestImagesWithRegistry, self).setUp()
-- self.api_server.data_api = (
-- 'glance.tests.functional.v2.registry_data_api')
-- self.registry_server.deployment_flavor = 'trusted-auth'
-->>>>>>> 4afdb01... To prevent client use v2 patch api to handle file and swift location
-+ self.assertEqual(image['size'], 6)
-
-
- class TestImageDirectURLVisibility(functional.FunctionalTest):
-@@ -2087,17 +2040,16 @@ class TestImageLocationSelectionStrategy(functional.FunctionalTest):
- super(TestImageLocationSelectionStrategy, self).setUp()
- self.cleanup()
- self.api_server.deployment_flavor = 'noauth'
-- for i in range(3):
-- ret = test_http.http_server("foo_image_id%d" % i,
-- "foo_image%d" % i)
-- setattr(self, 'http_server%d_pid' % i, ret[0])
-- setattr(self, 'http_port%d' % i, ret[1])
-+ self.foo_image_file = tempfile.NamedTemporaryFile()
-+ self.foo_image_file.write("foo image file")
-+ self.foo_image_file.flush()
-+ self.addCleanup(self.foo_image_file.close)
-+ ret = test_http.http_server("foo_image_id", "foo_image")
-+ self.http_server_pid, self.http_port = ret
-
- def tearDown(self):
-- for i in range(3):
-- pid = getattr(self, 'http_server%d_pid' % i, None)
-- if pid:
-- os.kill(pid, signal.SIGKILL)
-+ if self.http_server_pid is not None:
-+ os.kill(self.http_server_pid, signal.SIGKILL)
-
- super(TestImageLocationSelectionStrategy, self).tearDown()
-
-@@ -2146,14 +2098,73 @@ class TestImageLocationSelectionStrategy(functional.FunctionalTest):
- self.assertTrue('locations' in image)
- self.assertTrue(image["locations"] == [])
-
-- # Update image locations via PATCH
-+ # Update image locations via PATCH
-+ path = self._url('/v2/images/%s' % image_id)
-+ media_type = 'application/openstack-images-v2.1-json-patch'
-+ headers = self._headers({'content-type': media_type})
-+ values = [{'url': 'file://%s' % self.foo_image_file.name,
-+ 'metadata': {'idx': '1'}},
-+ {'url': 'http://127.0.0.1:%s/foo_image' % self.http_port,
-+ 'metadata': {'idx': '0'}}]
-+ doc = [{'op': 'replace',
-+ 'path': '/locations',
-+ 'value': values}]
-+ data = jsonutils.dumps(doc)
-+ response = requests.patch(path, headers=headers, data=data)
-+ self.assertEqual(200, response.status_code)
-+
-+ # Image locations should be visible
-+ path = self._url('/v2/images/%s' % image_id)
-+ headers = self._headers({'Content-Type': 'application/json'})
-+ response = requests.get(path, headers=headers)
-+ self.assertEqual(200, response.status_code)
-+ image = jsonutils.loads(response.text)
-+ self.assertTrue('locations' in image)
-+ self.assertEqual(image['locations'], values)
-+ self.assertTrue('direct_url' in image)
-+ self.assertEqual(image['direct_url'], values[0]['url'])
-+
-+ self.stop_servers()
-+
-+ def test_image_locatons_with_store_type_strategy(self):
-+ self.api_server.show_image_direct_url = True
-+ self.api_server.show_multiple_locations = True
-+ self.image_location_quota = 10
-+ self.api_server.location_strategy = 'store_type'
-+ preference = "http, swift, filesystem"
-+ self.api_server.store_type_location_strategy_preference = preference
-+ self.start_servers(**self.__dict__.copy())
-+
-+ # Create an image
-+ path = self._url('/v2/images')
-+ headers = self._headers({'content-type': 'application/json'})
-+ data = jsonutils.dumps({'name': 'image-1', 'type': 'kernel',
-+ 'foo': 'bar', 'disk_format': 'aki',
-+ 'container_format': 'aki'})
-+ response = requests.post(path, headers=headers, data=data)
-+ self.assertEqual(201, response.status_code)
-+
-+ # Get the image id
-+ image = jsonutils.loads(response.text)
-+ image_id = image['id']
-+
-+ # Image locations should not be visible before location is set
-+ path = self._url('/v2/images/%s' % image_id)
-+ headers = self._headers({'Content-Type': 'application/json'})
-+ response = requests.get(path, headers=headers)
-+ self.assertEqual(200, response.status_code)
-+ image = jsonutils.loads(response.text)
-+ self.assertTrue('locations' in image)
-+ self.assertTrue(image["locations"] == [])
-+
-+ # Update image locations via PATCH
- path = self._url('/v2/images/%s' % image_id)
- media_type = 'application/openstack-images-v2.1-json-patch'
- headers = self._headers({'content-type': media_type})
-- values = [{'url': 'http://127.0.0.1:%s/foo_image' % self.http_port0,
-- 'metadata': {}},
-- {'url': 'http://127.0.0.1:%s/foo_image' % self.http_port1,
-- 'metadata': {}}]
-+ values = [{'url': 'file://%s' % self.foo_image_file.name,
-+ 'metadata': {'idx': '1'}},
-+ {'url': 'http://127.0.0.1:%s/foo_image' % self.http_port,
-+ 'metadata': {'idx': '0'}}]
- doc = [{'op': 'replace',
- 'path': '/locations',
- 'value': values}]
-@@ -2161,6 +2172,8 @@ class TestImageLocationSelectionStrategy(functional.FunctionalTest):
- response = requests.patch(path, headers=headers, data=data)
- self.assertEqual(200, response.status_code)
-
-+ values.sort(key=lambda loc: int(loc['metadata']['idx']))
-+
- # Image locations should be visible
- path = self._url('/v2/images/%s' % image_id)
- headers = self._headers({'Content-Type': 'application/json'})
-diff --git a/glance/tests/unit/test_store_image.py b/glance/tests/unit/test_store_image.py
-index 9d6cc4e..424915b 100644
---- a/glance/tests/unit/test_store_image.py
-+++ b/glance/tests/unit/test_store_image.py
-@@ -16,7 +16,6 @@ import mox
-
- from glance.common import exception
- import glance.store
--from glance.tests.unit import base as unit_test_base
- from glance.tests.unit import utils as unit_test_utils
- from glance.tests import utils
-
-@@ -732,7 +731,7 @@ class TestStoreImageRepo(utils.BaseTestCase):
- self.assertEqual(acls['read'], [TENANT2])
-
-
--class TestImageFactory(unit_test_base.StoreClearingUnitTest):
-+class TestImageFactory(utils.BaseTestCase):
-
- def setUp(self):
- super(TestImageFactory, self).setUp()
-diff --git a/glance/tests/unit/test_store_location.py b/glance/tests/unit/test_store_location.py
-index a19a33a..df8d5d7 100644
---- a/glance/tests/unit/test_store_location.py
-+++ b/glance/tests/unit/test_store_location.py
-@@ -488,12 +488,11 @@ class TestStoreLocation(base.StoreClearingUnitTest):
- ctx,
- store)
-
-- class FakeImageProxy():
-- size = None
-- context = None
-- store_api = mock.Mock()
--
- def test_add_location_for_image_without_size(self):
-+ class FakeImageProxy():
-+ size = None
-+ context = None
-+ store_api = mock.Mock()
-
- def fake_get_size_from_backend(context, uri):
- return 1
-@@ -505,31 +504,14 @@ class TestStoreLocation(base.StoreClearingUnitTest):
- loc2 = {'url': 'file:///fake2.img.tar.gz', 'metadata': {}}
-
- # Test for insert location
-- image1 = TestStoreLocation.FakeImageProxy()
-+ image1 = FakeImageProxy()
- locations = glance.store.StoreLocations(image1, [])
- locations.insert(0, loc2)
- self.assertEqual(image1.size, 1)
-
- # Test for set_attr of _locations_proxy
-- image2 = TestStoreLocation.FakeImageProxy()
-+ image2 = FakeImageProxy()
- locations = glance.store.StoreLocations(image2, [loc1])
- locations[0] = loc2
- self.assertTrue(loc2 in locations)
- self.assertEqual(image2.size, 1)
--
-- def test_add_location_with_restricted_sources(self):
--
-- loc1 = {'url': 'file:///fake1.img.tar.gz', 'metadata': {}}
-- loc2 = {'url': 'swift+config:///xxx', 'metadata': {}}
--
-- # Test for insert location
-- image1 = TestStoreLocation.FakeImageProxy()
-- locations = glance.store.StoreLocations(image1, [])
-- self.assertRaises(exception.BadStoreUri, locations.insert, 0, loc1)
-- self.assertNotIn(loc1, locations)
--
-- # Test for set_attr of _locations_proxy
-- image2 = TestStoreLocation.FakeImageProxy()
-- locations = glance.store.StoreLocations(image2, [loc1])
-- self.assertRaises(exception.BadStoreUri, locations.insert, 0, loc2)
-- self.assertNotIn(loc2, locations)
-diff --git a/glance/tests/unit/utils.py b/glance/tests/unit/utils.py
-index e0dfc43..1c4e16a 100644
---- a/glance/tests/unit/utils.py
-+++ b/glance/tests/unit/utils.py
-@@ -14,9 +14,9 @@
- # under the License.
-
- import urllib
-+import urlparse
-
- from oslo.config import cfg
--import six.moves.urllib.parse as urlparse
-
- from glance.common import exception
- from glance.common import wsgi
-@@ -113,6 +113,7 @@ class FakeDB(object):
- def __getattr__(self, key):
- return getattr(simple_db, key)
-
-+
- class FakeStoreAPI(object):
- def __init__(self, store_metadata=None):
- self.data = {
-@@ -187,12 +188,6 @@ class FakeStoreAPI(object):
- def check_location_metadata(self, val, key=''):
- glance.store.check_location_metadata(val)
-
-- def validate_external_location(self, uri):
-- if uri and urlparse.urlparse(uri).scheme:
-- return glance.store.validate_external_location(uri)
-- else:
-- return True
--
-
- class FakePolicyEnforcer(object):
- def __init__(self, *_args, **kwargs):
-diff --git a/glance/tests/unit/v1/test_api.py b/glance/tests/unit/v1/test_api.py
-index 9856487..5618cb0 100644
---- a/glance/tests/unit/v1/test_api.py
-+++ b/glance/tests/unit/v1/test_api.py
-@@ -379,7 +379,7 @@ class TestGlanceAPI(base.IsolatedUnitTest):
-
- res = req.get_response(self.api)
- self.assertEqual(res.status_int, 400)
-- self.assertIn('External source are not supported', res.body)
-+ self.assertTrue('External sourcing not supported' in res.body)
-
- def test_create_with_location_bad_store_uri(self):
- fixture_headers = {
-@@ -962,53 +962,6 @@ class TestGlanceAPI(base.IsolatedUnitTest):
- res = req.get_response(self.api)
- self.assertEqual(res.status_int, 409)
-
-- def test_add_location_with_invalid_location_on_conflict_image_size(self):
-- """Tests creates an image from location and conflict image size"""
-- fixture_headers = {'x-image-meta-store': 'file',
-- 'x-image-meta-disk-format': 'vhd',
-- 'x-image-meta-location': 'http://a/b/c.tar.gz',
-- 'x-image-meta-container-format': 'ovf',
-- 'x-image-meta-name': 'fake image #F',
-- 'x-image-meta-size': '1'}
--
-- req = webob.Request.blank("/images")
-- req.headers['Content-Type'] = 'application/octet-stream'
-- req.method = 'POST'
-- for k, v in fixture_headers.iteritems():
-- req.headers[k] = v
-- res = req.get_response(self.api)
-- self.assertEqual(400, res.status_int)
--
-- def test_add_location_with_invalid_location_on_restricted_sources(self):
-- """Tests creates an image from location and restricted sources"""
-- fixture_headers = {'x-image-meta-store': 'file',
-- 'x-image-meta-disk-format': 'vhd',
-- 'x-image-meta-location': 'file:///etc/passwd',
-- 'x-image-meta-container-format': 'ovf',
-- 'x-image-meta-name': 'fake image #F'}
--
-- req = webob.Request.blank("/images")
-- req.headers['Content-Type'] = 'application/octet-stream'
-- req.method = 'POST'
-- for k, v in fixture_headers.iteritems():
-- req.headers[k] = v
-- res = req.get_response(self.api)
-- self.assertEqual(400, res.status_int)
--
-- fixture_headers = {'x-image-meta-store': 'file',
-- 'x-image-meta-disk-format': 'vhd',
-- 'x-image-meta-location': 'swift+config://xxx',
-- 'x-image-meta-container-format': 'ovf',
-- 'x-image-meta-name': 'fake image #F'}
--
-- req = webob.Request.blank("/images")
-- req.headers['Content-Type'] = 'application/octet-stream'
-- req.method = 'POST'
-- for k, v in fixture_headers.iteritems():
-- req.headers[k] = v
-- res = req.get_response(self.api)
-- self.assertEqual(400, res.status_int)
--
- def test_add_copy_from_with_location(self):
- """Tests creates an image from copy-from and location"""
- fixture_headers = {'x-image-meta-store': 'file',
-@@ -1025,34 +978,6 @@ class TestGlanceAPI(base.IsolatedUnitTest):
- res = req.get_response(self.api)
- self.assertEqual(res.status_int, 400)
-
-- def test_add_copy_from_with_restricted_sources(self):
-- """Tests creates an image from copy-from with restricted sources"""
-- fixture_headers = {'x-image-meta-store': 'file',
-- 'x-image-meta-disk-format': 'vhd',
-- 'x-glance-api-copy-from': 'file:///etc/passwd',
-- 'x-image-meta-container-format': 'ovf',
-- 'x-image-meta-name': 'fake image #F'}
--
-- req = webob.Request.blank("/images")
-- req.method = 'POST'
-- for k, v in six.iteritems(fixture_headers):
-- req.headers[k] = v
-- res = req.get_response(self.api)
-- self.assertEqual(400, res.status_int)
--
-- fixture_headers = {'x-image-meta-store': 'file',
-- 'x-image-meta-disk-format': 'vhd',
-- 'x-glance-api-copy-from': 'swift+config://xxx',
-- 'x-image-meta-container-format': 'ovf',
-- 'x-image-meta-name': 'fake image #F'}
--
-- req = webob.Request.blank("/images")
-- req.method = 'POST'
-- for k, v in six.iteritems(fixture_headers):
-- req.headers[k] = v
-- res = req.get_response(self.api)
-- self.assertEqual(400, res.status_int)
--
- def test_add_copy_from_upload_image_unauthorized_with_body(self):
- rules = {"upload_image": '!', "modify_image": '@',
- "add_image": '@'}
diff --git a/0008-To-prevent-client-use-v2-patch-api-to-handle-file-an.patch b/0008-To-prevent-client-use-v2-patch-api-to-handle-file-an.patch
deleted file mode 100644
index d80fddc..0000000
--- a/0008-To-prevent-client-use-v2-patch-api-to-handle-file-an.patch
+++ /dev/null
@@ -1,670 +0,0 @@
-From 0b637884baaea4cefc46dc59314d54c57f567921 Mon Sep 17 00:00:00 2001
-From: Zhi Yan Liu <zhiyanl at cn.ibm.com>
-Date: Mon, 15 Dec 2014 12:29:55 +0800
-Subject: [PATCH] To prevent client use v2 patch api to handle file and swift
- location
-
-The change will be used to restrict client to download and delete any
-file in glance-api server. The same resone and logic as what we did in
-v1:
-https://github.com/openstack/glance/blob/master/glance/api/v1/images.py#L429
-
-Closes-Bug: bug/1400966
-DocImpact
-
-Note: Even this change could fully resolve the problem for Glance, but
-we still need to fix this issue from glance_store perspective
-separatelly due to other projects can use the lib directly.
-
-Conflicts:
- glance/api/v1/images.py
- glance/common/store_utils.py
- glance/location.py
- glance/tests/functional/v1/test_copy_to_file.py
- glance/tests/functional/v2/test_images.py
- glance/tests/unit/test_store_image.py
- glance/tests/unit/test_store_location.py
- glance/tests/unit/utils.py
- glance/tests/unit/v1/test_api.py
-
-(cherry picked from commit 4afdb017aa1ccef01482f117cb8d0736a6da38ed)
-Signed-off-by: Zhi Yan Liu <zhiyanl at cn.ibm.com>
-
-Change-Id: I5a594731f18eb4dd4fba56de766e5c1c0e0cc442
-Resolves: rhbz #1174483
-Resolves: rhbz #1174484
-Upstream-Closes-Bug: #1400966
-Upstream-Kilo: https://review.openstack.org/#/c/141706/
-Upstream-Juno: https://review.openstack.org/#/c/142373/
-Upstream-Icehouse: https://review.openstack.org/#/c/142703/
-Upstream-change-Id: I72dbead3cb2dcb87f52658ddb880e26880cc229b
-Reviewed-on: https://code.engineering.redhat.com/gerrit/38836
-Reviewed-by: Eoghan Glynn <eglynn at redhat.com>
-Tested-by: Eoghan Glynn <eglynn at redhat.com>
-Reviewed-on: https://code.engineering.redhat.com/gerrit/39077
-Reviewed-by: Lon Hohberger <lhh at redhat.com>
-Tested-by: Lon Hohberger <lhh at redhat.com>
----
- glance/api/v1/images.py | 27 ++--
- glance/store/__init__.py | 30 ++++-
- glance/tests/functional/v1/test_copy_to_file.py | 30 ++++-
- glance/tests/functional/v2/test_images.py | 168 +++++++++++-------------
- glance/tests/unit/test_store_image.py | 3 +-
- glance/tests/unit/test_store_location.py | 30 ++++-
- glance/tests/unit/utils.py | 9 +-
- glance/tests/unit/v1/test_api.py | 77 ++++++++++-
- 8 files changed, 251 insertions(+), 123 deletions(-)
-
-diff --git a/glance/api/v1/images.py b/glance/api/v1/images.py
-index dd6ec06..2f8afa0 100644
---- a/glance/api/v1/images.py
-+++ b/glance/api/v1/images.py
-@@ -48,6 +48,7 @@ from glance.store import get_known_schemes
- from glance.store import get_size_from_backend
- from glance.store import get_store_from_location
- from glance.store import get_store_from_scheme
-+from glance.store import validate_external_location
-
- LOG = logging.getLogger(__name__)
- SUPPORTED_PARAMS = glance.api.v1.SUPPORTED_PARAMS
-@@ -404,23 +405,19 @@ class Controller(controller.BaseController):
- @staticmethod
- def _validate_source(source, req):
- """
-- External sources (as specified via the location or copy-from headers)
-- are supported only over non-local store types, i.e. S3, Swift, HTTP.
-- Note the absence of file:// for security reasons, see LP bug #942118.
-- If the above constraint is violated, we reject with 400 "Bad Request".
-+ To validate if external sources (as specified via the location
-+ or copy-from headers) are supported. Otherwise we reject
-+ with 400 "Bad Request".
- """
- if source:
-- pieces = urlparse.urlparse(source)
-- schemes = [scheme for scheme in get_known_schemes()
-- if scheme != 'file']
-- for scheme in schemes:
-- if pieces.scheme == scheme:
-- return source
-- msg = _("External sourcing not supported for store %s") % source
-- LOG.debug(msg)
-- raise HTTPBadRequest(explanation=msg,
-- request=req,
-- content_type="text/plain")
-+ if validate_external_location(source):
-+ return source
-+ else:
-+ msg = _("External source are not supported: '%s'") % source
-+ LOG.debug(msg)
-+ raise HTTPBadRequest(explanation=msg,
-+ request=req,
-+ content_type="text/plain")
-
- @staticmethod
- def _copy_from(req):
-diff --git a/glance/store/__init__.py b/glance/store/__init__.py
-index c73d3da..2ae8caa 100644
---- a/glance/store/__init__.py
-+++ b/glance/store/__init__.py
-@@ -19,6 +19,7 @@ import sys
-
- from oslo.config import cfg
- import six
-+import six.moves.urllib.parse as urlparse
-
- from glance.common import exception
- from glance.common import utils
-@@ -419,6 +420,24 @@ def set_acls(context, location_uri, public=False, read_tenants=[],
- LOG.debug(_("Skipping store.set_acls... not implemented."))
-
-
-+def validate_external_location(uri):
-+ """
-+ Validate if URI of external location are supported.
-+
-+ Only over non-local store types are OK, i.e. S3, Swift,
-+ HTTP. Note the absence of 'file://' for security reasons,
-+ see LP bug #942118, 1400966, 'swift+config://' is also
-+ absent for security reasons, see LP bug #1334196.
-+
-+ :param uri: The URI of external image location.
-+ :return: Whether given URI of external image location are OK.
-+ """
-+ pieces = urlparse.urlparse(uri)
-+ valid_schemes = [scheme for scheme in get_known_schemes()
-+ if scheme != 'file' and scheme != 'swift+config']
-+ return pieces.scheme in valid_schemes
-+
-+
- class ImageRepoProxy(glance.domain.proxy.Repo):
-
- def __init__(self, image_repo, context, store_api):
-@@ -451,22 +470,23 @@ class ImageRepoProxy(glance.domain.proxy.Repo):
-
-
- def _check_location_uri(context, store_api, uri):
-- """
-- Check if an image location uri is valid.
-+ """Check if an image location is valid.
-
- :param context: Glance request context
- :param store_api: store API module
- :param uri: location's uri string
- """
-+
- is_ok = True
- try:
-- size = store_api.get_size_from_backend(context, uri)
- # NOTE(zhiyan): Some stores return zero when it catch exception
-- is_ok = size > 0
-+ is_ok = (store_api.validate_external_location(uri) and
-+ store_api.get_size_from_backend(uri, context=context) > 0)
- except (exception.UnknownScheme, exception.NotFound):
- is_ok = False
- if not is_ok:
-- raise exception.BadStoreUri(_('Invalid location: %s') % uri)
-+ reason = _('Invalid location')
-+ raise exception.BadStoreUri(message=reason)
-
-
- def _check_image_location(context, store_api, location):
-diff --git a/glance/tests/functional/v1/test_copy_to_file.py b/glance/tests/functional/v1/test_copy_to_file.py
-index ae2c320..2c5d833 100644
---- a/glance/tests/functional/v1/test_copy_to_file.py
-+++ b/glance/tests/functional/v1/test_copy_to_file.py
-@@ -248,9 +248,35 @@ class TestCopyToFile(functional.FunctionalTest):
- path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
- http = httplib2.Http()
- response, content = http.request(path, 'POST', headers=headers)
-- self.assertEqual(response.status, 400, content)
-+ self.assertEqual(400, response.status, content)
-
-- expected = 'External sourcing not supported for store ' + copy_from
-+ expected = 'External source are not supported: \'%s\'' % copy_from
-+ msg = 'expected "%s" in "%s"' % (expected, content)
-+ self.assertTrue(expected in content, msg)
-+
-+ self.stop_servers()
-+
-+ @skip_if_disabled
-+ def test_copy_from_swift_config(self):
-+ """
-+ Ensure we can't copy from swift+config
-+ """
-+ self.cleanup()
-+
-+ self.start_servers(**self.__dict__.copy())
-+
-+ # POST /images with public image copied from file (to file)
-+ headers = {'X-Image-Meta-Name': 'copied',
-+ 'X-Image-Meta-disk_format': 'raw',
-+ 'X-Image-Meta-container_format': 'ovf',
-+ 'X-Image-Meta-Is-Public': 'True',
-+ 'X-Glance-API-Copy-From': 'swift+config://xxx'}
-+ path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
-+ http = httplib2.Http()
-+ response, content = http.request(path, 'POST', headers=headers)
-+ self.assertEqual(400, response.status, content)
-+
-+ expected = 'External source are not supported: \'swift+config://xxx\''
- msg = 'expected "%s" in "%s"' % (expected, content)
- self.assertTrue(expected in content, msg)
-
-diff --git a/glance/tests/functional/v2/test_images.py b/glance/tests/functional/v2/test_images.py
-index 4247434..36c986e 100644
---- a/glance/tests/functional/v2/test_images.py
-+++ b/glance/tests/functional/v2/test_images.py
-@@ -15,7 +15,6 @@
-
- import os
- import signal
--import tempfile
- import uuid
-
- import requests
-@@ -38,6 +37,19 @@ class TestImages(functional.FunctionalTest):
- self.cleanup()
- self.api_server.deployment_flavor = 'noauth'
- self.start_servers(**self.__dict__.copy())
-+ for i in range(3):
-+ ret = test_http.http_server("foo_image_id%d" % i,
-+ "foo_image%d" % i)
-+ setattr(self, 'http_server%d_pid' % i, ret[0])
-+ setattr(self, 'http_port%d' % i, ret[1])
-+
-+ def tearDown(self):
-+ for i in range(3):
-+ pid = getattr(self, 'http_server%d_pid' % i, None)
-+ if pid:
-+ os.kill(pid, signal.SIGKILL)
-+
-+ super(TestImages, self).tearDown()
-
- def _url(self, path):
- return 'http://127.0.0.1:%d%s' % (self.api_port, path)
-@@ -282,21 +294,15 @@ class TestImages(functional.FunctionalTest):
- self.assertEqual(413, response.status_code, response.text)
-
- # Adding 3 image locations should fail since configured limit is 2
-- for i in range(3):
-- file_path = os.path.join(self.test_dir, 'fake_image_%i' % i)
-- with open(file_path, 'w') as fap:
-- fap.write('glance')
--
- path = self._url('/v2/images/%s' % image_id)
- media_type = 'application/openstack-images-v2.1-json-patch'
- headers = self._headers({'content-type': media_type})
- changes = []
- for i in range(3):
-+ url = ('http://127.0.0.1:%s/foo_image' %
-+ getattr(self, 'http_port%d' % i))
- changes.append({'op': 'add', 'path': '/locations/-',
-- 'value': {'url': 'file://{0}'.format(
-- os.path.join(self.test_dir,
-- 'fake_image_%i' % i)),
-- 'metadata': {}},
-+ 'value': {'url': url, 'metadata': {}},
- })
-
- data = jsonutils.dumps(changes)
-@@ -1811,17 +1817,14 @@ class TestImages(functional.FunctionalTest):
- self.assertNotIn('size', image)
- self.assertNotIn('virtual_size', image)
-
-- file_path = os.path.join(self.test_dir, 'fake_image')
-- with open(file_path, 'w') as fap:
-- fap.write('glance')
--
- # Update locations for the queued image
- path = self._url('/v2/images/%s' % image_id)
- media_type = 'application/openstack-images-v2.1-json-patch'
- headers = self._headers({'content-type': media_type})
-+ url = 'http://127.0.0.1:%s/foo_image' % self.http_port0
- data = jsonutils.dumps([{'op': 'replace', 'path': '/locations',
-- 'value': [{'url': 'file://' + file_path,
-- 'metadata': {}}]}])
-+ 'value': [{'url': url, 'metadata': {}}]
-+ }])
- response = requests.patch(path, headers=headers, data=data)
- self.assertEqual(200, response.status_code, response.text)
-
-@@ -1830,7 +1833,50 @@ class TestImages(functional.FunctionalTest):
- response = requests.get(path, headers=headers)
- self.assertEqual(200, response.status_code)
- image = jsonutils.loads(response.text)
-- self.assertEqual(image['size'], 6)
-+ self.assertEqual(10, image['size'])
-+
-+ def test_update_locations_with_restricted_sources(self):
-+ self.start_servers(**self.__dict__.copy())
-+ # Create an image
-+ path = self._url('/v2/images')
-+ headers = self._headers({'content-type': 'application/json'})
-+ data = jsonutils.dumps({'name': 'image-1', 'disk_format': 'aki',
-+ 'container_format': 'aki'})
-+ response = requests.post(path, headers=headers, data=data)
-+ self.assertEqual(201, response.status_code)
-+
-+ # Returned image entity should have a generated id and status
-+ image = jsonutils.loads(response.text)
-+ image_id = image['id']
-+ self.assertEqual('queued', image['status'])
-+ self.assertIsNone(image['size'])
-+ self.assertIsNone(image['virtual_size'])
-+
-+ # Update locations for the queued image
-+ path = self._url('/v2/images/%s' % image_id)
-+ media_type = 'application/openstack-images-v2.1-json-patch'
-+ headers = self._headers({'content-type': media_type})
-+ data = jsonutils.dumps([{'op': 'replace', 'path': '/locations',
-+ 'value': [{'url': 'file:///foo_image',
-+ 'metadata': {}}]
-+ }])
-+ response = requests.patch(path, headers=headers, data=data)
-+ self.assertEqual(400, response.status_code, response.text)
-+
-+ data = jsonutils.dumps([{'op': 'replace', 'path': '/locations',
-+ 'value': [{'url': 'swift+config:///foo_image',
-+ 'metadata': {}}]
-+ }])
-+ response = requests.patch(path, headers=headers, data=data)
-+ self.assertEqual(400, response.status_code, response.text)
-+
-+
-+class TestImagesWithRegistry(TestImages):
-+ def setUp(self):
-+ super(TestImagesWithRegistry, self).setUp()
-+ self.api_server.data_api = (
-+ 'glance.tests.functional.v2.registry_data_api')
-+ self.registry_server.deployment_flavor = 'trusted-auth'
-
-
- class TestImageDirectURLVisibility(functional.FunctionalTest):
-@@ -2040,16 +2086,17 @@ class TestImageLocationSelectionStrategy(functional.FunctionalTest):
- super(TestImageLocationSelectionStrategy, self).setUp()
- self.cleanup()
- self.api_server.deployment_flavor = 'noauth'
-- self.foo_image_file = tempfile.NamedTemporaryFile()
-- self.foo_image_file.write("foo image file")
-- self.foo_image_file.flush()
-- self.addCleanup(self.foo_image_file.close)
-- ret = test_http.http_server("foo_image_id", "foo_image")
-- self.http_server_pid, self.http_port = ret
-+ for i in range(3):
-+ ret = test_http.http_server("foo_image_id%d" % i,
-+ "foo_image%d" % i)
-+ setattr(self, 'http_server%d_pid' % i, ret[0])
-+ setattr(self, 'http_port%d' % i, ret[1])
-
- def tearDown(self):
-- if self.http_server_pid is not None:
-- os.kill(self.http_server_pid, signal.SIGKILL)
-+ for i in range(3):
-+ pid = getattr(self, 'http_server%d_pid' % i, None)
-+ if pid:
-+ os.kill(pid, signal.SIGKILL)
-
- super(TestImageLocationSelectionStrategy, self).tearDown()
-
-@@ -2098,73 +2145,14 @@ class TestImageLocationSelectionStrategy(functional.FunctionalTest):
- self.assertTrue('locations' in image)
- self.assertTrue(image["locations"] == [])
-
-- # Update image locations via PATCH
-- path = self._url('/v2/images/%s' % image_id)
-- media_type = 'application/openstack-images-v2.1-json-patch'
-- headers = self._headers({'content-type': media_type})
-- values = [{'url': 'file://%s' % self.foo_image_file.name,
-- 'metadata': {'idx': '1'}},
-- {'url': 'http://127.0.0.1:%s/foo_image' % self.http_port,
-- 'metadata': {'idx': '0'}}]
-- doc = [{'op': 'replace',
-- 'path': '/locations',
-- 'value': values}]
-- data = jsonutils.dumps(doc)
-- response = requests.patch(path, headers=headers, data=data)
-- self.assertEqual(200, response.status_code)
--
-- # Image locations should be visible
-- path = self._url('/v2/images/%s' % image_id)
-- headers = self._headers({'Content-Type': 'application/json'})
-- response = requests.get(path, headers=headers)
-- self.assertEqual(200, response.status_code)
-- image = jsonutils.loads(response.text)
-- self.assertTrue('locations' in image)
-- self.assertEqual(image['locations'], values)
-- self.assertTrue('direct_url' in image)
-- self.assertEqual(image['direct_url'], values[0]['url'])
--
-- self.stop_servers()
--
-- def test_image_locatons_with_store_type_strategy(self):
-- self.api_server.show_image_direct_url = True
-- self.api_server.show_multiple_locations = True
-- self.image_location_quota = 10
-- self.api_server.location_strategy = 'store_type'
-- preference = "http, swift, filesystem"
-- self.api_server.store_type_location_strategy_preference = preference
-- self.start_servers(**self.__dict__.copy())
--
-- # Create an image
-- path = self._url('/v2/images')
-- headers = self._headers({'content-type': 'application/json'})
-- data = jsonutils.dumps({'name': 'image-1', 'type': 'kernel',
-- 'foo': 'bar', 'disk_format': 'aki',
-- 'container_format': 'aki'})
-- response = requests.post(path, headers=headers, data=data)
-- self.assertEqual(201, response.status_code)
--
-- # Get the image id
-- image = jsonutils.loads(response.text)
-- image_id = image['id']
--
-- # Image locations should not be visible before location is set
-- path = self._url('/v2/images/%s' % image_id)
-- headers = self._headers({'Content-Type': 'application/json'})
-- response = requests.get(path, headers=headers)
-- self.assertEqual(200, response.status_code)
-- image = jsonutils.loads(response.text)
-- self.assertTrue('locations' in image)
-- self.assertTrue(image["locations"] == [])
--
-- # Update image locations via PATCH
-+ # Update image locations via PATCH
- path = self._url('/v2/images/%s' % image_id)
- media_type = 'application/openstack-images-v2.1-json-patch'
- headers = self._headers({'content-type': media_type})
-- values = [{'url': 'file://%s' % self.foo_image_file.name,
-- 'metadata': {'idx': '1'}},
-- {'url': 'http://127.0.0.1:%s/foo_image' % self.http_port,
-- 'metadata': {'idx': '0'}}]
-+ values = [{'url': 'http://127.0.0.1:%s/foo_image' % self.http_port0,
-+ 'metadata': {}},
-+ {'url': 'http://127.0.0.1:%s/foo_image' % self.http_port1,
-+ 'metadata': {}}]
- doc = [{'op': 'replace',
- 'path': '/locations',
- 'value': values}]
-@@ -2172,8 +2160,6 @@ class TestImageLocationSelectionStrategy(functional.FunctionalTest):
- response = requests.patch(path, headers=headers, data=data)
- self.assertEqual(200, response.status_code)
-
-- values.sort(key=lambda loc: int(loc['metadata']['idx']))
--
- # Image locations should be visible
- path = self._url('/v2/images/%s' % image_id)
- headers = self._headers({'Content-Type': 'application/json'})
-diff --git a/glance/tests/unit/test_store_image.py b/glance/tests/unit/test_store_image.py
-index 424915b..9d6cc4e 100644
---- a/glance/tests/unit/test_store_image.py
-+++ b/glance/tests/unit/test_store_image.py
-@@ -16,6 +16,7 @@ import mox
-
- from glance.common import exception
- import glance.store
-+from glance.tests.unit import base as unit_test_base
- from glance.tests.unit import utils as unit_test_utils
- from glance.tests import utils
-
-@@ -731,7 +732,7 @@ class TestStoreImageRepo(utils.BaseTestCase):
- self.assertEqual(acls['read'], [TENANT2])
-
-
--class TestImageFactory(utils.BaseTestCase):
-+class TestImageFactory(unit_test_base.StoreClearingUnitTest):
-
- def setUp(self):
- super(TestImageFactory, self).setUp()
-diff --git a/glance/tests/unit/test_store_location.py b/glance/tests/unit/test_store_location.py
-index df8d5d7..a19a33a 100644
---- a/glance/tests/unit/test_store_location.py
-+++ b/glance/tests/unit/test_store_location.py
-@@ -488,11 +488,12 @@ class TestStoreLocation(base.StoreClearingUnitTest):
- ctx,
- store)
-
-+ class FakeImageProxy():
-+ size = None
-+ context = None
-+ store_api = mock.Mock()
-+
- def test_add_location_for_image_without_size(self):
-- class FakeImageProxy():
-- size = None
-- context = None
-- store_api = mock.Mock()
-
- def fake_get_size_from_backend(context, uri):
- return 1
-@@ -504,14 +505,31 @@ class TestStoreLocation(base.StoreClearingUnitTest):
- loc2 = {'url': 'file:///fake2.img.tar.gz', 'metadata': {}}
-
- # Test for insert location
-- image1 = FakeImageProxy()
-+ image1 = TestStoreLocation.FakeImageProxy()
- locations = glance.store.StoreLocations(image1, [])
- locations.insert(0, loc2)
- self.assertEqual(image1.size, 1)
-
- # Test for set_attr of _locations_proxy
-- image2 = FakeImageProxy()
-+ image2 = TestStoreLocation.FakeImageProxy()
- locations = glance.store.StoreLocations(image2, [loc1])
- locations[0] = loc2
- self.assertTrue(loc2 in locations)
- self.assertEqual(image2.size, 1)
-+
-+ def test_add_location_with_restricted_sources(self):
-+
-+ loc1 = {'url': 'file:///fake1.img.tar.gz', 'metadata': {}}
-+ loc2 = {'url': 'swift+config:///xxx', 'metadata': {}}
-+
-+ # Test for insert location
-+ image1 = TestStoreLocation.FakeImageProxy()
-+ locations = glance.store.StoreLocations(image1, [])
-+ self.assertRaises(exception.BadStoreUri, locations.insert, 0, loc1)
-+ self.assertNotIn(loc1, locations)
-+
-+ # Test for set_attr of _locations_proxy
-+ image2 = TestStoreLocation.FakeImageProxy()
-+ locations = glance.store.StoreLocations(image2, [loc1])
-+ self.assertRaises(exception.BadStoreUri, locations.insert, 0, loc2)
-+ self.assertNotIn(loc2, locations)
-diff --git a/glance/tests/unit/utils.py b/glance/tests/unit/utils.py
-index 1c4e16a..e0dfc43 100644
---- a/glance/tests/unit/utils.py
-+++ b/glance/tests/unit/utils.py
-@@ -14,9 +14,9 @@
- # under the License.
-
- import urllib
--import urlparse
-
- from oslo.config import cfg
-+import six.moves.urllib.parse as urlparse
-
- from glance.common import exception
- from glance.common import wsgi
-@@ -113,7 +113,6 @@ class FakeDB(object):
- def __getattr__(self, key):
- return getattr(simple_db, key)
-
--
- class FakeStoreAPI(object):
- def __init__(self, store_metadata=None):
- self.data = {
-@@ -188,6 +187,12 @@ class FakeStoreAPI(object):
- def check_location_metadata(self, val, key=''):
- glance.store.check_location_metadata(val)
-
-+ def validate_external_location(self, uri):
-+ if uri and urlparse.urlparse(uri).scheme:
-+ return glance.store.validate_external_location(uri)
-+ else:
-+ return True
-+
-
- class FakePolicyEnforcer(object):
- def __init__(self, *_args, **kwargs):
-diff --git a/glance/tests/unit/v1/test_api.py b/glance/tests/unit/v1/test_api.py
-index 5618cb0..9856487 100644
---- a/glance/tests/unit/v1/test_api.py
-+++ b/glance/tests/unit/v1/test_api.py
-@@ -379,7 +379,7 @@ class TestGlanceAPI(base.IsolatedUnitTest):
-
- res = req.get_response(self.api)
- self.assertEqual(res.status_int, 400)
-- self.assertTrue('External sourcing not supported' in res.body)
-+ self.assertIn('External source are not supported', res.body)
-
- def test_create_with_location_bad_store_uri(self):
- fixture_headers = {
-@@ -962,6 +962,53 @@ class TestGlanceAPI(base.IsolatedUnitTest):
- res = req.get_response(self.api)
- self.assertEqual(res.status_int, 409)
-
-+ def test_add_location_with_invalid_location_on_conflict_image_size(self):
-+ """Tests creates an image from location and conflict image size"""
-+ fixture_headers = {'x-image-meta-store': 'file',
-+ 'x-image-meta-disk-format': 'vhd',
-+ 'x-image-meta-location': 'http://a/b/c.tar.gz',
-+ 'x-image-meta-container-format': 'ovf',
-+ 'x-image-meta-name': 'fake image #F',
-+ 'x-image-meta-size': '1'}
-+
-+ req = webob.Request.blank("/images")
-+ req.headers['Content-Type'] = 'application/octet-stream'
-+ req.method = 'POST'
-+ for k, v in fixture_headers.iteritems():
-+ req.headers[k] = v
-+ res = req.get_response(self.api)
-+ self.assertEqual(400, res.status_int)
-+
-+ def test_add_location_with_invalid_location_on_restricted_sources(self):
-+ """Tests creates an image from location and restricted sources"""
-+ fixture_headers = {'x-image-meta-store': 'file',
-+ 'x-image-meta-disk-format': 'vhd',
-+ 'x-image-meta-location': 'file:///etc/passwd',
-+ 'x-image-meta-container-format': 'ovf',
-+ 'x-image-meta-name': 'fake image #F'}
-+
-+ req = webob.Request.blank("/images")
-+ req.headers['Content-Type'] = 'application/octet-stream'
-+ req.method = 'POST'
-+ for k, v in fixture_headers.iteritems():
-+ req.headers[k] = v
-+ res = req.get_response(self.api)
-+ self.assertEqual(400, res.status_int)
-+
-+ fixture_headers = {'x-image-meta-store': 'file',
-+ 'x-image-meta-disk-format': 'vhd',
-+ 'x-image-meta-location': 'swift+config://xxx',
-+ 'x-image-meta-container-format': 'ovf',
-+ 'x-image-meta-name': 'fake image #F'}
-+
-+ req = webob.Request.blank("/images")
-+ req.headers['Content-Type'] = 'application/octet-stream'
-+ req.method = 'POST'
-+ for k, v in fixture_headers.iteritems():
-+ req.headers[k] = v
-+ res = req.get_response(self.api)
-+ self.assertEqual(400, res.status_int)
-+
- def test_add_copy_from_with_location(self):
- """Tests creates an image from copy-from and location"""
- fixture_headers = {'x-image-meta-store': 'file',
-@@ -978,6 +1025,34 @@ class TestGlanceAPI(base.IsolatedUnitTest):
- res = req.get_response(self.api)
- self.assertEqual(res.status_int, 400)
-
-+ def test_add_copy_from_with_restricted_sources(self):
-+ """Tests creates an image from copy-from with restricted sources"""
-+ fixture_headers = {'x-image-meta-store': 'file',
-+ 'x-image-meta-disk-format': 'vhd',
-+ 'x-glance-api-copy-from': 'file:///etc/passwd',
-+ 'x-image-meta-container-format': 'ovf',
-+ 'x-image-meta-name': 'fake image #F'}
-+
-+ req = webob.Request.blank("/images")
-+ req.method = 'POST'
-+ for k, v in six.iteritems(fixture_headers):
-+ req.headers[k] = v
-+ res = req.get_response(self.api)
-+ self.assertEqual(400, res.status_int)
-+
-+ fixture_headers = {'x-image-meta-store': 'file',
-+ 'x-image-meta-disk-format': 'vhd',
-+ 'x-glance-api-copy-from': 'swift+config://xxx',
-+ 'x-image-meta-container-format': 'ovf',
-+ 'x-image-meta-name': 'fake image #F'}
-+
-+ req = webob.Request.blank("/images")
-+ req.method = 'POST'
-+ for k, v in six.iteritems(fixture_headers):
-+ req.headers[k] = v
-+ res = req.get_response(self.api)
-+ self.assertEqual(400, res.status_int)
-+
- def test_add_copy_from_upload_image_unauthorized_with_body(self):
- rules = {"upload_image": '!', "modify_image": '@',
- "add_image": '@'}
diff --git a/0009-Prevent-file-swift-config-and-filesystem-schemes.patch b/0009-Prevent-file-swift-config-and-filesystem-schemes.patch
deleted file mode 100644
index 4b19967..0000000
--- a/0009-Prevent-file-swift-config-and-filesystem-schemes.patch
+++ /dev/null
@@ -1,133 +0,0 @@
-From 0e47acd351d0a4793e0cce08b1958ad6129bd3fd Mon Sep 17 00:00:00 2001
-From: Grant Murphy <grant.murphy at hp.com>
-Date: Wed, 7 Jan 2015 16:09:38 -0800
-Subject: [PATCH] Prevent file, swift+config and filesystem schemes
-
-This change ensures that 'file', 'filesystem', and 'swift+config' URI
-schemes are not allowed when setting the location field. A previous
-fix to CVE-2014-9493 attempted to address this issue but did not
-include 'filesystem', a URI scheme allowed by the glance_store.
-
-Without this fix in place it is possible for a client to access any file
-the glance-api server has read permissions for.
-
-(cherry picked from commit 5191ed1879c5fd5b2694f922bcedec232f461088)
-
-Conflicts:
- glance/common/store_utils.py
-
-Change-Id: Ib92f464331b7bab9ca49f164102313d8652dc723
-Resolves: rhbz #1174483
-Resolves: rhbz #1174484
-Upstream-Closes-Bug: #1408663
-Upstream-Kilo: https://review.openstack.org/#/c/145640/
-Upstream-Juno: https://review.openstack.org/#/c/145916/
-Upstream-Icehouse: https://review.openstack.org/#/c/145974/
-Upstream-change-Id: I02cd099a8634b9c7e3cf8f172bcbd33f8edcbc83
-Reviewed-on: https://code.engineering.redhat.com/gerrit/39653
-Reviewed-by: Jon Bernard <jobernar at redhat.com>
-Reviewed-by: Flavio Percoco <fpercoco at redhat.com>
-Tested-by: Flavio Percoco <fpercoco at redhat.com>
----
- glance/store/__init__.py | 11 +++++++----
- glance/tests/unit/test_store_location.py | 3 +++
- glance/tests/unit/v1/test_api.py | 32 ++++++++++++--------------------
- 3 files changed, 22 insertions(+), 24 deletions(-)
-
-diff --git a/glance/store/__init__.py b/glance/store/__init__.py
-index 2ae8caa..d158f97 100644
---- a/glance/store/__init__.py
-+++ b/glance/store/__init__.py
-@@ -74,6 +74,8 @@ _ALL_STORES = [
- 'glance.store.gridfs.Store',
- ]
-
-+RESTRICTED_URI_SCHEMAS = frozenset(['file', 'filesystem', 'swift+config'])
-+
-
- class BackendException(Exception):
- pass
-@@ -432,10 +434,11 @@ def validate_external_location(uri):
- :param uri: The URI of external image location.
- :return: Whether given URI of external image location are OK.
- """
-- pieces = urlparse.urlparse(uri)
-- valid_schemes = [scheme for scheme in get_known_schemes()
-- if scheme != 'file' and scheme != 'swift+config']
-- return pieces.scheme in valid_schemes
-+
-+ # TODO(gm): Use a whitelist of allowed schemes
-+ scheme = urlparse.urlparse(uri).scheme
-+ return (scheme in get_known_schemes() and
-+ scheme not in RESTRICTED_URI_SCHEMAS)
-
-
- class ImageRepoProxy(glance.domain.proxy.Repo):
-diff --git a/glance/tests/unit/test_store_location.py b/glance/tests/unit/test_store_location.py
-index a19a33a..d64b783 100644
---- a/glance/tests/unit/test_store_location.py
-+++ b/glance/tests/unit/test_store_location.py
-@@ -521,12 +521,15 @@ class TestStoreLocation(base.StoreClearingUnitTest):
-
- loc1 = {'url': 'file:///fake1.img.tar.gz', 'metadata': {}}
- loc2 = {'url': 'swift+config:///xxx', 'metadata': {}}
-+ loc3 = {'url': 'filesystem:///foo.img.tar.gz', 'metadata': {}}
-
- # Test for insert location
- image1 = TestStoreLocation.FakeImageProxy()
- locations = glance.store.StoreLocations(image1, [])
- self.assertRaises(exception.BadStoreUri, locations.insert, 0, loc1)
-+ self.assertRaises(exception.BadStoreUri, locations.insert, 0, loc3)
- self.assertNotIn(loc1, locations)
-+ self.assertNotIn(loc3, locations)
-
- # Test for set_attr of _locations_proxy
- image2 = TestStoreLocation.FakeImageProxy()
-diff --git a/glance/tests/unit/v1/test_api.py b/glance/tests/unit/v1/test_api.py
-index 9856487..33af257 100644
---- a/glance/tests/unit/v1/test_api.py
-+++ b/glance/tests/unit/v1/test_api.py
-@@ -1027,31 +1027,23 @@ class TestGlanceAPI(base.IsolatedUnitTest):
-
- def test_add_copy_from_with_restricted_sources(self):
- """Tests creates an image from copy-from with restricted sources"""
-- fixture_headers = {'x-image-meta-store': 'file',
-+ header_template = {'x-image-meta-store': 'file',
- 'x-image-meta-disk-format': 'vhd',
-- 'x-glance-api-copy-from': 'file:///etc/passwd',
- 'x-image-meta-container-format': 'ovf',
- 'x-image-meta-name': 'fake image #F'}
-
-- req = webob.Request.blank("/images")
-- req.method = 'POST'
-- for k, v in six.iteritems(fixture_headers):
-- req.headers[k] = v
-- res = req.get_response(self.api)
-- self.assertEqual(400, res.status_int)
--
-- fixture_headers = {'x-image-meta-store': 'file',
-- 'x-image-meta-disk-format': 'vhd',
-- 'x-glance-api-copy-from': 'swift+config://xxx',
-- 'x-image-meta-container-format': 'ovf',
-- 'x-image-meta-name': 'fake image #F'}
-+ schemas = ["file:///etc/passwd",
-+ "swift+config:///xxx",
-+ "filesystem:///etc/passwd"]
-
-- req = webob.Request.blank("/images")
-- req.method = 'POST'
-- for k, v in six.iteritems(fixture_headers):
-- req.headers[k] = v
-- res = req.get_response(self.api)
-- self.assertEqual(400, res.status_int)
-+ for schema in schemas:
-+ req = webob.Request.blank("/images")
-+ req.method = 'POST'
-+ for k, v in six.iteritems(header_template):
-+ req.headers[k] = v
-+ req.headers['x-glance-api-copy-from'] = schema
-+ res = req.get_response(self.api)
-+ self.assertEqual(400, res.status_int)
-
- def test_add_copy_from_upload_image_unauthorized_with_body(self):
- rules = {"upload_image": '!', "modify_image": '@',
diff --git a/0010-Cleanup-chunks-for-deleted-image-that-was-saving.patch b/0010-Cleanup-chunks-for-deleted-image-that-was-saving.patch
deleted file mode 100644
index 51817e3..0000000
--- a/0010-Cleanup-chunks-for-deleted-image-that-was-saving.patch
+++ /dev/null
@@ -1,500 +0,0 @@
-From cbec3b5c4e42ae61d14dda0aaae93c36ba5bfde5 Mon Sep 17 00:00:00 2001
-From: Zhi Yan Liu <zhiyanl at cn.ibm.com>
-Date: Tue, 30 Dec 2014 22:25:50 +0800
-Subject: [PATCH] Cleanup chunks for deleted image that was 'saving'
-
-Currently image data cannot be removed synchronously for an image that
-is in saving state. And when, the upload operation for such an image is
-completed the operator configured quota can be exceeded.
-This patch fixes the issue of left over chunks for an image which was
-deleted from saving status. However, by the limitation of the design we
-cannot enforce a global quota check for the image in saving status.
-This change introduces a inconsonance between http response codes of
-v1 and v2 APIs. The status codes which we will now see after the upload
-process completes on an image which was deleted mid way are:
-
-v1: 412 Precondition Failed
-v2: 410 Gone
-
-SecurityImpact
-UpgradeImpact
-APIImpact
-
-Closes-Bug: 1383973
-Closes-Bug: 1398830
-Closes-Bug: 1188532
-
-Conflicts:
- glance/api/v1/upload_utils.py
- glance/api/v2/image_data.py
- glance/quota/__init__.py
- glance/tests/unit/test_domain_proxy.py
- glance/tests/unit/test_quota.py
- glance/tests/unit/v1/test_api.py
-
-Signed-off-by: Zhi Yan Liu <zhiyanl at cn.ibm.com>
-(cherry picked from commit 0dc8fbb3479a53c5bba8475d14f4c7206904c5ea)
-
-Change-Id: I47229b366c25367ec1bd48aec684e0880f3dfe60
-(cherry picked from commit f1260cc771ee068651aa62b972bef49d9af81eb0)
----
- glance/api/authorization.py | 4 +-
- glance/api/policy.py | 8 ++--
- glance/api/v1/upload_utils.py | 23 +++++++----
- glance/api/v2/image_data.py | 18 +++++----
- glance/db/__init__.py | 7 ++--
- glance/domain/proxy.py | 4 +-
- glance/notifier.py | 4 +-
- glance/quota/__init__.py | 4 +-
- glance/store/__init__.py | 2 +-
- glance/tests/unit/test_domain_proxy.py | 14 ++++---
- glance/tests/unit/test_policy.py | 2 +-
- glance/tests/unit/test_quota.py | 6 ++-
- glance/tests/unit/test_store_image.py | 2 +-
- glance/tests/unit/v1/test_api.py | 51 +++++++++++-------------
- glance/tests/unit/v2/test_image_data_resource.py | 24 ++++++-----
- 15 files changed, 95 insertions(+), 78 deletions(-)
-
-diff --git a/glance/api/authorization.py b/glance/api/authorization.py
-index 3a1045a..0b66af6 100644
---- a/glance/api/authorization.py
-+++ b/glance/api/authorization.py
-@@ -147,10 +147,10 @@ class ImageMemberRepoProxy(glance.domain.proxy.Repo):
- raise exception.Forbidden(message
- % self.image.image_id)
-
-- def save(self, image_member):
-+ def save(self, image_member, from_state=None):
- if (self.context.is_admin or
- self.context.owner == image_member.member_id):
-- self.member_repo.save(image_member)
-+ self.member_repo.save(image_member, from_state=from_state)
- else:
- message = _("You cannot update image member %s")
- raise exception.Forbidden(message % image_member.member_id)
-diff --git a/glance/api/policy.py b/glance/api/policy.py
-index 22a561d..1838395 100644
---- a/glance/api/policy.py
-+++ b/glance/api/policy.py
-@@ -182,9 +182,9 @@ class ImageRepoProxy(glance.domain.proxy.Repo):
- self.policy.enforce(self.context, 'get_images', {})
- return super(ImageRepoProxy, self).list(*args, **kwargs)
-
-- def save(self, image):
-+ def save(self, image, from_state=None):
- self.policy.enforce(self.context, 'modify_image', {})
-- return super(ImageRepoProxy, self).save(image)
-+ return super(ImageRepoProxy, self).save(image, from_state=from_state)
-
- def add(self, image):
- self.policy.enforce(self.context, 'add_image', {})
-@@ -283,9 +283,9 @@ class ImageMemberRepoProxy(glance.domain.proxy.Repo):
- self.policy.enforce(self.context, 'get_member', {})
- return self.member_repo.get(member_id)
-
-- def save(self, member):
-+ def save(self, member, from_state=None):
- self.policy.enforce(self.context, 'modify_member', {})
-- self.member_repo.save(member)
-+ self.member_repo.save(member, from_state=from_state)
-
- def list(self, *args, **kwargs):
- self.policy.enforce(self.context, 'get_members', {})
-diff --git a/glance/api/v1/upload_utils.py b/glance/api/v1/upload_utils.py
-index 8fcf766..f7e980b 100644
---- a/glance/api/v1/upload_utils.py
-+++ b/glance/api/v1/upload_utils.py
-@@ -146,14 +146,21 @@ def upload_data_to_store(req, image_meta, image_data, store, notifier):
- update_data = {'checksum': checksum,
- 'size': size}
- try:
-- image_meta = registry.update_image_metadata(req.context,
-- image_id,
-- update_data,
-- from_state='saving')
--
-- except exception.NotFound as e:
-- msg = _("Image %s could not be found after upload. The image may "
-- "have been deleted during the upload.") % image_id
-+ try:
-+ state = 'saving'
-+ image_meta = registry.update_image_metadata(req.context,
-+ image_id,
-+ update_data,
-+ from_state=state)
-+ except exception.Duplicate:
-+ image = registry.get_image_metadata(req.context, image_id)
-+ if image['status'] == 'deleted':
-+ raise exception.NotFound()
-+ else:
-+ raise
-+ except exception.NotFound:
-+ msg = _("Image %s could not be found after upload. The image may"
-+ " have been deleted during the upload.") % image_id
- LOG.info(msg)
-
- # NOTE(jculp): we need to clean up the datastore if an image
-diff --git a/glance/api/v2/image_data.py b/glance/api/v2/image_data.py
-index 1bff3d7..6253608 100644
---- a/glance/api/v2/image_data.py
-+++ b/glance/api/v2/image_data.py
-@@ -22,6 +22,7 @@ from glance.common import wsgi
- import glance.db
- import glance.gateway
- import glance.notifier
-+from glance.openstack.common import excutils
- import glance.openstack.common.log as logging
- import glance.store
-
-@@ -66,13 +67,12 @@ class ImageDataController(object):
- try:
- image_repo.save(image)
- image.set_data(data, size)
-- image_repo.save(image)
-- except exception.NotFound as e:
-- msg = (_("Image %(id)s could not be found after upload."
-- "The image may have been deleted during the upload: "
-- "%(error)s Cleaning up the chunks uploaded") %
-- {'id': image_id,
-- 'error': e})
-+ image_repo.save(image, from_state='saving')
-+ except (exception.NotFound, exception.Conflict):
-+ msg = (_("Image %s could not be found after upload. "
-+ "The image may have been deleted during the "
-+ "upload, cleaning up the chunks uploaded.") %
-+ image_id)
- LOG.warn(msg)
- # NOTE(sridevi): Cleaning up the uploaded chunks.
- try:
-@@ -131,6 +131,10 @@ class ImageDataController(object):
- raise webob.exc.HTTPServiceUnavailable(explanation=msg,
- request=req)
-
-+ except webob.exc.HTTPGone as e:
-+ with excutils.save_and_reraise_exception():
-+ LOG.error(_("Failed to upload image data due to HTTP error"))
-+
- except webob.exc.HTTPError as e:
- LOG.error(_("Failed to upload image data due to HTTP error"))
- self._restore(image_repo, image)
-diff --git a/glance/db/__init__.py b/glance/db/__init__.py
-index a59447d..379cf6f 100644
---- a/glance/db/__init__.py
-+++ b/glance/db/__init__.py
-@@ -162,7 +162,7 @@ class ImageRepo(object):
- image.created_at = new_values['created_at']
- image.updated_at = new_values['updated_at']
-
-- def save(self, image):
-+ def save(self, image, from_state=None):
- image_values = self._format_image_to_db(image)
- if image_values['size'] > CONF.image_size_cap:
- raise exception.ImageSizeLimitExceeded
-@@ -170,7 +170,8 @@ class ImageRepo(object):
- new_values = self.db_api.image_update(self.context,
- image.image_id,
- image_values,
-- purge_props=True)
-+ purge_props=True,
-+ from_state=from_state)
- except (exception.NotFound, exception.Forbidden):
- msg = _("No image found with ID %s") % image.image_id
- raise exception.NotFound(msg)
-@@ -263,7 +264,7 @@ class ImageMemberRepo(object):
- msg = _("The specified member %s could not be found")
- raise exception.NotFound(msg % image_member.id)
-
-- def save(self, image_member):
-+ def save(self, image_member, from_state=None):
- image_member_values = self._format_image_member_to_db(image_member)
- try:
- new_values = self.db_api.image_member_update(self.context,
-diff --git a/glance/domain/proxy.py b/glance/domain/proxy.py
-index 89f138c..b27b448 100644
---- a/glance/domain/proxy.py
-+++ b/glance/domain/proxy.py
-@@ -94,9 +94,9 @@ class Repo(object):
- result = self.base.add(base_item)
- return self.helper.proxy(result)
-
-- def save(self, item):
-+ def save(self, item, from_state=None):
- base_item = self.helper.unproxy(item)
-- result = self.base.save(base_item)
-+ result = self.base.save(base_item, from_state=from_state)
- return self.helper.proxy(result)
-
- def remove(self, item):
-diff --git a/glance/notifier.py b/glance/notifier.py
-index 1eaadbf..1d27c07 100644
---- a/glance/notifier.py
-+++ b/glance/notifier.py
-@@ -178,8 +178,8 @@ class ImageRepoProxy(glance.domain.proxy.Repo):
- item_proxy_class=ImageProxy,
- item_proxy_kwargs=proxy_kwargs)
-
-- def save(self, image):
-- super(ImageRepoProxy, self).save(image)
-+ def save(self, image, from_state=None):
-+ super(ImageRepoProxy, self).save(image, from_state=from_state)
- self.notifier.info('image.update',
- format_image_notification(image))
-
-diff --git a/glance/quota/__init__.py b/glance/quota/__init__.py
-index 0fcc792..a8babe1 100644
---- a/glance/quota/__init__.py
-+++ b/glance/quota/__init__.py
-@@ -96,9 +96,9 @@ class ImageRepoProxy(glance.domain.proxy.Repo):
- raise exception.ImagePropertyLimitExceeded(attempted=attempted,
- maximum=maximum)
-
-- def save(self, image):
-+ def save(self, image, from_state=None):
- self._enforce_image_property_quota(image)
-- super(ImageRepoProxy, self).save(image)
-+ return super(ImageRepoProxy, self).save(image, from_state=from_state)
-
- def add(self, image):
- self._enforce_image_property_quota(image)
-diff --git a/glance/store/__init__.py b/glance/store/__init__.py
-index d158f97..25739ff 100644
---- a/glance/store/__init__.py
-+++ b/glance/store/__init__.py
-@@ -466,7 +466,7 @@ class ImageRepoProxy(glance.domain.proxy.Repo):
- self._set_acls(image)
- return result
-
-- def save(self, image):
-+ def save(self, image, from_state=None):
- result = super(ImageRepoProxy, self).save(image)
- self._set_acls(image)
- return result
-diff --git a/glance/tests/unit/test_domain_proxy.py b/glance/tests/unit/test_domain_proxy.py
-index 86684fc..4260b2f 100644
---- a/glance/tests/unit/test_domain_proxy.py
-+++ b/glance/tests/unit/test_domain_proxy.py
-@@ -74,7 +74,7 @@ class TestProxyRepoPlain(test_utils.BaseTestCase):
- self._test_method('add', 'snuff', 'enough')
-
- def test_save(self):
-- self._test_method('save', 'snuff', 'enough')
-+ self._test_method('save', 'snuff', 'enough', from_state=None)
-
- def test_remove(self):
- self._test_method('add', None, 'flying')
-@@ -121,14 +121,14 @@ class TestProxyRepoWrapping(test_utils.BaseTestCase):
- self.assertEqual(results[i].args, tuple())
- self.assertEqual(results[i].kwargs, {'a': 1})
-
-- def _test_method_with_proxied_argument(self, name, result):
-+ def _test_method_with_proxied_argument(self, name, result, **kwargs):
- self.fake_repo.result = result
- item = FakeProxy('snoop')
- method = getattr(self.proxy_repo, name)
- proxy_result = method(item)
-
-- self.assertEqual(self.fake_repo.args, ('snoop',))
-- self.assertEqual(self.fake_repo.kwargs, {})
-+ self.assertEqual(('snoop',), self.fake_repo.args)
-+ self.assertEqual(kwargs, self.fake_repo.kwargs)
-
- if result is None:
- self.assertTrue(proxy_result is None)
-@@ -145,10 +145,12 @@ class TestProxyRepoWrapping(test_utils.BaseTestCase):
- self._test_method_with_proxied_argument('add', None)
-
- def test_save(self):
-- self._test_method_with_proxied_argument('save', 'dog')
-+ self._test_method_with_proxied_argument('save', 'dog',
-+ from_state=None)
-
- def test_save_with_no_result(self):
-- self._test_method_with_proxied_argument('save', None)
-+ self._test_method_with_proxied_argument('save', None,
-+ from_state=None)
-
- def test_remove(self):
- self._test_method_with_proxied_argument('remove', 'dog')
-diff --git a/glance/tests/unit/test_policy.py b/glance/tests/unit/test_policy.py
-index 6306f1b..0b51e8e 100644
---- a/glance/tests/unit/test_policy.py
-+++ b/glance/tests/unit/test_policy.py
-@@ -69,7 +69,7 @@ class MemberRepoStub(object):
- def get(self, *args, **kwargs):
- return 'member_repo_get'
-
-- def save(self, image_member):
-+ def save(self, image_member, from_state=None):
- image_member.output = 'member_repo_save'
-
- def list(self, *args, **kwargs):
-diff --git a/glance/tests/unit/test_quota.py b/glance/tests/unit/test_quota.py
-index 504cefd..a66b0ac 100644
---- a/glance/tests/unit/test_quota.py
-+++ b/glance/tests/unit/test_quota.py
-@@ -290,7 +290,8 @@ class TestImagePropertyQuotas(test_utils.BaseTestCase):
- self.image.extra_properties = {'foo': 'bar'}
- self.image_repo_proxy.save(self.image)
-
-- self.image_repo_mock.save.assert_called_once_with(self.base_image)
-+ self.image_repo_mock.save.assert_called_once_with(self.base_image,
-+ from_state=None)
-
- def test_save_image_too_many_image_properties(self):
- self.config(image_property_quota=1)
-@@ -306,7 +307,8 @@ class TestImagePropertyQuotas(test_utils.BaseTestCase):
- self.image.extra_properties = {'foo': 'bar'}
- self.image_repo_proxy.save(self.image)
-
-- self.image_repo_mock.save.assert_called_once_with(self.base_image)
-+ self.image_repo_mock.save.assert_called_once_with(self.base_image,
-+ from_state=None)
-
- def test_add_image_with_image_property(self):
- self.config(image_property_quota=1)
-diff --git a/glance/tests/unit/test_store_image.py b/glance/tests/unit/test_store_image.py
-index 9d6cc4e..8858e5d 100644
---- a/glance/tests/unit/test_store_image.py
-+++ b/glance/tests/unit/test_store_image.py
-@@ -34,7 +34,7 @@ class ImageRepoStub(object):
- def add(self, image):
- return image
-
-- def save(self, image):
-+ def save(self, image, from_state=None):
- return image
-
-
-diff --git a/glance/tests/unit/v1/test_api.py b/glance/tests/unit/v1/test_api.py
-index 33af257..8034682 100644
---- a/glance/tests/unit/v1/test_api.py
-+++ b/glance/tests/unit/v1/test_api.py
-@@ -1650,8 +1650,7 @@ class TestGlanceAPI(base.IsolatedUnitTest):
-
- self.assertEqual(1, mock_store_add_to_backend.call_count)
-
-- def test_delete_during_image_upload(self):
-- req = unit_test_utils.get_fake_request()
-+ def _check_delete_during_image_upload(self, is_admin=False):
-
- fixture_headers = {'x-image-meta-store': 'file',
- 'x-image-meta-disk-format': 'vhd',
-@@ -1685,30 +1684,18 @@ class TestGlanceAPI(base.IsolatedUnitTest):
- mock_initiate_deletion)
-
- orig_update_image_metadata = registry.update_image_metadata
-- ctlr = glance.api.v1.controller.BaseController
-- orig_get_image_meta_or_404 = ctlr.get_image_meta_or_404
-
-- def mock_update_image_metadata(*args, **kwargs):
--
-- if args[2].get('status', None) == 'deleted':
-+ data = "somedata"
-
-- # One shot.
-- def mock_get_image_meta_or_404(*args, **kwargs):
-- ret = orig_get_image_meta_or_404(*args, **kwargs)
-- ret['status'] = 'queued'
-- self.stubs.Set(ctlr, 'get_image_meta_or_404',
-- orig_get_image_meta_or_404)
-- return ret
--
-- self.stubs.Set(ctlr, 'get_image_meta_or_404',
-- mock_get_image_meta_or_404)
-+ def mock_update_image_metadata(*args, **kwargs):
-
-- req = webob.Request.blank("/images/%s" % image_id)
-- req.method = 'PUT'
-- req.headers['Content-Type'] = 'application/octet-stream'
-- req.body = "somedata"
-+ if args[2].get('size', None) == len(data):
-+ path = "/images/%s" % image_id
-+ req = unit_test_utils.get_fake_request(path=path,
-+ method='DELETE',
-+ is_admin=is_admin)
- res = req.get_response(self.api)
-- self.assertEqual(res.status_int, 200)
-+ self.assertEqual(200, res.status_int)
-
- self.stubs.Set(registry, 'update_image_metadata',
- orig_update_image_metadata)
-@@ -1718,20 +1705,30 @@ class TestGlanceAPI(base.IsolatedUnitTest):
- self.stubs.Set(registry, 'update_image_metadata',
- mock_update_image_metadata)
-
-- req = webob.Request.blank("/images/%s" % image_id)
-- req.method = 'DELETE'
-+ req = unit_test_utils.get_fake_request(path="/images/%s" % image_id,
-+ method='PUT')
-+ req.headers['Content-Type'] = 'application/octet-stream'
-+ req.body = data
- res = req.get_response(self.api)
-- self.assertEqual(res.status_int, 200)
-+ self.assertEqual(412, res.status_int)
-+ self.assertFalse(res.location)
-
- self.assertTrue(called['initiate_deletion'])
-
-- req = webob.Request.blank("/images/%s" % image_id)
-- req.method = 'HEAD'
-+ req = unit_test_utils.get_fake_request(path="/images/%s" % image_id,
-+ method='HEAD',
-+ is_admin=True)
- res = req.get_response(self.api)
- self.assertEqual(res.status_int, 200)
- self.assertEqual(res.headers['x-image-meta-deleted'], 'True')
- self.assertEqual(res.headers['x-image-meta-status'], 'deleted')
-
-+ def test_delete_during_image_upload_by_normal_user(self):
-+ self._check_delete_during_image_upload(is_admin=False)
-+
-+ def test_delete_during_image_upload_by_admin(self):
-+ self._check_delete_during_image_upload(is_admin=True)
-+
- def test_disable_purge_props(self):
- """
- Test the special x-glance-registry-purge-props header controls
-diff --git a/glance/tests/unit/v2/test_image_data_resource.py b/glance/tests/unit/v2/test_image_data_resource.py
-index 0dfb55e..19fb206 100644
---- a/glance/tests/unit/v2/test_image_data_resource.py
-+++ b/glance/tests/unit/v2/test_image_data_resource.py
-@@ -79,7 +79,7 @@ class FakeImageRepo(object):
- else:
- return self.result
-
-- def save(self, image):
-+ def save(self, image, from_state=None):
- self.saved_image = image
-
-
-@@ -180,17 +180,21 @@ class TestImagesController(base.StoreClearingUnitTest):
- request, unit_test_utils.UUID1, 'YYYY', 4)
-
- def test_upload_non_existent_image_during_save_initiates_deletion(self):
-- def fake_save(self):
-+ def fake_save_not_found(self):
- raise exception.NotFound()
-
-- request = unit_test_utils.get_fake_request()
-- image = FakeImage('abcd', locations=['http://example.com/image'])
-- self.image_repo.result = image
-- self.image_repo.save = fake_save
-- image.delete = mock.Mock()
-- self.assertRaises(webob.exc.HTTPGone, self.controller.upload,
-- request, str(uuid.uuid4()), 'ABC', 3)
-- self.assertTrue(image.delete.called)
-+ def fake_save_conflict(self):
-+ raise exception.Conflict()
-+
-+ for fun in [fake_save_not_found, fake_save_conflict]:
-+ request = unit_test_utils.get_fake_request()
-+ image = FakeImage('abcd', locations=['http://example.com/image'])
-+ self.image_repo.result = image
-+ self.image_repo.save = fun
-+ image.delete = mock.Mock()
-+ self.assertRaises(webob.exc.HTTPGone, self.controller.upload,
-+ request, str(uuid.uuid4()), 'ABC', 3)
-+ self.assertTrue(image.delete.called)
-
- def test_upload_non_existent_image_before_save(self):
- request = unit_test_utils.get_fake_request()
diff --git a/openstack-glance.spec b/openstack-glance.spec
index 1d5d91d..4f78794 100644
--- a/openstack-glance.spec
+++ b/openstack-glance.spec
@@ -1,6 +1,6 @@
Name: openstack-glance
-Version: 2014.1.3
-Release: 4%{?dist}
+Version: 2014.1.4
+Release: 1%{?dist}
Summary: OpenStack Image Service
Group: Applications/System
@@ -25,19 +25,10 @@ Source11: openstack-glance-scrubber.init
Source1100: openstack-glance-scrubber.upstart
Source9999: daemon_notify.sh
-#
-# patches_base=2014.1.3
-#
Patch0001: 0001-Remove-runtime-dep-on-python-pbr.patch
Patch0002: 0002-Don-t-access-the-net-while-building-docs.patch
Patch0003: 0003-avoid-unsupported-storage-drivers.patch
Patch0004: 0004-notify-calling-process-we-are-ready-to-serve.patch
-Patch0005: 0005-Make-rbd-store-s-pool-handling-more-universal.patch
-Patch0006: 0006-To-prevent-client-use-v2-patch-api-to-handle-file-an.patch
-Patch0007: 0007-Revert-To-prevent-client-use-v2-patch-api-to-handle-.patch
-Patch0008: 0008-To-prevent-client-use-v2-patch-api-to-handle-file-an.patch
-Patch0009: 0009-Prevent-file-swift-config-and-filesystem-schemes.patch
-Patch0010: 0010-Cleanup-chunks-for-deleted-image-that-was-saving.patch
BuildArch: noarch
BuildRequires: python2-devel
@@ -140,12 +131,6 @@ This package contains documentation files for glance.
%patch0002 -p1
%patch0003 -p1
%patch0004 -p1
-%patch0005 -p1
-%patch0006 -p1
-%patch0007 -p1
-%patch0008 -p1
-%patch0009 -p1
-%patch0010 -p1
# Remove bundled egg-info
rm -rf glance.egg-info
@@ -397,6 +382,9 @@ fi
%doc doc/build/html
%changelog
+* Thu Mar 26 2015 Haikel Guemar <hguemar at fedoraproject.org> - 2014.1.4-1
+- Update to upstream 2014.1.4
+
* Thu Jan 29 2015 Haïkel Guémar <hguemar at fedoraproject.org> - 2014.1.3-4
- Usage storage quota bypass - CVE-2014-9623 (RHBZ #1187003)
diff --git a/sources b/sources
index c03b834..bad31ce 100644
--- a/sources
+++ b/sources
@@ -1 +1 @@
-e7850ae017d6bf0dd53bdc78cff46f17 glance-2014.1.3.tar.gz
+d9937594e9a0c478f989bfa6ad1014cd glance-2014.1.4.tar.gz
--
cgit v0.10.2
http://pkgs.fedoraproject.org/cgit/openstack-glance.git/commit/?h=f21&id=3bdbdafa726555e9b2059cac8898c1526c473f22
More information about the scm-commits
mailing list