summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuul <zuul@review.openstack.org>2018-11-12 06:50:23 +0000
committerGerrit Code Review <review@openstack.org>2018-11-12 06:50:23 +0000
commitd2e62d43975dab346fb65abf5bcb2d38de06d2e0 (patch)
treeee5bb7f427cb9117fc175e1de1bfef4dce9c807b
parent97dac0f3800ad9768e5c118656c6bbbf55fc866d (diff)
parentb25a80fcd43d81c63fe88218e1c7819a73d86a8b (diff)
Merge "Embed validation data in locations"HEADmaster
-rw-r--r--glance/api/v2/images.py110
-rw-r--r--glance/tests/unit/v2/test_images_resource.py347
2 files changed, 439 insertions, 18 deletions
diff --git a/glance/api/v2/images.py b/glance/api/v2/images.py
index c005546..b0fb908 100644
--- a/glance/api/v2/images.py
+++ b/glance/api/v2/images.py
@@ -13,6 +13,7 @@
13# License for the specific language governing permissions and limitations 13# License for the specific language governing permissions and limitations
14# under the License. 14# under the License.
15 15
16import hashlib
16import re 17import re
17 18
18import glance_store 19import glance_store
@@ -45,6 +46,7 @@ CONF.import_opt('disk_formats', 'glance.common.config', group='image_format')
45CONF.import_opt('container_formats', 'glance.common.config', 46CONF.import_opt('container_formats', 'glance.common.config',
46 group='image_format') 47 group='image_format')
47CONF.import_opt('show_multiple_locations', 'glance.common.config') 48CONF.import_opt('show_multiple_locations', 'glance.common.config')
49CONF.import_opt('hashing_algorithm', 'glance.common.config')
48 50
49 51
50class ImagesController(object): 52class ImagesController(object):
@@ -364,6 +366,76 @@ class ImagesController(object):
364 except exception.NotAuthenticated as e: 366 except exception.NotAuthenticated as e:
365 raise webob.exc.HTTPUnauthorized(explanation=e.msg) 367 raise webob.exc.HTTPUnauthorized(explanation=e.msg)
366 368
369 def _validate_validation_data(self, image, locations):
370 val_data = {}
371 for loc in locations:
372 if 'validation_data' not in loc:
373 continue
374 for k, v in loc['validation_data'].items():
375 if val_data.get(k, v) != v:
376 msg = _("Conflicting values for %s") % k
377 raise webob.exc.HTTPConflict(explanation=msg)
378 val_data[k] = v
379
380 # NOTE(imacdonn): values may be provided for items which are
381 # already set, so long as the values exactly match. In this
382 # case, nothing actually needs to be updated, but we should
383 # reject the request if there's an apparent attempt to supply
384 # a different value.
385 new_val_data = {}
386 for k, v in val_data.items():
387 current = getattr(image, k)
388 if v == current:
389 continue
390 if current:
391 msg = _("%s is already set with a different value") % k
392 raise webob.exc.HTTPConflict(explanation=msg)
393 new_val_data[k] = v
394
395 if not new_val_data:
396 return {}
397
398 if image.status != 'queued':
399 msg = _("New value(s) for %s may only be provided when image "
400 "status is 'queued'") % ', '.join(new_val_data.keys())
401 raise webob.exc.HTTPConflict(explanation=msg)
402
403 if 'checksum' in new_val_data:
404 try:
405 checksum_bytes = bytearray.fromhex(new_val_data['checksum'])
406 except ValueError:
407 msg = (_("checksum (%s) is not a valid hexadecimal value") %
408 new_val_data['checksum'])
409 raise webob.exc.HTTPConflict(explanation=msg)
410 if len(checksum_bytes) != 16:
411 msg = (_("checksum (%s) is not the correct size for md5 "
412 "(should be 16 bytes)") %
413 new_val_data['checksum'])
414 raise webob.exc.HTTPConflict(explanation=msg)
415
416 hash_algo = new_val_data.get('os_hash_algo')
417 if hash_algo != CONF['hashing_algorithm']:
418 msg = (_("os_hash_algo must be %(want)s, not %(got)s") %
419 {'want': CONF['hashing_algorithm'], 'got': hash_algo})
420 raise webob.exc.HTTPConflict(explanation=msg)
421
422 try:
423 hash_bytes = bytearray.fromhex(new_val_data['os_hash_value'])
424 except ValueError:
425 msg = (_("os_hash_value (%s) is not a valid hexadecimal value") %
426 new_val_data['os_hash_value'])
427 raise webob.exc.HTTPConflict(explanation=msg)
428 want_size = hashlib.new(hash_algo).digest_size
429 if len(hash_bytes) != want_size:
430 msg = (_("os_hash_value (%(value)s) is not the correct size for "
431 "%(algo)s (should be %(want)d bytes)") %
432 {'value': new_val_data['os_hash_value'],
433 'algo': hash_algo,
434 'want': want_size})
435 raise webob.exc.HTTPConflict(explanation=msg)
436
437 return new_val_data
438
367 def _get_locations_op_pos(self, path_pos, max_pos, allow_max): 439 def _get_locations_op_pos(self, path_pos, max_pos, allow_max):
368 if path_pos is None or max_pos is None: 440 if path_pos is None or max_pos is None:
369 return None 441 return None
@@ -387,11 +459,15 @@ class ImagesController(object):
387 "%s.") % image.status 459 "%s.") % image.status
388 raise webob.exc.HTTPConflict(explanation=msg) 460 raise webob.exc.HTTPConflict(explanation=msg)
389 461
462 val_data = self._validate_validation_data(image, value)
463
390 try: 464 try:
391 # NOTE(flwang): _locations_proxy's setattr method will check if 465 # NOTE(flwang): _locations_proxy's setattr method will check if
392 # the update is acceptable. 466 # the update is acceptable.
393 image.locations = value 467 image.locations = value
394 if image.status == 'queued': 468 if image.status == 'queued':
469 for k, v in val_data.items():
470 setattr(image, k, v)
395 image.status = 'active' 471 image.status = 'active'
396 except (exception.BadStoreUri, exception.DuplicateLocation) as e: 472 except (exception.BadStoreUri, exception.DuplicateLocation) as e:
397 raise webob.exc.HTTPBadRequest(explanation=e.msg) 473 raise webob.exc.HTTPBadRequest(explanation=e.msg)
@@ -410,6 +486,8 @@ class ImagesController(object):
410 "%s.") % image.status 486 "%s.") % image.status
411 raise webob.exc.HTTPConflict(explanation=msg) 487 raise webob.exc.HTTPConflict(explanation=msg)
412 488
489 val_data = self._validate_validation_data(image, [value])
490
413 pos = self._get_locations_op_pos(path_pos, 491 pos = self._get_locations_op_pos(path_pos,
414 len(image.locations), True) 492 len(image.locations), True)
415 if pos is None: 493 if pos is None:
@@ -418,6 +496,8 @@ class ImagesController(object):
418 try: 496 try:
419 image.locations.insert(pos, value) 497 image.locations.insert(pos, value)
420 if image.status == 'queued': 498 if image.status == 'queued':
499 for k, v in val_data.items():
500 setattr(image, k, v)
421 image.status = 'active' 501 image.status = 'active'
422 except (exception.BadStoreUri, exception.DuplicateLocation) as e: 502 except (exception.BadStoreUri, exception.DuplicateLocation) as e:
423 raise webob.exc.HTTPBadRequest(explanation=e.msg) 503 raise webob.exc.HTTPBadRequest(explanation=e.msg)
@@ -1164,6 +1244,36 @@ def get_base_properties():
1164 'metadata': { 1244 'metadata': {
1165 'type': 'object', 1245 'type': 'object',
1166 }, 1246 },
1247 'validation_data': {
1248 'description': _(
1249 'Values to be used to populate the corresponding '
1250 'image properties. If the image status is not '
1251 '\'queued\', values must exactly match those '
1252 'already contained in the image properties.'
1253 ),
1254 'type': 'object',
1255 'writeOnly': True,
1256 'additionalProperties': False,
1257 'properties': {
1258 'checksum': {
1259 'type': 'string',
1260 'minLength': 32,
1261 'maxLength': 32,
1262 },
1263 'os_hash_algo': {
1264 'type': 'string',
1265 'maxLength': 64,
1266 },
1267 'os_hash_value': {
1268 'type': 'string',
1269 'maxLength': 128,
1270 },
1271 },
1272 'required': [
1273 'os_hash_algo',
1274 'os_hash_value',
1275 ],
1276 },
1167 }, 1277 },
1168 'required': ['url', 'metadata'], 1278 'required': ['url', 'metadata'],
1169 }, 1279 },
diff --git a/glance/tests/unit/v2/test_images_resource.py b/glance/tests/unit/v2/test_images_resource.py
index 30379d4..45b7951 100644
--- a/glance/tests/unit/v2/test_images_resource.py
+++ b/glance/tests/unit/v2/test_images_resource.py
@@ -1725,33 +1725,131 @@ class TestImagesController(base.IsolatedUnitTest):
1725 @mock.patch.object(glance.location.ImageRepoProxy, '_set_acls') 1725 @mock.patch.object(glance.location.ImageRepoProxy, '_set_acls')
1726 @mock.patch.object(store, 'get_size_from_uri_and_backend') 1726 @mock.patch.object(store, 'get_size_from_uri_and_backend')
1727 @mock.patch.object(store, 'get_size_from_backend') 1727 @mock.patch.object(store, 'get_size_from_backend')
1728 def test_replace_location_on_queued(self, 1728 def test_replace_locations_on_queued(self,
1729 mock_get_size, 1729 mock_get_size,
1730 mock_get_size_uri, 1730 mock_get_size_uri,
1731 mock_set_acls, 1731 mock_set_acls,
1732 mock_check_loc, 1732 mock_check_loc,
1733 mock_calc): 1733 mock_calc):
1734 mock_calc.return_value = 1 1734 mock_calc.return_value = 1
1735 mock_get_size.return_value = 1 1735 mock_get_size.return_value = 1
1736 mock_get_size_uri.return_value = 1 1736 mock_get_size_uri.return_value = 1
1737 self.config(show_multiple_locations=True) 1737 self.config(show_multiple_locations=True)
1738 image_id = str(uuid.uuid4())
1738 self.images = [ 1739 self.images = [
1739 _db_fixture('1', owner=TENANT1, checksum=CHKSUM, 1740 _db_fixture(image_id, owner=TENANT1,
1740 name='1', 1741 name='1',
1741 disk_format='raw', 1742 disk_format='raw',
1742 container_format='bare', 1743 container_format='bare',
1743 status='queued'), 1744 status='queued',
1745 checksum=None,
1746 os_hash_algo=None,
1747 os_hash_value=None),
1744 ] 1748 ]
1745 self.db.image_create(None, self.images[0]) 1749 self.db.image_create(None, self.images[0])
1746 request = unit_test_utils.get_fake_request() 1750 request = unit_test_utils.get_fake_request()
1747 new_location = {'url': '%s/fake_location_1' % BASE_URI, 'metadata': {}} 1751 new_location1 = {'url': '%s/fake_location_1' % BASE_URI,
1752 'metadata': {},
1753 'validation_data': {'checksum': CHKSUM,
1754 'os_hash_algo': 'sha512',
1755 'os_hash_value': MULTIHASH1}}
1756 new_location2 = {'url': '%s/fake_location_2' % BASE_URI,
1757 'metadata': {},
1758 'validation_data': {'checksum': CHKSUM,
1759 'os_hash_algo': 'sha512',
1760 'os_hash_value': MULTIHASH1}}
1748 changes = [{'op': 'replace', 'path': ['locations'], 1761 changes = [{'op': 'replace', 'path': ['locations'],
1749 'value': [new_location]}] 1762 'value': [new_location1, new_location2]}]
1750 output = self.controller.update(request, '1', changes) 1763 output = self.controller.update(request, image_id, changes)
1751 self.assertEqual('1', output.image_id) 1764 self.assertEqual(image_id, output.image_id)
1752 self.assertEqual(1, len(output.locations)) 1765 self.assertEqual(2, len(output.locations))
1753 self.assertEqual(new_location, output.locations[0]) 1766 self.assertEqual(new_location1['url'], output.locations[0]['url'])
1767 self.assertEqual(new_location2['url'], output.locations[1]['url'])
1754 self.assertEqual('active', output.status) 1768 self.assertEqual('active', output.status)
1769 self.assertEqual(CHKSUM, output.checksum)
1770 self.assertEqual('sha512', output.os_hash_algo)
1771 self.assertEqual(MULTIHASH1, output.os_hash_value)
1772
1773 @mock.patch.object(glance.quota, '_calc_required_size')
1774 @mock.patch.object(glance.location, '_check_image_location')
1775 @mock.patch.object(glance.location.ImageRepoProxy, '_set_acls')
1776 @mock.patch.object(store, 'get_size_from_uri_and_backend')
1777 @mock.patch.object(store, 'get_size_from_backend')
1778 def test_add_location_new_validation_data_on_active(self,
1779 mock_get_size,
1780 mock_get_size_uri,
1781 mock_set_acls,
1782 mock_check_loc,
1783 mock_calc):
1784 mock_calc.return_value = 1
1785 mock_get_size.return_value = 1
1786 mock_get_size_uri.return_value = 1
1787 self.config(show_multiple_locations=True)
1788 image_id = str(uuid.uuid4())
1789 self.images = [
1790 _db_fixture(image_id, owner=TENANT1,
1791 name='1',
1792 disk_format='raw',
1793 container_format='bare',
1794 status='active',
1795 checksum=None,
1796 os_hash_algo=None,
1797 os_hash_value=None),
1798 ]
1799 self.db.image_create(None, self.images[0])
1800 request = unit_test_utils.get_fake_request()
1801 new_location = {'url': '%s/fake_location_1' % BASE_URI,
1802 'metadata': {},
1803 'validation_data': {'checksum': CHKSUM,
1804 'os_hash_algo': 'sha512',
1805 'os_hash_value': MULTIHASH1}}
1806 changes = [{'op': 'add', 'path': ['locations', '-'],
1807 'value': new_location}]
1808 self.assertRaisesRegexp(webob.exc.HTTPConflict,
1809 "may only be provided when image status "
1810 "is 'queued'",
1811 self.controller.update,
1812 request, image_id, changes)
1813
1814 @mock.patch.object(glance.quota, '_calc_required_size')
1815 @mock.patch.object(glance.location, '_check_image_location')
1816 @mock.patch.object(glance.location.ImageRepoProxy, '_set_acls')
1817 @mock.patch.object(store, 'get_size_from_uri_and_backend')
1818 @mock.patch.object(store, 'get_size_from_backend')
1819 def test_replace_locations_different_validation_data(self,
1820 mock_get_size,
1821 mock_get_size_uri,
1822 mock_set_acls,
1823 mock_check_loc,
1824 mock_calc):
1825 mock_calc.return_value = 1
1826 mock_get_size.return_value = 1
1827 mock_get_size_uri.return_value = 1
1828 self.config(show_multiple_locations=True)
1829 image_id = str(uuid.uuid4())
1830 self.images = [
1831 _db_fixture(image_id, owner=TENANT1,
1832 name='1',
1833 disk_format='raw',
1834 container_format='bare',
1835 status='active',
1836 checksum=CHKSUM,
1837 os_hash_algo='sha512',
1838 os_hash_value=MULTIHASH1),
1839 ]
1840 self.db.image_create(None, self.images[0])
1841 request = unit_test_utils.get_fake_request()
1842 new_location = {'url': '%s/fake_location_1' % BASE_URI,
1843 'metadata': {},
1844 'validation_data': {'checksum': CHKSUM1,
1845 'os_hash_algo': 'sha512',
1846 'os_hash_value': MULTIHASH2}}
1847 changes = [{'op': 'replace', 'path': ['locations'],
1848 'value': [new_location]}]
1849 self.assertRaisesRegexp(webob.exc.HTTPConflict,
1850 "already set with a different value",
1851 self.controller.update,
1852 request, image_id, changes)
1755 1853
1756 @mock.patch.object(glance.quota, '_calc_required_size') 1854 @mock.patch.object(glance.quota, '_calc_required_size')
1757 @mock.patch.object(glance.location, '_check_image_location') 1855 @mock.patch.object(glance.location, '_check_image_location')
@@ -1768,8 +1866,47 @@ class TestImagesController(base.IsolatedUnitTest):
1768 mock_get_size.return_value = 1 1866 mock_get_size.return_value = 1
1769 mock_get_size_uri.return_value = 1 1867 mock_get_size_uri.return_value = 1
1770 self.config(show_multiple_locations=True) 1868 self.config(show_multiple_locations=True)
1869 image_id = str(uuid.uuid4())
1771 self.images = [ 1870 self.images = [
1772 _db_fixture('1', owner=TENANT1, checksum=CHKSUM, 1871 _db_fixture(image_id, owner=TENANT1, checksum=CHKSUM,
1872 name='1',
1873 disk_format='raw',
1874 container_format='bare',
1875 status='queued'),
1876 ]
1877 self.db.image_create(None, self.images[0])
1878 request = unit_test_utils.get_fake_request()
1879 new_location = {'url': '%s/fake_location_1' % BASE_URI,
1880 'metadata': {}}
1881 changes = [{'op': 'add', 'path': ['locations', '-'],
1882 'value': new_location}]
1883 output = self.controller.update(request, image_id, changes)
1884 self.assertEqual(image_id, output.image_id)
1885 self.assertEqual(1, len(output.locations))
1886 self.assertEqual(new_location, output.locations[0])
1887 self.assertEqual('active', output.status)
1888
1889 @mock.patch.object(glance.quota, '_calc_required_size')
1890 @mock.patch.object(glance.location, '_check_image_location')
1891 @mock.patch.object(glance.location.ImageRepoProxy, '_set_acls')
1892 @mock.patch.object(store, 'get_size_from_uri_and_backend')
1893 @mock.patch.object(store, 'get_size_from_backend')
1894 def test_add_location_invalid_validation_data(self,
1895 mock_get_size,
1896 mock_get_size_uri,
1897 mock_set_acls,
1898 mock_check_loc,
1899 mock_calc):
1900 mock_calc.return_value = 1
1901 mock_get_size.return_value = 1
1902 mock_get_size_uri.return_value = 1
1903 self.config(show_multiple_locations=True)
1904 image_id = str(uuid.uuid4())
1905 self.images = [
1906 _db_fixture(image_id, owner=TENANT1,
1907 checksum=None,
1908 os_hash_algo=None,
1909 os_hash_value=None,
1773 name='1', 1910 name='1',
1774 disk_format='raw', 1911 disk_format='raw',
1775 container_format='bare', 1912 container_format='bare',
@@ -1777,15 +1914,151 @@ class TestImagesController(base.IsolatedUnitTest):
1777 ] 1914 ]
1778 self.db.image_create(None, self.images[0]) 1915 self.db.image_create(None, self.images[0])
1779 request = unit_test_utils.get_fake_request() 1916 request = unit_test_utils.get_fake_request()
1780 new_location = {'url': '%s/fake_location_1' % BASE_URI, 'metadata': {}} 1917
1918 location = {
1919 'url': '%s/fake_location_1' % BASE_URI,
1920 'metadata': {},
1921 'validation_data': {}
1922 }
1923 changes = [{'op': 'add', 'path': ['locations', '-'],
1924 'value': location}]
1925
1926 changes[0]['value']['validation_data'] = {
1927 'checksum': 'something the same length as md5',
1928 'os_hash_algo': 'sha512',
1929 'os_hash_value': MULTIHASH1,
1930 }
1931 self.assertRaisesRegexp(webob.exc.HTTPConflict,
1932 'checksum .* is not a valid hexadecimal value',
1933 self.controller.update,
1934 request, image_id, changes)
1935
1936 changes[0]['value']['validation_data'] = {
1937 'checksum': '0123456789abcdef',
1938 'os_hash_algo': 'sha512',
1939 'os_hash_value': MULTIHASH1,
1940 }
1941 self.assertRaisesRegexp(webob.exc.HTTPConflict,
1942 'checksum .* is not the correct size',
1943 self.controller.update,
1944 request, image_id, changes)
1945
1946 changes[0]['value']['validation_data'] = {
1947 'checksum': CHKSUM,
1948 'os_hash_algo': 'sha256',
1949 'os_hash_value': MULTIHASH1,
1950 }
1951 self.assertRaisesRegexp(webob.exc.HTTPConflict,
1952 'os_hash_algo must be sha512',
1953 self.controller.update,
1954 request, image_id, changes)
1955
1956 changes[0]['value']['validation_data'] = {
1957 'checksum': CHKSUM,
1958 'os_hash_algo': 'sha512',
1959 'os_hash_value': 'not a hex value',
1960 }
1961 self.assertRaisesRegexp(webob.exc.HTTPConflict,
1962 'os_hash_value .* is not a valid hexadecimal '
1963 'value',
1964 self.controller.update,
1965 request, image_id, changes)
1966
1967 changes[0]['value']['validation_data'] = {
1968 'checksum': CHKSUM,
1969 'os_hash_algo': 'sha512',
1970 'os_hash_value': '0123456789abcdef',
1971 }
1972 self.assertRaisesRegexp(webob.exc.HTTPConflict,
1973 'os_hash_value .* is not the correct size '
1974 'for sha512',
1975 self.controller.update,
1976 request, image_id, changes)
1977
1978 @mock.patch.object(glance.quota, '_calc_required_size')
1979 @mock.patch.object(glance.location, '_check_image_location')
1980 @mock.patch.object(glance.location.ImageRepoProxy, '_set_acls')
1981 @mock.patch.object(store, 'get_size_from_uri_and_backend')
1982 @mock.patch.object(store, 'get_size_from_backend')
1983 def test_add_location_same_validation_data(self,
1984 mock_get_size,
1985 mock_get_size_uri,
1986 mock_set_acls,
1987 mock_check_loc,
1988 mock_calc):
1989 mock_calc.return_value = 1
1990 mock_get_size.return_value = 1
1991 mock_get_size_uri.return_value = 1
1992 self.config(show_multiple_locations=True)
1993 image_id = str(uuid.uuid4())
1994 os_hash_value = '6513f21e44aa3da349f248188a44bc304a3653a04122d8fb45' \
1995 '35423c8e1d14cd6a153f735bb0982e2161b5b5186106570c17' \
1996 'a9e58b64dd39390617cd5a350f78'
1997 self.images = [
1998 _db_fixture(image_id, owner=TENANT1,
1999 name='1',
2000 disk_format='raw',
2001 container_format='bare',
2002 status='active',
2003 checksum='checksum1',
2004 os_hash_algo='sha512',
2005 os_hash_value=os_hash_value),
2006 ]
2007 self.db.image_create(None, self.images[0])
2008 request = unit_test_utils.get_fake_request()
2009 new_location = {'url': '%s/fake_location_1' % BASE_URI,
2010 'metadata': {},
2011 'validation_data': {'checksum': 'checksum1',
2012 'os_hash_algo': 'sha512',
2013 'os_hash_value': os_hash_value}}
1781 changes = [{'op': 'add', 'path': ['locations', '-'], 2014 changes = [{'op': 'add', 'path': ['locations', '-'],
1782 'value': new_location}] 2015 'value': new_location}]
1783 output = self.controller.update(request, '1', changes) 2016 output = self.controller.update(request, image_id, changes)
1784 self.assertEqual('1', output.image_id) 2017 self.assertEqual(image_id, output.image_id)
1785 self.assertEqual(1, len(output.locations)) 2018 self.assertEqual(1, len(output.locations))
1786 self.assertEqual(new_location, output.locations[0]) 2019 self.assertEqual(new_location, output.locations[0])
1787 self.assertEqual('active', output.status) 2020 self.assertEqual('active', output.status)
1788 2021
2022 @mock.patch.object(glance.quota, '_calc_required_size')
2023 @mock.patch.object(glance.location, '_check_image_location')
2024 @mock.patch.object(glance.location.ImageRepoProxy, '_set_acls')
2025 @mock.patch.object(store, 'get_size_from_uri_and_backend')
2026 @mock.patch.object(store, 'get_size_from_backend')
2027 def test_add_location_different_validation_data(self,
2028 mock_get_size,
2029 mock_get_size_uri,
2030 mock_set_acls,
2031 mock_check_loc,
2032 mock_calc):
2033 mock_calc.return_value = 1
2034 mock_get_size.return_value = 1
2035 mock_get_size_uri.return_value = 1
2036 self.config(show_multiple_locations=True)
2037 image_id = str(uuid.uuid4())
2038 self.images = [
2039 _db_fixture(image_id, owner=TENANT1,
2040 name='1',
2041 disk_format='raw',
2042 container_format='bare',
2043 status='active',
2044 checksum=CHKSUM,
2045 os_hash_algo='sha512',
2046 os_hash_value=MULTIHASH1),
2047 ]
2048 self.db.image_create(None, self.images[0])
2049 request = unit_test_utils.get_fake_request()
2050 new_location = {'url': '%s/fake_location_1' % BASE_URI,
2051 'metadata': {},
2052 'validation_data': {'checksum': CHKSUM1,
2053 'os_hash_algo': 'sha512',
2054 'os_hash_value': MULTIHASH2}}
2055 changes = [{'op': 'add', 'path': ['locations', '-'],
2056 'value': new_location}]
2057 self.assertRaisesRegexp(webob.exc.HTTPConflict,
2058 "already set with a different value",
2059 self.controller.update,
2060 request, image_id, changes)
2061
1789 def _test_update_locations_status(self, image_status, update): 2062 def _test_update_locations_status(self, image_status, update):
1790 self.config(show_multiple_locations=True) 2063 self.config(show_multiple_locations=True)
1791 self.images = [ 2064 self.images = [
@@ -2696,6 +2969,44 @@ class TestImagesDeserializer(test_utils.BaseTestCase):
2696 self.assertRaises(webob.exc.HTTPBadRequest, 2969 self.assertRaises(webob.exc.HTTPBadRequest,
2697 self.deserializer.update, request) 2970 self.deserializer.update, request)
2698 2971
2972 def test_update_invalid_validation_data(self):
2973 request = self._get_fake_patch_request()
2974 changes = [{
2975 'op': 'add',
2976 'path': '/locations/0',
2977 'value': {
2978 'url': 'http://localhost/fake',
2979 'metadata': {},
2980 }
2981 }]
2982
2983 changes[0]['value']['validation_data'] = {
2984 'os_hash_algo': 'sha512',
2985 'os_hash_value': MULTIHASH1,
2986 'checksum': CHKSUM,
2987 }
2988 request.body = jsonutils.dump_as_bytes(changes)
2989 self.deserializer.update(request)
2990
2991 changes[0]['value']['validation_data'] = {
2992 'os_hash_algo': 'sha512',
2993 'os_hash_value': MULTIHASH1,
2994 'checksum': CHKSUM,
2995 'bogus_key': 'bogus_value',
2996 }
2997 request.body = jsonutils.dump_as_bytes(changes)
2998 self.assertRaisesRegexp(webob.exc.HTTPBadRequest,
2999 'Additional properties are not allowed',
3000 self.deserializer.update, request)
3001
3002 changes[0]['value']['validation_data'] = {
3003 'checksum': CHKSUM,
3004 }
3005 request.body = jsonutils.dump_as_bytes(changes)
3006 self.assertRaisesRegexp(webob.exc.HTTPBadRequest,
3007 'os_hash.* is a required property',
3008 self.deserializer.update, request)
3009
2699 def test_update(self): 3010 def test_update(self):
2700 request = self._get_fake_patch_request() 3011 request = self._get_fake_patch_request()
2701 body = [ 3012 body = [