1764 lines
73 KiB
Python
1764 lines
73 KiB
Python
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import datetime
|
|
import hashlib
|
|
import os
|
|
import tempfile
|
|
import testtools
|
|
|
|
from glance.openstack.common import jsonutils
|
|
from glance.openstack.common import timeutils
|
|
from glance.openstack.common import units
|
|
from glance.tests.integration.legacy_functional import base
|
|
from glance.tests.utils import minimal_headers
|
|
|
|
FIVE_KB = 5 * units.Ki
|
|
FIVE_GB = 5 * units.Gi
|
|
|
|
|
|
class TestApi(base.ApiTest):
|
|
def test_get_head_simple_post(self):
|
|
# 0. GET /images
|
|
# Verify no public images
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(content, '{"images": []}')
|
|
|
|
# 1. GET /images/detail
|
|
# Verify no public images
|
|
path = "/v1/images/detail"
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(content, '{"images": []}')
|
|
|
|
# 2. POST /images with public image named Image1
|
|
# attribute and no custom properties. Verify a 200 OK is returned
|
|
image_data = "*" * FIVE_KB
|
|
headers = minimal_headers('Image1')
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'POST', headers=headers,
|
|
body=image_data)
|
|
self.assertEqual(response.status, 201)
|
|
data = jsonutils.loads(content)
|
|
image_id = data['image']['id']
|
|
self.assertEqual(data['image']['checksum'],
|
|
hashlib.md5(image_data).hexdigest())
|
|
self.assertEqual(data['image']['size'], FIVE_KB)
|
|
self.assertEqual(data['image']['name'], "Image1")
|
|
self.assertEqual(data['image']['is_public'], True)
|
|
|
|
# 3. HEAD image
|
|
# Verify image found now
|
|
path = "/v1/images/%s" % image_id
|
|
response, content = self.http.request(path, 'HEAD')
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(response['x-image-meta-name'], "Image1")
|
|
|
|
# 4. GET image
|
|
# Verify all information on image we just added is correct
|
|
path = "/v1/images/%s" % image_id
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
|
|
expected_image_headers = {
|
|
'x-image-meta-id': image_id,
|
|
'x-image-meta-name': 'Image1',
|
|
'x-image-meta-is_public': 'True',
|
|
'x-image-meta-status': 'active',
|
|
'x-image-meta-disk_format': 'raw',
|
|
'x-image-meta-container_format': 'ovf',
|
|
'x-image-meta-size': str(FIVE_KB)}
|
|
|
|
expected_std_headers = {
|
|
'content-length': str(FIVE_KB),
|
|
'content-type': 'application/octet-stream'}
|
|
|
|
for expected_key, expected_value in expected_image_headers.items():
|
|
self.assertEqual(response[expected_key], expected_value,
|
|
"For key '%s' expected header value '%s'. "
|
|
"Got '%s'" % (expected_key,
|
|
expected_value,
|
|
response[expected_key]))
|
|
|
|
for expected_key, expected_value in expected_std_headers.items():
|
|
self.assertEqual(response[expected_key], expected_value,
|
|
"For key '%s' expected header value '%s'. "
|
|
"Got '%s'" % (expected_key,
|
|
expected_value,
|
|
response[expected_key]))
|
|
|
|
self.assertEqual(content, "*" * FIVE_KB)
|
|
self.assertEqual(hashlib.md5(content).hexdigest(),
|
|
hashlib.md5("*" * FIVE_KB).hexdigest())
|
|
|
|
# 5. GET /images
|
|
# Verify no public images
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
|
|
expected_result = {"images": [
|
|
{"container_format": "ovf",
|
|
"disk_format": "raw",
|
|
"id": image_id,
|
|
"name": "Image1",
|
|
"checksum": "c2e5db72bd7fd153f53ede5da5a06de3",
|
|
"size": 5120}]}
|
|
self.assertEqual(jsonutils.loads(content), expected_result)
|
|
|
|
# 6. GET /images/detail
|
|
# Verify image and all its metadata
|
|
path = "/v1/images/detail"
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
|
|
expected_image = {
|
|
"status": "active",
|
|
"name": "Image1",
|
|
"deleted": False,
|
|
"container_format": "ovf",
|
|
"disk_format": "raw",
|
|
"id": image_id,
|
|
"is_public": True,
|
|
"deleted_at": None,
|
|
"properties": {},
|
|
"size": 5120}
|
|
|
|
image = jsonutils.loads(content)
|
|
|
|
for expected_key, expected_value in expected_image.items():
|
|
self.assertEqual(expected_value, image['images'][0][expected_key],
|
|
"For key '%s' expected header value '%s'. "
|
|
"Got '%s'" % (expected_key,
|
|
expected_value,
|
|
image['images'][0][expected_key]))
|
|
|
|
# 7. PUT image with custom properties of "distro" and "arch"
|
|
# Verify 200 returned
|
|
headers = {'X-Image-Meta-Property-Distro': 'Ubuntu',
|
|
'X-Image-Meta-Property-Arch': 'x86_64'}
|
|
path = "/v1/images/%s" % image_id
|
|
response, content = self.http.request(path, 'PUT', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
data = jsonutils.loads(content)
|
|
self.assertEqual(data['image']['properties']['arch'], "x86_64")
|
|
self.assertEqual(data['image']['properties']['distro'], "Ubuntu")
|
|
|
|
# 8. GET /images/detail
|
|
# Verify image and all its metadata
|
|
path = "/v1/images/detail"
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
|
|
expected_image = {
|
|
"status": "active",
|
|
"name": "Image1",
|
|
"deleted": False,
|
|
"container_format": "ovf",
|
|
"disk_format": "raw",
|
|
"id": image_id,
|
|
"is_public": True,
|
|
"deleted_at": None,
|
|
"properties": {'distro': 'Ubuntu', 'arch': 'x86_64'},
|
|
"size": 5120}
|
|
|
|
image = jsonutils.loads(content)
|
|
|
|
for expected_key, expected_value in expected_image.items():
|
|
self.assertEqual(expected_value, image['images'][0][expected_key],
|
|
"For key '%s' expected header value '%s'. "
|
|
"Got '%s'" % (expected_key,
|
|
expected_value,
|
|
image['images'][0][expected_key]))
|
|
|
|
# 9. PUT image and remove a previously existing property.
|
|
headers = {'X-Image-Meta-Property-Arch': 'x86_64'}
|
|
path = "/v1/images/%s" % image_id
|
|
response, content = self.http.request(path, 'PUT', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
|
|
path = "/v1/images/detail"
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
data = jsonutils.loads(content)['images'][0]
|
|
self.assertEqual(len(data['properties']), 1)
|
|
self.assertEqual(data['properties']['arch'], "x86_64")
|
|
|
|
# 10. PUT image and add a previously deleted property.
|
|
headers = {'X-Image-Meta-Property-Distro': 'Ubuntu',
|
|
'X-Image-Meta-Property-Arch': 'x86_64'}
|
|
path = "/v1/images/%s" % image_id
|
|
response, content = self.http.request(path, 'PUT', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
data = jsonutils.loads(content)
|
|
|
|
path = "/v1/images/detail"
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
data = jsonutils.loads(content)['images'][0]
|
|
self.assertEqual(len(data['properties']), 2)
|
|
self.assertEqual(data['properties']['arch'], "x86_64")
|
|
self.assertEqual(data['properties']['distro'], "Ubuntu")
|
|
self.assertNotEqual(data['created_at'], data['updated_at'])
|
|
|
|
# DELETE image
|
|
path = "/v1/images/%s" % image_id
|
|
response, content = self.http.request(path, 'DELETE')
|
|
self.assertEqual(response.status, 200)
|
|
|
|
def test_queued_process_flow(self):
|
|
"""
|
|
We test the process flow where a user registers an image
|
|
with Glance but does not immediately upload an image file.
|
|
Later, the user uploads an image file using a PUT operation.
|
|
We track the changing of image status throughout this process.
|
|
|
|
0. GET /images
|
|
- Verify no public images
|
|
1. POST /images with public image named Image1 with no location
|
|
attribute and no image data.
|
|
- Verify 201 returned
|
|
2. GET /images
|
|
- Verify one public image
|
|
3. HEAD image
|
|
- Verify image now in queued status
|
|
4. PUT image with image data
|
|
- Verify 200 returned
|
|
5. HEAD images
|
|
- Verify image now in active status
|
|
6. GET /images
|
|
- Verify one public image
|
|
"""
|
|
|
|
# 0. GET /images
|
|
# Verify no public images
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(content, '{"images": []}')
|
|
|
|
# 1. POST /images with public image named Image1
|
|
# with no location or image data
|
|
headers = minimal_headers('Image1')
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'POST', headers=headers)
|
|
self.assertEqual(response.status, 201)
|
|
data = jsonutils.loads(content)
|
|
self.assertIsNone(data['image']['checksum'])
|
|
self.assertEqual(data['image']['size'], 0)
|
|
self.assertEqual(data['image']['container_format'], 'ovf')
|
|
self.assertEqual(data['image']['disk_format'], 'raw')
|
|
self.assertEqual(data['image']['name'], "Image1")
|
|
self.assertEqual(data['image']['is_public'], True)
|
|
|
|
image_id = data['image']['id']
|
|
|
|
# 2. GET /images
|
|
# Verify 1 public image
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
data = jsonutils.loads(content)
|
|
self.assertEqual(data['images'][0]['id'], image_id)
|
|
self.assertIsNone(data['images'][0]['checksum'])
|
|
self.assertEqual(data['images'][0]['size'], 0)
|
|
self.assertEqual(data['images'][0]['container_format'], 'ovf')
|
|
self.assertEqual(data['images'][0]['disk_format'], 'raw')
|
|
self.assertEqual(data['images'][0]['name'], "Image1")
|
|
|
|
# 3. HEAD /images
|
|
# Verify status is in queued
|
|
path = "/v1/images/%s" % (image_id)
|
|
response, content = self.http.request(path, 'HEAD')
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(response['x-image-meta-name'], "Image1")
|
|
self.assertEqual(response['x-image-meta-status'], "queued")
|
|
self.assertEqual(response['x-image-meta-size'], '0')
|
|
self.assertEqual(response['x-image-meta-id'], image_id)
|
|
|
|
# 4. PUT image with image data, verify 200 returned
|
|
image_data = "*" * FIVE_KB
|
|
headers = {'Content-Type': 'application/octet-stream'}
|
|
path = "/v1/images/%s" % (image_id)
|
|
response, content = self.http.request(path, 'PUT', headers=headers,
|
|
body=image_data)
|
|
self.assertEqual(response.status, 200)
|
|
data = jsonutils.loads(content)
|
|
self.assertEqual(data['image']['checksum'],
|
|
hashlib.md5(image_data).hexdigest())
|
|
self.assertEqual(data['image']['size'], FIVE_KB)
|
|
self.assertEqual(data['image']['name'], "Image1")
|
|
self.assertEqual(data['image']['is_public'], True)
|
|
|
|
# 5. HEAD /images
|
|
# Verify status is in active
|
|
path = "/v1/images/%s" % (image_id)
|
|
response, content = self.http.request(path, 'HEAD')
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(response['x-image-meta-name'], "Image1")
|
|
self.assertEqual(response['x-image-meta-status'], "active")
|
|
|
|
# 6. GET /images
|
|
# Verify 1 public image still...
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
data = jsonutils.loads(content)
|
|
self.assertEqual(data['images'][0]['checksum'],
|
|
hashlib.md5(image_data).hexdigest())
|
|
self.assertEqual(data['images'][0]['id'], image_id)
|
|
self.assertEqual(data['images'][0]['size'], FIVE_KB)
|
|
self.assertEqual(data['images'][0]['container_format'], 'ovf')
|
|
self.assertEqual(data['images'][0]['disk_format'], 'raw')
|
|
self.assertEqual(data['images'][0]['name'], "Image1")
|
|
|
|
# DELETE image
|
|
path = "/v1/images/%s" % (image_id)
|
|
response, content = self.http.request(path, 'DELETE')
|
|
self.assertEqual(response.status, 200)
|
|
|
|
def test_size_greater_2G_mysql(self):
|
|
"""
|
|
A test against the actual datastore backend for the registry
|
|
to ensure that the image size property is not truncated.
|
|
|
|
:see https://bugs.launchpad.net/glance/+bug/739433
|
|
"""
|
|
|
|
# 1. POST /images with public image named Image1
|
|
# attribute and a size of 5G. Use the HTTP engine with an
|
|
# X-Image-Meta-Location attribute to make Glance forego
|
|
# "adding" the image data.
|
|
# Verify a 201 OK is returned
|
|
headers = {'Content-Type': 'application/octet-stream',
|
|
'X-Image-Meta-Location': 'http://example.com/fakeimage',
|
|
'X-Image-Meta-Size': str(FIVE_GB),
|
|
'X-Image-Meta-Name': 'Image1',
|
|
'X-Image-Meta-disk_format': 'raw',
|
|
'X-image-Meta-container_format': 'ovf',
|
|
'X-Image-Meta-Is-Public': 'True'}
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'POST', headers=headers)
|
|
self.assertEqual(response.status, 201)
|
|
|
|
# 2. HEAD /images
|
|
# Verify image size is what was passed in, and not truncated
|
|
path = response.get('location')
|
|
response, content = self.http.request(path, 'HEAD')
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(response['x-image-meta-size'], str(FIVE_GB))
|
|
self.assertEqual(response['x-image-meta-name'], 'Image1')
|
|
self.assertEqual(response['x-image-meta-is_public'], 'True')
|
|
|
|
def test_v1_not_enabled(self):
|
|
self.config(enable_v1_api=False)
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 300)
|
|
|
|
def test_v1_enabled(self):
|
|
self.config(enable_v1_api=True)
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
|
|
def test_zero_initial_size(self):
|
|
"""
|
|
A test to ensure that an image with size explicitly set to zero
|
|
has status that immediately transitions to active.
|
|
"""
|
|
# 1. POST /images with public image named Image1
|
|
# attribute and a size of zero.
|
|
# Verify a 201 OK is returned
|
|
headers = {'Content-Type': 'application/octet-stream',
|
|
'X-Image-Meta-Size': '0',
|
|
'X-Image-Meta-Name': 'Image1',
|
|
'X-Image-Meta-disk_format': 'raw',
|
|
'X-image-Meta-container_format': 'ovf',
|
|
'X-Image-Meta-Is-Public': 'True'}
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'POST', headers=headers)
|
|
self.assertEqual(response.status, 201)
|
|
|
|
# 2. HEAD image-location
|
|
# Verify image size is zero and the status is active
|
|
path = response.get('location')
|
|
response, content = self.http.request(path, 'HEAD')
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(response['x-image-meta-size'], '0')
|
|
self.assertEqual(response['x-image-meta-status'], 'active')
|
|
|
|
# 3. GET image-location
|
|
# Verify image content is empty
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(len(content), 0)
|
|
|
|
def test_traceback_not_consumed(self):
|
|
"""
|
|
A test that errors coming from the POST API do not
|
|
get consumed and print the actual error message, and
|
|
not something like <traceback object at 0x1918d40>
|
|
|
|
:see https://bugs.launchpad.net/glance/+bug/755912
|
|
"""
|
|
# POST /images with binary data, but not setting
|
|
# Content-Type to application/octet-stream, verify a
|
|
# 400 returned and that the error is readable.
|
|
with tempfile.NamedTemporaryFile() as test_data_file:
|
|
test_data_file.write("XXX")
|
|
test_data_file.flush()
|
|
path = "/v1/images"
|
|
headers = minimal_headers('Image1')
|
|
headers['Content-Type'] = 'not octet-stream'
|
|
response, content = self.http.request(path, 'POST',
|
|
body=test_data_file.name,
|
|
headers=headers)
|
|
self.assertEqual(response.status, 400)
|
|
expected = "Content-Type must be application/octet-stream"
|
|
self.assertTrue(expected in content,
|
|
"Could not find '%s' in '%s'" % (expected, content))
|
|
|
|
def test_filtered_images(self):
|
|
"""
|
|
Set up four test images and ensure each query param filter works
|
|
"""
|
|
|
|
# 0. GET /images
|
|
# Verify no public images
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(content, '{"images": []}')
|
|
|
|
image_ids = []
|
|
|
|
# 1. POST /images with three public images, and one private image
|
|
# with various attributes
|
|
headers = {'Content-Type': 'application/octet-stream',
|
|
'X-Image-Meta-Name': 'Image1',
|
|
'X-Image-Meta-Status': 'active',
|
|
'X-Image-Meta-Container-Format': 'ovf',
|
|
'X-Image-Meta-Disk-Format': 'vdi',
|
|
'X-Image-Meta-Size': '19',
|
|
'X-Image-Meta-Is-Public': 'True',
|
|
'X-Image-Meta-Protected': 'True',
|
|
'X-Image-Meta-Property-pants': 'are on'}
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'POST', headers=headers)
|
|
self.assertEqual(response.status, 201)
|
|
data = jsonutils.loads(content)
|
|
self.assertEqual(data['image']['properties']['pants'], "are on")
|
|
self.assertEqual(data['image']['is_public'], True)
|
|
image_ids.append(data['image']['id'])
|
|
|
|
headers = {'Content-Type': 'application/octet-stream',
|
|
'X-Image-Meta-Name': 'My Image!',
|
|
'X-Image-Meta-Status': 'active',
|
|
'X-Image-Meta-Container-Format': 'ovf',
|
|
'X-Image-Meta-Disk-Format': 'vhd',
|
|
'X-Image-Meta-Size': '20',
|
|
'X-Image-Meta-Is-Public': 'True',
|
|
'X-Image-Meta-Protected': 'False',
|
|
'X-Image-Meta-Property-pants': 'are on'}
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'POST', headers=headers)
|
|
self.assertEqual(response.status, 201)
|
|
data = jsonutils.loads(content)
|
|
self.assertEqual(data['image']['properties']['pants'], "are on")
|
|
self.assertEqual(data['image']['is_public'], True)
|
|
image_ids.append(data['image']['id'])
|
|
|
|
headers = {'Content-Type': 'application/octet-stream',
|
|
'X-Image-Meta-Name': 'My Image!',
|
|
'X-Image-Meta-Status': 'saving',
|
|
'X-Image-Meta-Container-Format': 'ami',
|
|
'X-Image-Meta-Disk-Format': 'ami',
|
|
'X-Image-Meta-Size': '21',
|
|
'X-Image-Meta-Is-Public': 'True',
|
|
'X-Image-Meta-Protected': 'False',
|
|
'X-Image-Meta-Property-pants': 'are off'}
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'POST', headers=headers)
|
|
self.assertEqual(response.status, 201)
|
|
data = jsonutils.loads(content)
|
|
self.assertEqual(data['image']['properties']['pants'], "are off")
|
|
self.assertEqual(data['image']['is_public'], True)
|
|
image_ids.append(data['image']['id'])
|
|
|
|
headers = {'Content-Type': 'application/octet-stream',
|
|
'X-Image-Meta-Name': 'My Private Image',
|
|
'X-Image-Meta-Status': 'active',
|
|
'X-Image-Meta-Container-Format': 'ami',
|
|
'X-Image-Meta-Disk-Format': 'ami',
|
|
'X-Image-Meta-Size': '22',
|
|
'X-Image-Meta-Is-Public': 'False',
|
|
'X-Image-Meta-Protected': 'False'}
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'POST', headers=headers)
|
|
self.assertEqual(response.status, 201)
|
|
data = jsonutils.loads(content)
|
|
self.assertEqual(data['image']['is_public'], False)
|
|
image_ids.append(data['image']['id'])
|
|
|
|
# 2. GET /images
|
|
# Verify three public images
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
data = jsonutils.loads(content)
|
|
self.assertEqual(len(data['images']), 3)
|
|
|
|
# 3. GET /images with name filter
|
|
# Verify correct images returned with name
|
|
params = "name=My%20Image!"
|
|
path = "/v1/images?%s" % (params)
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
data = jsonutils.loads(content)
|
|
self.assertEqual(len(data['images']), 2)
|
|
for image in data['images']:
|
|
self.assertEqual(image['name'], "My Image!")
|
|
|
|
# 4. GET /images with status filter
|
|
# Verify correct images returned with status
|
|
params = "status=queued"
|
|
path = "/v1/images/detail?%s" % (params)
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
data = jsonutils.loads(content)
|
|
self.assertEqual(len(data['images']), 3)
|
|
for image in data['images']:
|
|
self.assertEqual(image['status'], "queued")
|
|
|
|
params = "status=active"
|
|
path = "/v1/images/detail?%s" % (params)
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
data = jsonutils.loads(content)
|
|
self.assertEqual(len(data['images']), 0)
|
|
|
|
# 5. GET /images with container_format filter
|
|
# Verify correct images returned with container_format
|
|
params = "container_format=ovf"
|
|
path = "/v1/images?%s" % (params)
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
data = jsonutils.loads(content)
|
|
self.assertEqual(len(data['images']), 2)
|
|
for image in data['images']:
|
|
self.assertEqual(image['container_format'], "ovf")
|
|
|
|
# 6. GET /images with disk_format filter
|
|
# Verify correct images returned with disk_format
|
|
params = "disk_format=vdi"
|
|
path = "/v1/images?%s" % (params)
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
data = jsonutils.loads(content)
|
|
self.assertEqual(len(data['images']), 1)
|
|
for image in data['images']:
|
|
self.assertEqual(image['disk_format'], "vdi")
|
|
|
|
# 7. GET /images with size_max filter
|
|
# Verify correct images returned with size <= expected
|
|
params = "size_max=20"
|
|
path = "/v1/images?%s" % (params)
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
data = jsonutils.loads(content)
|
|
self.assertEqual(len(data['images']), 2)
|
|
for image in data['images']:
|
|
self.assertTrue(image['size'] <= 20)
|
|
|
|
# 8. GET /images with size_min filter
|
|
# Verify correct images returned with size >= expected
|
|
params = "size_min=20"
|
|
path = "/v1/images?%s" % (params)
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
data = jsonutils.loads(content)
|
|
self.assertEqual(len(data['images']), 2)
|
|
for image in data['images']:
|
|
self.assertTrue(image['size'] >= 20)
|
|
|
|
# 9. Get /images with is_public=None filter
|
|
# Verify correct images returned with property
|
|
# Bug lp:803656 Support is_public in filtering
|
|
params = "is_public=None"
|
|
path = "/v1/images?%s" % (params)
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
data = jsonutils.loads(content)
|
|
self.assertEqual(len(data['images']), 4)
|
|
|
|
# 10. Get /images with is_public=False filter
|
|
# Verify correct images returned with property
|
|
# Bug lp:803656 Support is_public in filtering
|
|
params = "is_public=False"
|
|
path = "/v1/images?%s" % (params)
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
data = jsonutils.loads(content)
|
|
self.assertEqual(len(data['images']), 1)
|
|
for image in data['images']:
|
|
self.assertEqual(image['name'], "My Private Image")
|
|
|
|
# 11. Get /images with is_public=True filter
|
|
# Verify correct images returned with property
|
|
# Bug lp:803656 Support is_public in filtering
|
|
params = "is_public=True"
|
|
path = "/v1/images?%s" % (params)
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
data = jsonutils.loads(content)
|
|
self.assertEqual(len(data['images']), 3)
|
|
for image in data['images']:
|
|
self.assertNotEqual(image['name'], "My Private Image")
|
|
|
|
# 12. Get /images with protected=False filter
|
|
# Verify correct images returned with property
|
|
params = "protected=False"
|
|
path = "/v1/images?%s" % (params)
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
data = jsonutils.loads(content)
|
|
self.assertEqual(len(data['images']), 2)
|
|
for image in data['images']:
|
|
self.assertNotEqual(image['name'], "Image1")
|
|
|
|
# 13. Get /images with protected=True filter
|
|
# Verify correct images returned with property
|
|
params = "protected=True"
|
|
path = "/v1/images?%s" % (params)
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
data = jsonutils.loads(content)
|
|
self.assertEqual(len(data['images']), 1)
|
|
for image in data['images']:
|
|
self.assertEqual(image['name'], "Image1")
|
|
|
|
# 14. GET /images with property filter
|
|
# Verify correct images returned with property
|
|
params = "property-pants=are%20on"
|
|
path = "/v1/images/detail?%s" % (params)
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
data = jsonutils.loads(content)
|
|
self.assertEqual(len(data['images']), 2)
|
|
for image in data['images']:
|
|
self.assertEqual(image['properties']['pants'], "are on")
|
|
|
|
# 15. GET /images with property filter and name filter
|
|
# Verify correct images returned with property and name
|
|
# Make sure you quote the url when using more than one param!
|
|
params = "name=My%20Image!&property-pants=are%20on"
|
|
path = "/v1/images/detail?%s" % (params)
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
data = jsonutils.loads(content)
|
|
self.assertEqual(len(data['images']), 1)
|
|
for image in data['images']:
|
|
self.assertEqual(image['properties']['pants'], "are on")
|
|
self.assertEqual(image['name'], "My Image!")
|
|
|
|
# 16. GET /images with past changes-since filter
|
|
yesterday = timeutils.isotime(timeutils.utcnow() -
|
|
datetime.timedelta(1))
|
|
params = "changes-since=%s" % yesterday
|
|
path = "/v1/images?%s" % (params)
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
data = jsonutils.loads(content)
|
|
self.assertEqual(len(data['images']), 3)
|
|
|
|
# one timezone west of Greenwich equates to an hour ago
|
|
# taking care to pre-urlencode '+' as '%2B', otherwise the timezone
|
|
# '+' is wrongly decoded as a space
|
|
# TODO(eglynn): investigate '+' --> <SPACE> decoding, an artifact
|
|
# of WSGI/webob dispatch?
|
|
now = timeutils.utcnow()
|
|
hour_ago = now.strftime('%Y-%m-%dT%H:%M:%S%%2B01:00')
|
|
params = "changes-since=%s" % hour_ago
|
|
path = "/v1/images?%s" % (params)
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
data = jsonutils.loads(content)
|
|
self.assertEqual(len(data['images']), 3)
|
|
|
|
# 17. GET /images with future changes-since filter
|
|
tomorrow = timeutils.isotime(timeutils.utcnow() +
|
|
datetime.timedelta(1))
|
|
params = "changes-since=%s" % tomorrow
|
|
path = "/v1/images?%s" % (params)
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
data = jsonutils.loads(content)
|
|
self.assertEqual(len(data['images']), 0)
|
|
|
|
# one timezone east of Greenwich equates to an hour from now
|
|
now = timeutils.utcnow()
|
|
hour_hence = now.strftime('%Y-%m-%dT%H:%M:%S-01:00')
|
|
params = "changes-since=%s" % hour_hence
|
|
path = "/v1/images?%s" % (params)
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
data = jsonutils.loads(content)
|
|
self.assertEqual(len(data['images']), 0)
|
|
|
|
# 18. GET /images with size_min filter
|
|
# Verify correct images returned with size >= expected
|
|
params = "size_min=-1"
|
|
path = "/v1/images?%s" % (params)
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 400)
|
|
self.assertTrue("filter size_min got -1" in content)
|
|
|
|
# 19. GET /images with size_min filter
|
|
# Verify correct images returned with size >= expected
|
|
params = "size_max=-1"
|
|
path = "/v1/images?%s" % (params)
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 400)
|
|
self.assertTrue("filter size_max got -1" in content)
|
|
|
|
# 20. GET /images with size_min filter
|
|
# Verify correct images returned with size >= expected
|
|
params = "min_ram=-1"
|
|
path = "/v1/images?%s" % (params)
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 400)
|
|
self.assertTrue("Bad value passed to filter min_ram got -1" in content)
|
|
|
|
# 21. GET /images with size_min filter
|
|
# Verify correct images returned with size >= expected
|
|
params = "protected=imalittleteapot"
|
|
path = "/v1/images?%s" % (params)
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 400)
|
|
self.assertTrue("protected got imalittleteapot" in content)
|
|
|
|
# 22. GET /images with size_min filter
|
|
# Verify correct images returned with size >= expected
|
|
params = "is_public=imalittleteapot"
|
|
path = "/v1/images?%s" % (params)
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 400)
|
|
self.assertTrue("is_public got imalittleteapot" in content)
|
|
|
|
def test_limited_images(self):
|
|
"""
|
|
Ensure marker and limit query params work
|
|
"""
|
|
|
|
# 0. GET /images
|
|
# Verify no public images
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(content, '{"images": []}')
|
|
|
|
image_ids = []
|
|
|
|
# 1. POST /images with three public images with various attributes
|
|
headers = minimal_headers('Image1')
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'POST', headers=headers)
|
|
self.assertEqual(response.status, 201)
|
|
image_ids.append(jsonutils.loads(content)['image']['id'])
|
|
|
|
headers = minimal_headers('Image2')
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'POST', headers=headers)
|
|
self.assertEqual(response.status, 201)
|
|
image_ids.append(jsonutils.loads(content)['image']['id'])
|
|
|
|
headers = minimal_headers('Image3')
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'POST', headers=headers)
|
|
self.assertEqual(response.status, 201)
|
|
image_ids.append(jsonutils.loads(content)['image']['id'])
|
|
|
|
# 2. GET /images with all images
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
images = jsonutils.loads(content)['images']
|
|
self.assertEqual(len(images), 3)
|
|
|
|
# 3. GET /images with limit of 2
|
|
# Verify only two images were returned
|
|
params = "limit=2"
|
|
path = "/v1/images?%s" % (params)
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
data = jsonutils.loads(content)['images']
|
|
self.assertEqual(len(data), 2)
|
|
self.assertEqual(data[0]['id'], images[0]['id'])
|
|
self.assertEqual(data[1]['id'], images[1]['id'])
|
|
|
|
# 4. GET /images with marker
|
|
# Verify only two images were returned
|
|
params = "marker=%s" % images[0]['id']
|
|
path = "/v1/images?%s" % (params)
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
data = jsonutils.loads(content)['images']
|
|
self.assertEqual(len(data), 2)
|
|
self.assertEqual(data[0]['id'], images[1]['id'])
|
|
self.assertEqual(data[1]['id'], images[2]['id'])
|
|
|
|
# 5. GET /images with marker and limit
|
|
# Verify only one image was returned with the correct id
|
|
params = "limit=1&marker=%s" % images[1]['id']
|
|
path = "/v1/images?%s" % (params)
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
data = jsonutils.loads(content)['images']
|
|
self.assertEqual(len(data), 1)
|
|
self.assertEqual(data[0]['id'], images[2]['id'])
|
|
|
|
# 6. GET /images/detail with marker and limit
|
|
# Verify only one image was returned with the correct id
|
|
params = "limit=1&marker=%s" % images[1]['id']
|
|
path = "/v1/images?%s" % (params)
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
data = jsonutils.loads(content)['images']
|
|
self.assertEqual(len(data), 1)
|
|
self.assertEqual(data[0]['id'], images[2]['id'])
|
|
|
|
# DELETE images
|
|
for image_id in image_ids:
|
|
path = "/v1/images/%s" % (image_id)
|
|
response, content = self.http.request(path, 'DELETE')
|
|
self.assertEqual(response.status, 200)
|
|
|
|
def test_ordered_images(self):
|
|
"""
|
|
Set up three test images and ensure each query param filter works
|
|
"""
|
|
# 0. GET /images
|
|
# Verify no public images
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(content, '{"images": []}')
|
|
|
|
# 1. POST /images with three public images with various attributes
|
|
image_ids = []
|
|
headers = {'Content-Type': 'application/octet-stream',
|
|
'X-Image-Meta-Name': 'Image1',
|
|
'X-Image-Meta-Status': 'active',
|
|
'X-Image-Meta-Container-Format': 'ovf',
|
|
'X-Image-Meta-Disk-Format': 'vdi',
|
|
'X-Image-Meta-Size': '19',
|
|
'X-Image-Meta-Is-Public': 'True'}
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'POST', headers=headers)
|
|
self.assertEqual(response.status, 201)
|
|
image_ids.append(jsonutils.loads(content)['image']['id'])
|
|
|
|
headers = {'Content-Type': 'application/octet-stream',
|
|
'X-Image-Meta-Name': 'ASDF',
|
|
'X-Image-Meta-Status': 'active',
|
|
'X-Image-Meta-Container-Format': 'bare',
|
|
'X-Image-Meta-Disk-Format': 'iso',
|
|
'X-Image-Meta-Size': '2',
|
|
'X-Image-Meta-Is-Public': 'True'}
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'POST', headers=headers)
|
|
self.assertEqual(response.status, 201)
|
|
image_ids.append(jsonutils.loads(content)['image']['id'])
|
|
|
|
headers = {'Content-Type': 'application/octet-stream',
|
|
'X-Image-Meta-Name': 'XYZ',
|
|
'X-Image-Meta-Status': 'saving',
|
|
'X-Image-Meta-Container-Format': 'ami',
|
|
'X-Image-Meta-Disk-Format': 'ami',
|
|
'X-Image-Meta-Size': '5',
|
|
'X-Image-Meta-Is-Public': 'True'}
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'POST', headers=headers)
|
|
self.assertEqual(response.status, 201)
|
|
image_ids.append(jsonutils.loads(content)['image']['id'])
|
|
|
|
# 2. GET /images with no query params
|
|
# Verify three public images sorted by created_at desc
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
data = jsonutils.loads(content)
|
|
self.assertEqual(len(data['images']), 3)
|
|
self.assertEqual(data['images'][0]['id'], image_ids[2])
|
|
self.assertEqual(data['images'][1]['id'], image_ids[1])
|
|
self.assertEqual(data['images'][2]['id'], image_ids[0])
|
|
|
|
# 3. GET /images sorted by name asc
|
|
params = 'sort_key=name&sort_dir=asc'
|
|
path = "/v1/images?%s" % (params)
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
data = jsonutils.loads(content)
|
|
self.assertEqual(len(data['images']), 3)
|
|
self.assertEqual(data['images'][0]['id'], image_ids[1])
|
|
self.assertEqual(data['images'][1]['id'], image_ids[0])
|
|
self.assertEqual(data['images'][2]['id'], image_ids[2])
|
|
|
|
# 4. GET /images sorted by size desc
|
|
params = 'sort_key=size&sort_dir=desc'
|
|
path = "/v1/images?%s" % (params)
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
data = jsonutils.loads(content)
|
|
self.assertEqual(len(data['images']), 3)
|
|
self.assertEqual(data['images'][0]['id'], image_ids[0])
|
|
self.assertEqual(data['images'][1]['id'], image_ids[2])
|
|
self.assertEqual(data['images'][2]['id'], image_ids[1])
|
|
|
|
# 5. GET /images sorted by size desc with a marker
|
|
params = 'sort_key=size&sort_dir=desc&marker=%s' % image_ids[0]
|
|
path = "/v1/images?%s" % (params)
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
data = jsonutils.loads(content)
|
|
self.assertEqual(len(data['images']), 2)
|
|
self.assertEqual(data['images'][0]['id'], image_ids[2])
|
|
self.assertEqual(data['images'][1]['id'], image_ids[1])
|
|
|
|
# 6. GET /images sorted by name asc with a marker
|
|
params = 'sort_key=name&sort_dir=asc&marker=%s' % image_ids[2]
|
|
path = "/v1/images?%s" % (params)
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
data = jsonutils.loads(content)
|
|
self.assertEqual(len(data['images']), 0)
|
|
|
|
# DELETE images
|
|
for image_id in image_ids:
|
|
path = "/v1/images/%s" % (image_id)
|
|
response, content = self.http.request(path, 'DELETE')
|
|
self.assertEqual(response.status, 200)
|
|
|
|
def test_duplicate_image_upload(self):
|
|
"""
|
|
Upload initial image, then attempt to upload duplicate image
|
|
"""
|
|
# 0. GET /images
|
|
# Verify no public images
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(content, '{"images": []}')
|
|
|
|
# 1. POST /images with public image named Image1
|
|
headers = {'Content-Type': 'application/octet-stream',
|
|
'X-Image-Meta-Name': 'Image1',
|
|
'X-Image-Meta-Status': 'active',
|
|
'X-Image-Meta-Container-Format': 'ovf',
|
|
'X-Image-Meta-Disk-Format': 'vdi',
|
|
'X-Image-Meta-Size': '19',
|
|
'X-Image-Meta-Is-Public': 'True'}
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'POST', headers=headers)
|
|
self.assertEqual(response.status, 201)
|
|
|
|
image = jsonutils.loads(content)['image']
|
|
|
|
# 2. POST /images with public image named Image1, and ID: 1
|
|
headers = {'Content-Type': 'application/octet-stream',
|
|
'X-Image-Meta-Name': 'Image1 Update',
|
|
'X-Image-Meta-Status': 'active',
|
|
'X-Image-Meta-Container-Format': 'ovf',
|
|
'X-Image-Meta-Disk-Format': 'vdi',
|
|
'X-Image-Meta-Size': '19',
|
|
'X-Image-Meta-Id': image['id'],
|
|
'X-Image-Meta-Is-Public': 'True'}
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'POST', headers=headers)
|
|
self.assertEqual(response.status, 409)
|
|
|
|
def test_delete_not_existing(self):
|
|
"""
|
|
We test the following:
|
|
|
|
0. GET /images/1
|
|
- Verify 404
|
|
1. DELETE /images/1
|
|
- Verify 404
|
|
"""
|
|
|
|
# 0. GET /images
|
|
# Verify no public images
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(content, '{"images": []}')
|
|
|
|
# 1. DELETE /images/1
|
|
# Verify 404 returned
|
|
path = "/v1/images/1"
|
|
response, content = self.http.request(path, 'DELETE')
|
|
self.assertEqual(response.status, 404)
|
|
|
|
def _do_test_post_image_content_bad_format(self, format):
|
|
"""
|
|
We test that missing container/disk format fails with 400 "Bad Request"
|
|
|
|
:see https://bugs.launchpad.net/glance/+bug/933702
|
|
"""
|
|
|
|
# Verify no public images
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
images = jsonutils.loads(content)['images']
|
|
self.assertEqual(len(images), 0)
|
|
|
|
path = "/v1/images"
|
|
|
|
# POST /images without given format being specified
|
|
headers = minimal_headers('Image1')
|
|
headers['X-Image-Meta-' + format] = 'bad_value'
|
|
with tempfile.NamedTemporaryFile() as test_data_file:
|
|
test_data_file.write("XXX")
|
|
test_data_file.flush()
|
|
response, content = self.http.request(path, 'POST',
|
|
headers=headers,
|
|
body=test_data_file.name)
|
|
self.assertEqual(response.status, 400)
|
|
type = format.replace('_format', '')
|
|
expected = "Invalid %s format 'bad_value' for image" % type
|
|
self.assertTrue(expected in content,
|
|
"Could not find '%s' in '%s'" % (expected, content))
|
|
|
|
# make sure the image was not created
|
|
# Verify no public images
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
images = jsonutils.loads(content)['images']
|
|
self.assertEqual(len(images), 0)
|
|
|
|
def test_post_image_content_bad_container_format(self):
|
|
self._do_test_post_image_content_bad_format('container_format')
|
|
|
|
def test_post_image_content_bad_disk_format(self):
|
|
self._do_test_post_image_content_bad_format('disk_format')
|
|
|
|
def _do_test_put_image_content_missing_format(self, format):
|
|
"""
|
|
We test that missing container/disk format only fails with
|
|
400 "Bad Request" when the image content is PUT (i.e. not
|
|
on the original POST of a queued image).
|
|
|
|
:see https://bugs.launchpad.net/glance/+bug/937216
|
|
"""
|
|
|
|
# POST queued image
|
|
path = "/v1/images"
|
|
headers = {
|
|
'X-Image-Meta-Name': 'Image1',
|
|
'X-Image-Meta-Is-Public': 'True',
|
|
}
|
|
response, content = self.http.request(path, 'POST', headers=headers)
|
|
self.assertEqual(response.status, 201)
|
|
data = jsonutils.loads(content)
|
|
image_id = data['image']['id']
|
|
self.addDetail('image_data', testtools.content.json_content(data))
|
|
|
|
# PUT image content images without given format being specified
|
|
path = "/v1/images/%s" % (image_id)
|
|
headers = minimal_headers('Image1')
|
|
del headers['X-Image-Meta-' + format]
|
|
with tempfile.NamedTemporaryFile() as test_data_file:
|
|
test_data_file.write("XXX")
|
|
test_data_file.flush()
|
|
response, content = self.http.request(path, 'PUT',
|
|
headers=headers,
|
|
body=test_data_file.name)
|
|
self.assertEqual(response.status, 400)
|
|
type = format.replace('_format', '')
|
|
expected = "Invalid %s format 'None' for image" % type
|
|
self.assertTrue(expected in content,
|
|
"Could not find '%s' in '%s'" % (expected, content))
|
|
|
|
def test_put_image_content_bad_container_format(self):
|
|
self._do_test_put_image_content_missing_format('container_format')
|
|
|
|
def test_put_image_content_bad_disk_format(self):
|
|
self._do_test_put_image_content_missing_format('disk_format')
|
|
|
|
def _do_test_mismatched_attribute(self, attribute, value):
|
|
"""
|
|
Test mismatched attribute.
|
|
"""
|
|
|
|
image_data = "*" * FIVE_KB
|
|
headers = minimal_headers('Image1')
|
|
headers[attribute] = value
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'POST', headers=headers,
|
|
body=image_data)
|
|
self.assertEqual(response.status, 400)
|
|
|
|
images_dir = os.path.join(self.test_dir, 'images')
|
|
image_count = len([name for name in os.listdir(images_dir)
|
|
if os.path.isfile(os.path.join(images_dir, name))])
|
|
self.assertEqual(image_count, 0)
|
|
|
|
def test_mismatched_size(self):
|
|
"""
|
|
Test mismatched size.
|
|
"""
|
|
self._do_test_mismatched_attribute('x-image-meta-size',
|
|
str(FIVE_KB + 1))
|
|
|
|
def test_mismatched_checksum(self):
|
|
"""
|
|
Test mismatched checksum.
|
|
"""
|
|
self._do_test_mismatched_attribute('x-image-meta-checksum',
|
|
'foobar')
|
|
|
|
|
|
class TestApiWithFakeAuth(base.ApiTest):
|
|
def __init__(self, *args, **kwargs):
|
|
super(TestApiWithFakeAuth, self).__init__(*args, **kwargs)
|
|
self.api_flavor = 'fakeauth'
|
|
self.registry_flavor = 'fakeauth'
|
|
|
|
def test_ownership(self):
|
|
# Add an image with admin privileges and ensure the owner
|
|
# can be set to something other than what was used to authenticate
|
|
auth_headers = {
|
|
'X-Auth-Token': 'user1:tenant1:admin',
|
|
}
|
|
|
|
create_headers = {
|
|
'X-Image-Meta-Name': 'MyImage',
|
|
'X-Image-Meta-disk_format': 'raw',
|
|
'X-Image-Meta-container_format': 'ovf',
|
|
'X-Image-Meta-Is-Public': 'True',
|
|
'X-Image-Meta-Owner': 'tenant2',
|
|
}
|
|
create_headers.update(auth_headers)
|
|
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'POST',
|
|
headers=create_headers)
|
|
self.assertEqual(response.status, 201)
|
|
data = jsonutils.loads(content)
|
|
image_id = data['image']['id']
|
|
|
|
path = "/v1/images/%s" % (image_id)
|
|
response, content = self.http.request(path, 'HEAD',
|
|
headers=auth_headers)
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual('tenant2', response['x-image-meta-owner'])
|
|
|
|
# Now add an image without admin privileges and ensure the owner
|
|
# cannot be set to something other than what was used to authenticate
|
|
auth_headers = {
|
|
'X-Auth-Token': 'user1:tenant1:role1',
|
|
}
|
|
create_headers.update(auth_headers)
|
|
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'POST',
|
|
headers=create_headers)
|
|
self.assertEqual(response.status, 201)
|
|
data = jsonutils.loads(content)
|
|
image_id = data['image']['id']
|
|
|
|
# We have to be admin to see the owner
|
|
auth_headers = {
|
|
'X-Auth-Token': 'user1:tenant1:admin',
|
|
}
|
|
create_headers.update(auth_headers)
|
|
|
|
path = "/v1/images/%s" % (image_id)
|
|
response, content = self.http.request(path, 'HEAD',
|
|
headers=auth_headers)
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual('tenant1', response['x-image-meta-owner'])
|
|
|
|
# Make sure the non-privileged user can't update their owner either
|
|
update_headers = {
|
|
'X-Image-Meta-Name': 'MyImage2',
|
|
'X-Image-Meta-Owner': 'tenant2',
|
|
'X-Auth-Token': 'user1:tenant1:role1',
|
|
}
|
|
|
|
path = "/v1/images/%s" % (image_id)
|
|
response, content = self.http.request(path, 'PUT',
|
|
headers=update_headers)
|
|
self.assertEqual(response.status, 200)
|
|
|
|
# We have to be admin to see the owner
|
|
auth_headers = {
|
|
'X-Auth-Token': 'user1:tenant1:admin',
|
|
}
|
|
|
|
path = "/v1/images/%s" % (image_id)
|
|
response, content = self.http.request(path, 'HEAD',
|
|
headers=auth_headers)
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual('tenant1', response['x-image-meta-owner'])
|
|
|
|
# An admin user should be able to update the owner
|
|
auth_headers = {
|
|
'X-Auth-Token': 'user1:tenant3:admin',
|
|
}
|
|
|
|
update_headers = {
|
|
'X-Image-Meta-Name': 'MyImage2',
|
|
'X-Image-Meta-Owner': 'tenant2',
|
|
}
|
|
update_headers.update(auth_headers)
|
|
|
|
path = "/v1/images/%s" % (image_id)
|
|
response, content = self.http.request(path, 'PUT',
|
|
headers=update_headers)
|
|
self.assertEqual(response.status, 200)
|
|
|
|
path = "/v1/images/%s" % (image_id)
|
|
response, content = self.http.request(path, 'HEAD',
|
|
headers=auth_headers)
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual('tenant2', response['x-image-meta-owner'])
|
|
|
|
def test_image_visibility_to_different_users(self):
|
|
owners = ['admin', 'tenant1', 'tenant2', 'none']
|
|
visibilities = {'public': 'True', 'private': 'False'}
|
|
image_ids = {}
|
|
|
|
for owner in owners:
|
|
for visibility, is_public in visibilities.items():
|
|
name = '%s-%s' % (owner, visibility)
|
|
headers = {
|
|
'Content-Type': 'application/octet-stream',
|
|
'X-Image-Meta-Name': name,
|
|
'X-Image-Meta-Status': 'active',
|
|
'X-Image-Meta-Is-Public': is_public,
|
|
'X-Image-Meta-Owner': owner,
|
|
'X-Auth-Token': 'createuser:createtenant:admin',
|
|
}
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'POST',
|
|
headers=headers)
|
|
self.assertEqual(response.status, 201)
|
|
data = jsonutils.loads(content)
|
|
image_ids[name] = data['image']['id']
|
|
|
|
def list_images(tenant, role='', is_public=None):
|
|
auth_token = 'user:%s:%s' % (tenant, role)
|
|
headers = {'X-Auth-Token': auth_token}
|
|
path = "/v1/images/detail"
|
|
if is_public is not None:
|
|
path += '?is_public=%s' % is_public
|
|
response, content = self.http.request(path, 'GET', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
return jsonutils.loads(content)['images']
|
|
|
|
# 1. Known user sees public and their own images
|
|
images = list_images('tenant1')
|
|
self.assertEqual(len(images), 5)
|
|
for image in images:
|
|
self.assertTrue(image['is_public'] or image['owner'] == 'tenant1')
|
|
|
|
# 2. Unknown user sees only public images
|
|
images = list_images('none')
|
|
self.assertEqual(len(images), 4)
|
|
for image in images:
|
|
self.assertTrue(image['is_public'])
|
|
|
|
# 3. Unknown admin sees only public images
|
|
images = list_images('none', role='admin')
|
|
self.assertEqual(len(images), 4)
|
|
for image in images:
|
|
self.assertTrue(image['is_public'])
|
|
|
|
# 4. Unknown admin, is_public=none, shows all images
|
|
images = list_images('none', role='admin', is_public='none')
|
|
self.assertEqual(len(images), 8)
|
|
|
|
# 5. Unknown admin, is_public=true, shows only public images
|
|
images = list_images('none', role='admin', is_public='true')
|
|
self.assertEqual(len(images), 4)
|
|
for image in images:
|
|
self.assertTrue(image['is_public'])
|
|
|
|
# 6. Unknown admin, is_public=false, sees only private images
|
|
images = list_images('none', role='admin', is_public='false')
|
|
self.assertEqual(len(images), 4)
|
|
for image in images:
|
|
self.assertFalse(image['is_public'])
|
|
|
|
# 7. Known admin sees public and their own images
|
|
images = list_images('admin', role='admin')
|
|
self.assertEqual(len(images), 5)
|
|
for image in images:
|
|
self.assertTrue(image['is_public'] or image['owner'] == 'admin')
|
|
|
|
# 8. Known admin, is_public=none, shows all images
|
|
images = list_images('admin', role='admin', is_public='none')
|
|
self.assertEqual(len(images), 8)
|
|
|
|
# 9. Known admin, is_public=true, sees all public and their images
|
|
images = list_images('admin', role='admin', is_public='true')
|
|
self.assertEqual(len(images), 5)
|
|
for image in images:
|
|
self.assertTrue(image['is_public'] or image['owner'] == 'admin')
|
|
|
|
# 10. Known admin, is_public=false, sees all private images
|
|
images = list_images('admin', role='admin', is_public='false')
|
|
self.assertEqual(len(images), 4)
|
|
for image in images:
|
|
self.assertFalse(image['is_public'])
|
|
|
|
def test_property_protections(self):
|
|
# Enable property protection
|
|
self.config(property_protection_file=self.property_file)
|
|
self.setUp()
|
|
|
|
CREATE_HEADERS = {
|
|
'X-Image-Meta-Name': 'MyImage',
|
|
'X-Image-Meta-disk_format': 'raw',
|
|
'X-Image-Meta-container_format': 'ovf',
|
|
'X-Image-Meta-Is-Public': 'True',
|
|
'X-Image-Meta-Owner': 'tenant2',
|
|
}
|
|
|
|
# Create an image for role member with extra properties
|
|
# Raises 403 since user is not allowed to create 'foo'
|
|
auth_headers = {
|
|
'X-Auth-Token': 'user1:tenant1:member',
|
|
}
|
|
custom_props = {
|
|
'x-image-meta-property-foo': 'bar'
|
|
}
|
|
auth_headers.update(custom_props)
|
|
auth_headers.update(CREATE_HEADERS)
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'POST',
|
|
headers=auth_headers)
|
|
self.assertEqual(response.status, 403)
|
|
|
|
# Create an image for role member without 'foo'
|
|
auth_headers = {
|
|
'X-Auth-Token': 'user1:tenant1:member',
|
|
}
|
|
custom_props = {
|
|
'x-image-meta-property-x_owner_foo': 'o_s_bar',
|
|
}
|
|
auth_headers.update(custom_props)
|
|
auth_headers.update(CREATE_HEADERS)
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'POST',
|
|
headers=auth_headers)
|
|
self.assertEqual(response.status, 201)
|
|
|
|
# Returned image entity should have 'x_owner_foo'
|
|
data = jsonutils.loads(content)
|
|
self.assertEqual(data['image']['properties']['x_owner_foo'],
|
|
'o_s_bar')
|
|
|
|
# Create an image for role spl_role with extra properties
|
|
auth_headers = {
|
|
'X-Auth-Token': 'user1:tenant1:spl_role',
|
|
}
|
|
custom_props = {
|
|
'X-Image-Meta-Property-spl_create_prop': 'create_bar',
|
|
'X-Image-Meta-Property-spl_read_prop': 'read_bar',
|
|
'X-Image-Meta-Property-spl_update_prop': 'update_bar',
|
|
'X-Image-Meta-Property-spl_delete_prop': 'delete_bar'
|
|
}
|
|
auth_headers.update(custom_props)
|
|
auth_headers.update(CREATE_HEADERS)
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'POST',
|
|
headers=auth_headers)
|
|
self.assertEqual(response.status, 201)
|
|
data = jsonutils.loads(content)
|
|
image_id = data['image']['id']
|
|
|
|
# Attempt to update two properties, one protected(spl_read_prop), the
|
|
# other not(spl_update_prop). Request should be forbidden.
|
|
auth_headers = {
|
|
'X-Auth-Token': 'user1:tenant1:spl_role',
|
|
}
|
|
custom_props = {
|
|
'X-Image-Meta-Property-spl_read_prop': 'r',
|
|
'X-Image-Meta-Property-spl_update_prop': 'u',
|
|
'X-Glance-Registry-Purge-Props': 'False'
|
|
}
|
|
auth_headers.update(auth_headers)
|
|
auth_headers.update(custom_props)
|
|
path = "/v1/images/%s" % image_id
|
|
response, content = self.http.request(path, 'PUT',
|
|
headers=auth_headers)
|
|
self.assertEqual(response.status, 403)
|
|
|
|
# Attempt to create properties which are forbidden
|
|
auth_headers = {
|
|
'X-Auth-Token': 'user1:tenant1:spl_role',
|
|
}
|
|
custom_props = {
|
|
'X-Image-Meta-Property-spl_new_prop': 'new',
|
|
'X-Glance-Registry-Purge-Props': 'True'
|
|
}
|
|
auth_headers.update(auth_headers)
|
|
auth_headers.update(custom_props)
|
|
path = "/v1/images/%s" % image_id
|
|
response, content = self.http.request(path, 'PUT',
|
|
headers=auth_headers)
|
|
self.assertEqual(response.status, 403)
|
|
|
|
# Attempt to update, create and delete properties
|
|
auth_headers = {
|
|
'X-Auth-Token': 'user1:tenant1:spl_role',
|
|
}
|
|
custom_props = {
|
|
'X-Image-Meta-Property-spl_create_prop': 'create_bar',
|
|
'X-Image-Meta-Property-spl_read_prop': 'read_bar',
|
|
'X-Image-Meta-Property-spl_update_prop': 'u',
|
|
'X-Glance-Registry-Purge-Props': 'True'
|
|
}
|
|
auth_headers.update(auth_headers)
|
|
auth_headers.update(custom_props)
|
|
path = "/v1/images/%s" % image_id
|
|
response, content = self.http.request(path, 'PUT',
|
|
headers=auth_headers)
|
|
self.assertEqual(response.status, 200)
|
|
|
|
# Returned image entity should reflect the changes
|
|
image = jsonutils.loads(content)
|
|
|
|
# 'spl_update_prop' has update permission for spl_role
|
|
# hence the value has changed
|
|
self.assertEqual('u', image['image']['properties']['spl_update_prop'])
|
|
|
|
# 'spl_delete_prop' has delete permission for spl_role
|
|
# hence the property has been deleted
|
|
self.assertTrue('spl_delete_prop' not in image['image']['properties'])
|
|
|
|
# 'spl_create_prop' has create permission for spl_role
|
|
# hence the property has been created
|
|
self.assertEqual('create_bar',
|
|
image['image']['properties']['spl_create_prop'])
|
|
|
|
# Image Deletion should work
|
|
auth_headers = {
|
|
'X-Auth-Token': 'user1:tenant1:spl_role',
|
|
}
|
|
path = "/v1/images/%s" % image_id
|
|
response, content = self.http.request(path, 'DELETE',
|
|
headers=auth_headers)
|
|
self.assertEqual(response.status, 200)
|
|
|
|
# This image should be no longer be directly accessible
|
|
auth_headers = {
|
|
'X-Auth-Token': 'user1:tenant1:spl_role',
|
|
}
|
|
path = "/v1/images/%s" % image_id
|
|
response, content = self.http.request(path, 'HEAD',
|
|
headers=auth_headers)
|
|
self.assertEqual(response.status, 404)
|
|
|
|
def test_property_protections_special_chars(self):
|
|
# Enable property protection
|
|
self.config(property_protection_file=self.property_file)
|
|
self.setUp()
|
|
|
|
CREATE_HEADERS = {
|
|
'X-Image-Meta-Name': 'MyImage',
|
|
'X-Image-Meta-disk_format': 'raw',
|
|
'X-Image-Meta-container_format': 'ovf',
|
|
'X-Image-Meta-Is-Public': 'True',
|
|
'X-Image-Meta-Owner': 'tenant2',
|
|
'X-Image-Meta-Size': '0',
|
|
}
|
|
|
|
# Create an image
|
|
auth_headers = {
|
|
'X-Auth-Token': 'user1:tenant1:member',
|
|
}
|
|
auth_headers.update(CREATE_HEADERS)
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'POST',
|
|
headers=auth_headers)
|
|
self.assertEqual(response.status, 201)
|
|
data = jsonutils.loads(content)
|
|
image_id = data['image']['id']
|
|
|
|
# Verify both admin and unknown role can create properties marked with
|
|
# '@'
|
|
auth_headers = {
|
|
'X-Auth-Token': 'user1:tenant1:admin',
|
|
}
|
|
custom_props = {
|
|
'X-Image-Meta-Property-x_all_permitted_admin': '1'
|
|
}
|
|
auth_headers.update(custom_props)
|
|
path = "/v1/images/%s" % image_id
|
|
response, content = self.http.request(path, 'PUT',
|
|
headers=auth_headers)
|
|
self.assertEqual(response.status, 200)
|
|
image = jsonutils.loads(content)
|
|
self.assertEqual('1',
|
|
image['image']['properties']['x_all_permitted_admin'])
|
|
auth_headers = {
|
|
'X-Auth-Token': 'user1:tenant1:joe_soap',
|
|
}
|
|
custom_props = {
|
|
'X-Image-Meta-Property-x_all_permitted_joe_soap': '1',
|
|
'X-Glance-Registry-Purge-Props': 'False'
|
|
}
|
|
auth_headers.update(custom_props)
|
|
path = "/v1/images/%s" % image_id
|
|
response, content = self.http.request(path, 'PUT',
|
|
headers=auth_headers)
|
|
self.assertEqual(response.status, 200)
|
|
image = jsonutils.loads(content)
|
|
self.assertEqual(
|
|
'1', image['image']['properties']['x_all_permitted_joe_soap'])
|
|
|
|
# Verify both admin and unknown role can read properties marked with
|
|
# '@'
|
|
auth_headers = {
|
|
'X-Auth-Token': 'user1:tenant1:admin',
|
|
}
|
|
path = "/v1/images/%s" % image_id
|
|
response, content = self.http.request(path, 'HEAD',
|
|
headers=auth_headers)
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual('1', response.get(
|
|
'x-image-meta-property-x_all_permitted_admin'))
|
|
self.assertEqual('1', response.get(
|
|
'x-image-meta-property-x_all_permitted_joe_soap'))
|
|
auth_headers = {
|
|
'X-Auth-Token': 'user1:tenant1:joe_soap',
|
|
}
|
|
path = "/v1/images/%s" % image_id
|
|
response, content = self.http.request(path, 'HEAD',
|
|
headers=auth_headers)
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual('1', response.get(
|
|
'x-image-meta-property-x_all_permitted_admin'))
|
|
self.assertEqual('1', response.get(
|
|
'x-image-meta-property-x_all_permitted_joe_soap'))
|
|
|
|
# Verify both admin and unknown role can update properties marked with
|
|
# '@'
|
|
auth_headers = {
|
|
'X-Auth-Token': 'user1:tenant1:admin',
|
|
}
|
|
custom_props = {
|
|
'X-Image-Meta-Property-x_all_permitted_admin': '2',
|
|
'X-Glance-Registry-Purge-Props': 'False'
|
|
}
|
|
auth_headers.update(custom_props)
|
|
path = "/v1/images/%s" % image_id
|
|
response, content = self.http.request(path, 'PUT',
|
|
headers=auth_headers)
|
|
self.assertEqual(response.status, 200)
|
|
image = jsonutils.loads(content)
|
|
self.assertEqual('2',
|
|
image['image']['properties']['x_all_permitted_admin'])
|
|
auth_headers = {
|
|
'X-Auth-Token': 'user1:tenant1:joe_soap',
|
|
}
|
|
custom_props = {
|
|
'X-Image-Meta-Property-x_all_permitted_joe_soap': '2',
|
|
'X-Glance-Registry-Purge-Props': 'False'
|
|
}
|
|
auth_headers.update(custom_props)
|
|
path = "/v1/images/%s" % image_id
|
|
response, content = self.http.request(path, 'PUT',
|
|
headers=auth_headers)
|
|
self.assertEqual(response.status, 200)
|
|
image = jsonutils.loads(content)
|
|
self.assertEqual(
|
|
'2', image['image']['properties']['x_all_permitted_joe_soap'])
|
|
|
|
# Verify both admin and unknown role can delete properties marked with
|
|
# '@'
|
|
auth_headers = {
|
|
'X-Auth-Token': 'user1:tenant1:admin',
|
|
}
|
|
custom_props = {
|
|
'X-Image-Meta-Property-x_all_permitted_joe_soap': '2',
|
|
'X-Glance-Registry-Purge-Props': 'True'
|
|
}
|
|
auth_headers.update(custom_props)
|
|
path = "/v1/images/%s" % image_id
|
|
response, content = self.http.request(path, 'PUT',
|
|
headers=auth_headers)
|
|
self.assertEqual(response.status, 200)
|
|
image = jsonutils.loads(content)
|
|
self.assertNotIn('x_all_permitted_admin', image['image']['properties'])
|
|
auth_headers = {
|
|
'X-Auth-Token': 'user1:tenant1:joe_soap',
|
|
}
|
|
custom_props = {
|
|
'X-Glance-Registry-Purge-Props': 'True'
|
|
}
|
|
auth_headers.update(custom_props)
|
|
path = "/v1/images/%s" % image_id
|
|
response, content = self.http.request(path, 'PUT',
|
|
headers=auth_headers)
|
|
self.assertEqual(response.status, 200)
|
|
image = jsonutils.loads(content)
|
|
self.assertNotIn('x_all_permitted_joe_soap',
|
|
image['image']['properties'])
|
|
|
|
# Verify neither admin nor unknown role can create a property protected
|
|
# with '!'
|
|
auth_headers = {
|
|
'X-Auth-Token': 'user1:tenant1:admin',
|
|
}
|
|
custom_props = {
|
|
'X-Image-Meta-Property-x_none_permitted_admin': '1'
|
|
}
|
|
auth_headers.update(custom_props)
|
|
path = "/v1/images/%s" % image_id
|
|
response, content = self.http.request(path, 'PUT',
|
|
headers=auth_headers)
|
|
self.assertEqual(response.status, 403)
|
|
auth_headers = {
|
|
'X-Auth-Token': 'user1:tenant1:joe_soap',
|
|
}
|
|
custom_props = {
|
|
'X-Image-Meta-Property-x_none_permitted_joe_soap': '1'
|
|
}
|
|
auth_headers.update(custom_props)
|
|
path = "/v1/images/%s" % image_id
|
|
response, content = self.http.request(path, 'PUT',
|
|
headers=auth_headers)
|
|
self.assertEqual(response.status, 403)
|
|
|
|
# Verify neither admin nor unknown role can read properties marked with
|
|
# '!'
|
|
auth_headers = {
|
|
'X-Auth-Token': 'user1:tenant1:admin',
|
|
}
|
|
custom_props = {
|
|
'X-Image-Meta-Property-x_none_read': '1'
|
|
}
|
|
auth_headers.update(custom_props)
|
|
auth_headers.update(CREATE_HEADERS)
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'POST',
|
|
headers=auth_headers)
|
|
self.assertEqual(response.status, 201)
|
|
data = jsonutils.loads(content)
|
|
image_id = data['image']['id']
|
|
auth_headers = {
|
|
'X-Auth-Token': 'user1:tenant1:admin',
|
|
}
|
|
path = "/v1/images/%s" % image_id
|
|
response, content = self.http.request(path, 'HEAD',
|
|
headers=auth_headers)
|
|
self.assertEqual(response.status, 200)
|
|
self.assertRaises(KeyError,
|
|
response.get, 'X-Image-Meta-Property-x_none_read')
|
|
auth_headers = {
|
|
'X-Auth-Token': 'user1:tenant1:joe_soap',
|
|
}
|
|
path = "/v1/images/%s" % image_id
|
|
response, content = self.http.request(path, 'HEAD',
|
|
headers=auth_headers)
|
|
self.assertEqual(response.status, 200)
|
|
self.assertRaises(KeyError,
|
|
response.get, 'X-Image-Meta-Property-x_none_read')
|
|
|
|
# Verify neither admin nor unknown role can update properties marked
|
|
# with '!'
|
|
auth_headers = {
|
|
'X-Auth-Token': 'user1:tenant1:admin',
|
|
}
|
|
custom_props = {
|
|
'X-Image-Meta-Property-x_none_update': '1'
|
|
}
|
|
auth_headers.update(custom_props)
|
|
auth_headers.update(CREATE_HEADERS)
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'POST',
|
|
headers=auth_headers)
|
|
self.assertEqual(response.status, 201)
|
|
data = jsonutils.loads(content)
|
|
image_id = data['image']['id']
|
|
auth_headers = {
|
|
'X-Auth-Token': 'user1:tenant1:admin',
|
|
}
|
|
custom_props = {
|
|
'X-Image-Meta-Property-x_none_update': '2'
|
|
}
|
|
auth_headers.update(custom_props)
|
|
path = "/v1/images/%s" % image_id
|
|
response, content = self.http.request(path, 'PUT',
|
|
headers=auth_headers)
|
|
self.assertEqual(response.status, 403)
|
|
auth_headers = {
|
|
'X-Auth-Token': 'user1:tenant1:joe_soap',
|
|
}
|
|
custom_props = {
|
|
'X-Image-Meta-Property-x_none_update': '2'
|
|
}
|
|
auth_headers.update(custom_props)
|
|
path = "/v1/images/%s" % image_id
|
|
response, content = self.http.request(path, 'PUT',
|
|
headers=auth_headers)
|
|
self.assertEqual(response.status, 403)
|
|
|
|
# Verify neither admin nor unknown role can delete properties marked
|
|
# with '!'
|
|
auth_headers = {
|
|
'X-Auth-Token': 'user1:tenant1:admin',
|
|
}
|
|
custom_props = {
|
|
'X-Image-Meta-Property-x_none_delete': '1'
|
|
}
|
|
auth_headers.update(custom_props)
|
|
auth_headers.update(CREATE_HEADERS)
|
|
path = "/v1/images"
|
|
response, content = self.http.request(path, 'POST',
|
|
headers=auth_headers)
|
|
self.assertEqual(response.status, 201)
|
|
data = jsonutils.loads(content)
|
|
image_id = data['image']['id']
|
|
auth_headers = {
|
|
'X-Auth-Token': 'user1:tenant1:admin',
|
|
}
|
|
custom_props = {
|
|
'X-Glance-Registry-Purge-Props': 'True'
|
|
}
|
|
auth_headers.update(custom_props)
|
|
path = "/v1/images/%s" % image_id
|
|
response, content = self.http.request(path, 'PUT',
|
|
headers=auth_headers)
|
|
self.assertEqual(response.status, 403)
|
|
auth_headers = {
|
|
'X-Auth-Token': 'user1:tenant1:joe_soap',
|
|
}
|
|
custom_props = {
|
|
'X-Glance-Registry-Purge-Props': 'True'
|
|
}
|
|
auth_headers.update(custom_props)
|
|
path = "/v1/images/%s" % image_id
|
|
response, content = self.http.request(path, 'PUT',
|
|
headers=auth_headers)
|
|
self.assertEqual(response.status, 403)
|