901 lines
41 KiB
Python
901 lines
41 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
# Copyright 2011 OpenStack, LLC
|
|
# All Rights Reserved.
|
|
#
|
|
# 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.
|
|
|
|
"""Functional test case that verifies private images functionality"""
|
|
|
|
import httplib2
|
|
import json
|
|
import os
|
|
|
|
from glance.tests import functional
|
|
from glance.tests.functional import keystone_utils
|
|
from glance.tests.utils import (execute,
|
|
skip_if_disabled,
|
|
minimal_headers,
|
|
minimal_add_command,
|
|
)
|
|
|
|
FIVE_KB = 5 * 1024
|
|
FIVE_GB = 5 * 1024 * 1024 * 1024
|
|
|
|
|
|
class TestPrivateImagesApi(keystone_utils.KeystoneTests):
|
|
"""
|
|
Functional tests to verify private images functionality.
|
|
"""
|
|
|
|
@skip_if_disabled
|
|
def test_private_images_notadmin(self):
|
|
"""
|
|
Test that we can upload an owned image; that we can manipulate
|
|
its is_public setting; and that appropriate authorization
|
|
checks are applied to other (non-admin) users.
|
|
"""
|
|
self.cleanup()
|
|
self.start_servers()
|
|
|
|
# First, we need to push an image up
|
|
image_data = "*" * FIVE_KB
|
|
headers = minimal_headers('Image1', public=False)
|
|
headers['X-Auth-Token'] = keystone_utils.pattieblack_token
|
|
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'POST', headers=headers,
|
|
body=image_data)
|
|
self.assertEqual(response.status, 201)
|
|
data = json.loads(content)
|
|
self.assertEqual(data['image']['size'], FIVE_KB)
|
|
self.assertEqual(data['image']['name'], "Image1")
|
|
self.assertEqual(data['image']['is_public'], False)
|
|
self.assertEqual(data['image']['owner'], keystone_utils.pattieblack_id)
|
|
|
|
image_id = data['image']['id']
|
|
|
|
# Next, make sure froggy can't list the image
|
|
headers = {'X-Auth-Token': keystone_utils.froggy_token}
|
|
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'GET', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(content, '{"images": []}')
|
|
|
|
# Shouldn't show up in the detail list, either
|
|
headers = {'X-Auth-Token': keystone_utils.froggy_token}
|
|
path = "http://%s:%d/v1/images/detail" % ("0.0.0.0", self.api_port)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'GET', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(content, '{"images": []}')
|
|
|
|
# Also check that froggy can't get the image metadata
|
|
headers = {'X-Auth-Token': keystone_utils.froggy_token}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'HEAD', headers=headers)
|
|
self.assertEqual(response.status, 404)
|
|
|
|
# Froggy shouldn't be able to get the image, either.
|
|
headers = {'X-Auth-Token': keystone_utils.froggy_token}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'GET', headers=headers)
|
|
self.assertEqual(response.status, 404)
|
|
|
|
# Froggy shouldn't be able to give themselves permission too
|
|
# easily...
|
|
headers = {'X-Auth-Token': keystone_utils.froggy_token,
|
|
'X-Image-Meta-Is-Public': 'True'}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'PUT', headers=headers)
|
|
self.assertEqual(response.status, 404)
|
|
|
|
# Froggy shouldn't be able to give themselves ownership,
|
|
# either
|
|
headers = {'X-Auth-Token': keystone_utils.froggy_token,
|
|
'X-Image-Meta-Owner': 'froggy'}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'PUT', headers=headers)
|
|
self.assertEqual(response.status, 404)
|
|
|
|
# Froggy can't delete it, either
|
|
headers = {'X-Auth-Token': keystone_utils.froggy_token}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'DELETE', headers=headers)
|
|
self.assertEqual(response.status, 404)
|
|
|
|
# Pattieblack should be able to see the image in lists
|
|
headers = {'X-Auth-Token': keystone_utils.pattieblack_token}
|
|
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'GET', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
data = json.loads(content)
|
|
self.assertEqual(len(data['images']), 1)
|
|
self.assertEqual(data['images'][0]['id'], image_id)
|
|
self.assertEqual(data['images'][0]['size'], FIVE_KB)
|
|
self.assertEqual(data['images'][0]['name'], "Image1")
|
|
|
|
# And in the detail list
|
|
headers = {'X-Auth-Token': keystone_utils.pattieblack_token}
|
|
path = "http://%s:%d/v1/images/detail" % ("0.0.0.0", self.api_port)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'GET', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
data = json.loads(content)
|
|
self.assertEqual(len(data['images']), 1)
|
|
self.assertEqual(data['images'][0]['size'], FIVE_KB)
|
|
self.assertEqual(data['images'][0]['name'], "Image1")
|
|
self.assertEqual(data['images'][0]['is_public'], False)
|
|
self.assertEqual(data['images'][0]['owner'],
|
|
keystone_utils.pattieblack_id)
|
|
|
|
# Pattieblack should be able to get the image metadata
|
|
headers = {'X-Auth-Token': keystone_utils.pattieblack_token}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'HEAD', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(response['x-image-meta-name'], "Image1")
|
|
self.assertEqual(response['x-image-meta-is_public'], "False")
|
|
self.assertEqual(response['x-image-meta-owner'],
|
|
keystone_utils.pattieblack_id)
|
|
|
|
# And of course the image itself
|
|
headers = {'X-Auth-Token': keystone_utils.pattieblack_token}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'GET', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(content, "*" * FIVE_KB)
|
|
self.assertEqual(response['x-image-meta-name'], "Image1")
|
|
self.assertEqual(response['x-image-meta-is_public'], "False")
|
|
self.assertEqual(response['x-image-meta-owner'],
|
|
keystone_utils.pattieblack_id)
|
|
|
|
# Pattieblack should be able to manipulate is_public
|
|
headers = {'X-Auth-Token': keystone_utils.pattieblack_token,
|
|
'X-Image-Meta-Is-Public': 'True'}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'PUT', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
data = json.loads(content)
|
|
self.assertEqual(data['image']['name'], "Image1")
|
|
self.assertEqual(data['image']['is_public'], True)
|
|
self.assertEqual(data['image']['owner'], keystone_utils.pattieblack_id)
|
|
|
|
# Pattieblack can't give the image away, however
|
|
headers = {'X-Auth-Token': keystone_utils.pattieblack_token,
|
|
'X-Image-Meta-Owner': 'froggy'}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'PUT', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
data = json.loads(content)
|
|
self.assertEqual(data['image']['name'], "Image1")
|
|
self.assertEqual(data['image']['is_public'], True)
|
|
self.assertEqual(data['image']['owner'], keystone_utils.pattieblack_id)
|
|
|
|
# Now that the image is public, froggy can see it
|
|
headers = {'X-Auth-Token': keystone_utils.froggy_token}
|
|
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'GET', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
data = json.loads(content)
|
|
self.assertEqual(len(data['images']), 1)
|
|
self.assertEqual(data['images'][0]['id'], image_id)
|
|
self.assertEqual(data['images'][0]['size'], FIVE_KB)
|
|
self.assertEqual(data['images'][0]['name'], "Image1")
|
|
|
|
# Should also be in details
|
|
headers = {'X-Auth-Token': keystone_utils.froggy_token}
|
|
path = "http://%s:%d/v1/images/detail" % ("0.0.0.0", self.api_port)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'GET', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
data = json.loads(content)
|
|
self.assertEqual(len(data['images']), 1)
|
|
self.assertEqual(data['images'][0]['id'], image_id)
|
|
self.assertEqual(data['images'][0]['size'], FIVE_KB)
|
|
self.assertEqual(data['images'][0]['name'], "Image1")
|
|
self.assertEqual(data['images'][0]['is_public'], True)
|
|
self.assertEqual(data['images'][0]['owner'],
|
|
keystone_utils.pattieblack_id)
|
|
|
|
# Froggy can get the image metadata now...
|
|
headers = {'X-Auth-Token': keystone_utils.froggy_token}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'HEAD', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(response['x-image-meta-name'], "Image1")
|
|
self.assertEqual(response['x-image-meta-is_public'], "True")
|
|
self.assertEqual(response['x-image-meta-owner'],
|
|
keystone_utils.pattieblack_id)
|
|
|
|
# And of course the image itself
|
|
headers = {'X-Auth-Token': keystone_utils.froggy_token}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'GET', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(content, "*" * FIVE_KB)
|
|
self.assertEqual(response['x-image-meta-name'], "Image1")
|
|
self.assertEqual(response['x-image-meta-is_public'], "True")
|
|
self.assertEqual(response['x-image-meta-owner'],
|
|
keystone_utils.pattieblack_id)
|
|
|
|
# Froggy still can't change is-public
|
|
headers = {'X-Auth-Token': keystone_utils.froggy_token,
|
|
'X-Image-Meta-Is-Public': 'True'}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'PUT', headers=headers)
|
|
self.assertEqual(response.status, 404)
|
|
|
|
# Or give themselves ownership
|
|
headers = {'X-Auth-Token': keystone_utils.froggy_token,
|
|
'X-Image-Meta-Owner': 'froggy'}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'PUT', headers=headers)
|
|
self.assertEqual(response.status, 404)
|
|
|
|
# Froggy can't delete it, either
|
|
headers = {'X-Auth-Token': keystone_utils.froggy_token}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'DELETE', headers=headers)
|
|
self.assertEqual(response.status, 404)
|
|
|
|
# But pattieblack can
|
|
headers = {'X-Auth-Token': keystone_utils.pattieblack_token}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'DELETE', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
|
|
self.stop_servers()
|
|
|
|
@skip_if_disabled
|
|
def test_private_images_admin(self):
|
|
"""
|
|
Test that admin users can manipulate is_public and owner
|
|
settings on an image.
|
|
"""
|
|
self.cleanup()
|
|
self.start_servers()
|
|
|
|
# Need to push an image up
|
|
image_data = "*" * FIVE_KB
|
|
headers = minimal_headers('Image1', public=False)
|
|
headers['X-Auth-Token'] = keystone_utils.pattieblack_token
|
|
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'POST', headers=headers,
|
|
body=image_data)
|
|
self.assertEqual(response.status, 201)
|
|
data = json.loads(content)
|
|
self.assertEqual(data['image']['size'], FIVE_KB)
|
|
self.assertEqual(data['image']['name'], "Image1")
|
|
self.assertEqual(data['image']['is_public'], False)
|
|
self.assertEqual(data['image']['owner'], keystone_utils.pattieblack_id)
|
|
|
|
image_id = data['image']['id']
|
|
|
|
# Make sure admin does not see image by default
|
|
headers = {'X-Auth-Token': keystone_utils.admin_token}
|
|
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'GET', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(content, '{"images": []}')
|
|
|
|
# Shouldn't show up in the detail list, either
|
|
headers = {'X-Auth-Token': keystone_utils.froggy_token}
|
|
path = "http://%s:%d/v1/images/detail" % ("0.0.0.0", self.api_port)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'GET', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(content, '{"images": []}')
|
|
|
|
# Admin should see the image if we're looking for private
|
|
# images specifically
|
|
headers = {'X-Auth-Token': keystone_utils.admin_token}
|
|
path = "http://%s:%d/v1/images?is_public=false" % ("0.0.0.0",
|
|
self.api_port)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'GET', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
data = json.loads(content)
|
|
self.assertEqual(len(data['images']), 1)
|
|
self.assertEqual(data['images'][0]['id'], image_id)
|
|
self.assertEqual(data['images'][0]['size'], FIVE_KB)
|
|
self.assertEqual(data['images'][0]['name'], "Image1")
|
|
|
|
# Also in the detail list...
|
|
headers = {'X-Auth-Token': keystone_utils.admin_token}
|
|
path = ("http://%s:%d/v1/images/detail?is_public=false" %
|
|
("0.0.0.0", self.api_port))
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'GET', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
data = json.loads(content)
|
|
self.assertEqual(len(data['images']), 1)
|
|
self.assertEqual(data['images'][0]['size'], FIVE_KB)
|
|
self.assertEqual(data['images'][0]['name'], "Image1")
|
|
self.assertEqual(data['images'][0]['is_public'], False)
|
|
self.assertEqual(data['images'][0]['owner'],
|
|
keystone_utils.pattieblack_id)
|
|
|
|
image_id = data['images'][0]['id']
|
|
|
|
# Admin should be able to get the image metadata
|
|
headers = {'X-Auth-Token': keystone_utils.admin_token}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'HEAD', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(response['x-image-meta-name'], "Image1")
|
|
self.assertEqual(response['x-image-meta-is_public'], "False")
|
|
self.assertEqual(response['x-image-meta-owner'],
|
|
keystone_utils.pattieblack_id)
|
|
|
|
# And of course the image itself
|
|
headers = {'X-Auth-Token': keystone_utils.admin_token}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'GET', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(content, "*" * FIVE_KB)
|
|
self.assertEqual(response['x-image-meta-name'], "Image1")
|
|
self.assertEqual(response['x-image-meta-is_public'], "False")
|
|
self.assertEqual(response['x-image-meta-owner'],
|
|
keystone_utils.pattieblack_id)
|
|
|
|
# Admin should be able to manipulate is_public
|
|
headers = {'X-Auth-Token': keystone_utils.admin_token,
|
|
'X-Image-Meta-Is-Public': 'True'}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'PUT', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
data = json.loads(content)
|
|
self.assertEqual(data['image']['name'], "Image1")
|
|
self.assertEqual(data['image']['is_public'], True)
|
|
self.assertEqual(data['image']['owner'],
|
|
keystone_utils.pattieblack_id)
|
|
|
|
# Admin should also be able to change the ownership of the
|
|
# image
|
|
headers = {'X-Auth-Token': keystone_utils.admin_token,
|
|
'X-Image-Meta-Owner': 'froggy'}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'PUT', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
data = json.loads(content)
|
|
self.assertEqual(data['image']['name'], "Image1")
|
|
self.assertEqual(data['image']['is_public'], True)
|
|
self.assertEqual(data['image']['owner'], 'froggy')
|
|
|
|
# Even setting it to no owner
|
|
headers = {'X-Auth-Token': keystone_utils.admin_token,
|
|
'X-Image-Meta-Owner': ''}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'PUT', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
data = json.loads(content)
|
|
self.assertEqual(data['image']['name'], "Image1")
|
|
self.assertEqual(data['image']['is_public'], True)
|
|
self.assertEqual(data['image']['owner'], None)
|
|
|
|
# Make sure pattieblack can see it, since it's unowned but
|
|
# public
|
|
headers = {'X-Auth-Token': keystone_utils.pattieblack_token}
|
|
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'GET', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
data = json.loads(content)
|
|
self.assertEqual(len(data['images']), 1)
|
|
self.assertEqual(data['images'][0]['id'], image_id)
|
|
self.assertEqual(data['images'][0]['size'], FIVE_KB)
|
|
self.assertEqual(data['images'][0]['name'], "Image1")
|
|
|
|
# But if we change it back to private...
|
|
headers = {'X-Auth-Token': keystone_utils.admin_token,
|
|
'X-Image-Meta-Is-Public': 'False'}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'PUT', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
data = json.loads(content)
|
|
self.assertEqual(data['image']['name'], "Image1")
|
|
self.assertEqual(data['image']['is_public'], False)
|
|
self.assertEqual(data['image']['owner'], None)
|
|
|
|
# Now pattieblack can't see it in the list...
|
|
headers = {'X-Auth-Token': keystone_utils.pattieblack_token}
|
|
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'GET', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(content, '{"images": []}')
|
|
|
|
# Or in the details list...
|
|
headers = {'X-Auth-Token': keystone_utils.pattieblack_token}
|
|
path = "http://%s:%d/v1/images/detail" % ("0.0.0.0", self.api_port)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'GET', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(content, '{"images": []}')
|
|
|
|
# But pattieblack should be able to access the image metadata
|
|
headers = {'X-Auth-Token': keystone_utils.pattieblack_token}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'HEAD', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(response['x-image-meta-name'], "Image1")
|
|
self.assertEqual(response['x-image-meta-is_public'], "False")
|
|
self.assertFalse('x-image-meta-owner' in response)
|
|
|
|
# And of course the image itself
|
|
headers = {'X-Auth-Token': keystone_utils.pattieblack_token}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'GET', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(content, "*" * FIVE_KB)
|
|
self.assertEqual(response['x-image-meta-name'], "Image1")
|
|
self.assertEqual(response['x-image-meta-is_public'], "False")
|
|
self.assertFalse('x-image-meta-owner' in response)
|
|
|
|
# Pattieblack can't change is-public, though
|
|
headers = {'X-Auth-Token': keystone_utils.pattieblack_token,
|
|
'X-Image-Meta-Is-Public': 'True'}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'PUT', headers=headers)
|
|
self.assertEqual(response.status, 404)
|
|
|
|
# Or give themselves ownership
|
|
headers = {'X-Auth-Token': keystone_utils.pattieblack_token,
|
|
'X-Image-Meta-Owner': keystone_utils.pattieblack_id}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'PUT', headers=headers)
|
|
self.assertEqual(response.status, 404)
|
|
|
|
# They can't delete it, either
|
|
headers = {'X-Auth-Token': keystone_utils.pattieblack_token}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'DELETE', headers=headers)
|
|
self.assertEqual(response.status, 404)
|
|
|
|
self.stop_servers()
|
|
|
|
@skip_if_disabled
|
|
def test_private_images_anon(self):
|
|
"""
|
|
Test that anonymous users can access images but not manipulate
|
|
them.
|
|
"""
|
|
self.cleanup()
|
|
self.start_servers()
|
|
|
|
# Make sure anonymous user can't push up an image
|
|
image_data = "*" * FIVE_KB
|
|
headers = minimal_headers('Image1', public=False)
|
|
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'POST', headers=headers,
|
|
body=image_data)
|
|
self.assertEqual(response.status, 403)
|
|
|
|
# Now push up an image for anonymous user to try to access
|
|
image_data = "*" * FIVE_KB
|
|
headers = minimal_headers('Image1', public=False)
|
|
headers['X-Auth-Token'] = keystone_utils.pattieblack_token
|
|
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'POST', headers=headers,
|
|
body=image_data)
|
|
self.assertEqual(response.status, 201)
|
|
data = json.loads(content)
|
|
self.assertEqual(data['image']['size'], FIVE_KB)
|
|
self.assertEqual(data['image']['name'], "Image1")
|
|
self.assertEqual(data['image']['is_public'], False)
|
|
self.assertEqual(data['image']['owner'], keystone_utils.pattieblack_id)
|
|
|
|
image_id = data['image']['id']
|
|
|
|
# Make sure anonymous user can't list the image
|
|
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(content, '{"images": []}')
|
|
|
|
# Shouldn't show up in the detail list, either
|
|
path = "http://%s:%d/v1/images/detail" % ("0.0.0.0", self.api_port)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(content, '{"images": []}')
|
|
|
|
# Also check that anonymous can't get the image metadata
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'HEAD')
|
|
self.assertEqual(response.status, 404)
|
|
|
|
# Nor the image, either.
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'GET')
|
|
self.assertEqual(response.status, 404)
|
|
|
|
# Anonymous shouldn't be able to make the image public...
|
|
headers = {'X-Image-Meta-Is-Public': 'True'}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'PUT', headers=headers)
|
|
self.assertEqual(response.status, 403)
|
|
|
|
# Nor change ownership...
|
|
headers = {'X-Image-Meta-Owner': 'froggy'}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'PUT', headers=headers)
|
|
self.assertEqual(response.status, 403)
|
|
|
|
# Nor even delete it...
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'DELETE')
|
|
self.assertEqual(response.status, 403)
|
|
|
|
# Now, let's use our admin credentials and change the
|
|
# ownership to None...
|
|
headers = {'X-Auth-Token': keystone_utils.admin_token,
|
|
'X-Image-Meta-Owner': ''}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'PUT', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
data = json.loads(content)
|
|
self.assertEqual(data['image']['name'], "Image1")
|
|
self.assertEqual(data['image']['is_public'], False)
|
|
self.assertEqual(data['image']['owner'], None)
|
|
|
|
# Anonymous user still can't list image...
|
|
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(content, '{"images": []}')
|
|
|
|
# Nor see it in details...
|
|
path = "http://%s:%d/v1/images/detail" % ("0.0.0.0", self.api_port)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(content, '{"images": []}')
|
|
|
|
# But they should be able to access the metadata...
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'HEAD')
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(response['x-image-meta-name'], "Image1")
|
|
self.assertEqual(response['x-image-meta-is_public'], "False")
|
|
self.assertFalse('x-image-meta-owner' in response)
|
|
|
|
# And even the image itself...
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(content, "*" * FIVE_KB)
|
|
self.assertEqual(response['x-image-meta-name'], "Image1")
|
|
self.assertEqual(response['x-image-meta-is_public'], "False")
|
|
self.assertFalse('x-image-meta-owner' in response)
|
|
|
|
# Anonymous still shouldn't be able to make the image
|
|
# public...
|
|
headers = {'X-Image-Meta-Is-Public': 'True'}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'PUT', headers=headers)
|
|
self.assertEqual(response.status, 403)
|
|
|
|
# Nor change ownership...
|
|
headers = {'X-Image-Meta-Owner': 'froggy'}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'PUT', headers=headers)
|
|
self.assertEqual(response.status, 403)
|
|
|
|
# Nor even delete it...
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'DELETE')
|
|
self.assertEqual(response.status, 403)
|
|
|
|
# Now make the image public...
|
|
headers = {'X-Auth-Token': keystone_utils.admin_token,
|
|
'X-Image-Meta-Is-Public': 'True'}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'PUT', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
data = json.loads(content)
|
|
self.assertEqual(data['image']['name'], "Image1")
|
|
self.assertEqual(data['image']['is_public'], True)
|
|
self.assertEqual(data['image']['owner'], None)
|
|
|
|
# Now the user should see it in the list...
|
|
path = "http://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
data = json.loads(content)
|
|
self.assertEqual(len(data['images']), 1)
|
|
self.assertEqual(data['images'][0]['id'], image_id)
|
|
self.assertEqual(data['images'][0]['size'], FIVE_KB)
|
|
self.assertEqual(data['images'][0]['name'], "Image1")
|
|
|
|
# Especially in the details...
|
|
path = "http://%s:%d/v1/images/detail" % ("0.0.0.0", self.api_port)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'GET')
|
|
self.assertEqual(response.status, 200)
|
|
data = json.loads(content)
|
|
self.assertEqual(len(data['images']), 1)
|
|
self.assertEqual(data['images'][0]['id'], image_id)
|
|
self.assertEqual(data['images'][0]['size'], FIVE_KB)
|
|
self.assertEqual(data['images'][0]['name'], "Image1")
|
|
self.assertEqual(data['images'][0]['is_public'], True)
|
|
self.assertEqual(data['images'][0]['owner'], None)
|
|
|
|
# But still can't change ownership...
|
|
headers = {'X-Image-Meta-Owner': 'froggy'}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'PUT', headers=headers)
|
|
self.assertEqual(response.status, 403)
|
|
|
|
# Or delete it...
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'DELETE')
|
|
self.assertEqual(response.status, 403)
|
|
|
|
self.stop_servers()
|
|
|
|
|
|
class TestPrivateImagesCli(keystone_utils.KeystoneTests):
|
|
"""
|
|
Functional tests to verify private images functionality through
|
|
bin/glance.
|
|
"""
|
|
|
|
def setUp(self):
|
|
"""
|
|
Clear environment to ensure that pre-existing $OS_* variables
|
|
do not leak into test.
|
|
"""
|
|
self._clear_os_env()
|
|
super(TestPrivateImagesCli, self).setUp()
|
|
|
|
def _do_test_glance_cli(self, cmd):
|
|
"""
|
|
Test that we can upload an owned image with a given command line;
|
|
that we can manipulate its is_public setting; and that appropriate
|
|
authorization checks are applied to other (non-admin) users.
|
|
"""
|
|
self.cleanup()
|
|
self.start_servers()
|
|
|
|
# Add a non-public image using the given glance command line
|
|
exitcode, out, err = execute("echo testdata | %s" % cmd)
|
|
|
|
self.assertEqual(0, exitcode)
|
|
image_id = out.strip()[25:]
|
|
|
|
# Verify the attributes of the image
|
|
headers = {'X-Auth-Token': keystone_utils.pattieblack_token}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'HEAD', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(response['x-image-meta-name'], "MyImage")
|
|
self.assertEqual(response['x-image-meta-is_public'], "False")
|
|
self.assertEqual(response['x-image-meta-owner'],
|
|
keystone_utils.pattieblack_id)
|
|
|
|
image_id = response['x-image-meta-id']
|
|
|
|
# Test that we can update is_public through the CLI
|
|
args = (self.api_port, keystone_utils.pattieblack_token, image_id)
|
|
cmd = "bin/glance --port=%d --auth_token=%s update %s is_public=True"
|
|
exitcode, out, err = execute(cmd % args)
|
|
|
|
self.assertEqual(0, exitcode)
|
|
self.assertEqual('Updated image %s' % image_id, out.strip())
|
|
|
|
# Verify the appropriate change was made
|
|
headers = {'X-Auth-Token': keystone_utils.pattieblack_token}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'HEAD', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(response['x-image-meta-name'], "MyImage")
|
|
self.assertEqual(response['x-image-meta-is_public'], "True")
|
|
self.assertEqual(response['x-image-meta-owner'],
|
|
keystone_utils.pattieblack_id)
|
|
|
|
# Test that admin can change the owner
|
|
args = (self.api_port, keystone_utils.admin_token, image_id)
|
|
cmd = "bin/glance --port=%d --auth_token=%s update %s owner=froggy"
|
|
exitcode, out, err = execute(cmd % args)
|
|
|
|
self.assertEqual(0, exitcode)
|
|
self.assertEqual('Updated image %s' % image_id, out.strip())
|
|
|
|
# Verify the appropriate change was made
|
|
headers = {'X-Auth-Token': keystone_utils.admin_token}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'HEAD', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(response['x-image-meta-name'], "MyImage")
|
|
self.assertEqual(response['x-image-meta-is_public'], "True")
|
|
self.assertEqual(response['x-image-meta-owner'], "froggy")
|
|
|
|
# Test that admin can remove the owner
|
|
args = (self.api_port, keystone_utils.admin_token, image_id)
|
|
cmd = "bin/glance --port=%d --auth_token=%s update %s owner="
|
|
exitcode, out, err = execute(cmd % args)
|
|
|
|
self.assertEqual(0, exitcode)
|
|
self.assertEqual('Updated image %s' % image_id, out.strip())
|
|
|
|
# Verify the appropriate change was made
|
|
headers = {'X-Auth-Token': keystone_utils.admin_token}
|
|
path = "http://%s:%d/v1/images/%s" % ("0.0.0.0", self.api_port,
|
|
image_id)
|
|
http = httplib2.Http()
|
|
response, content = http.request(path, 'HEAD', headers=headers)
|
|
self.assertEqual(response.status, 200)
|
|
self.assertEqual(response['x-image-meta-name'], "MyImage")
|
|
self.assertEqual(response['x-image-meta-is_public'], "True")
|
|
self.assertFalse('x-image-meta-owner' in response)
|
|
|
|
self.stop_servers()
|
|
|
|
def _clear_os_env(self):
|
|
os.environ.pop('OS_AUTH_URL', None)
|
|
os.environ.pop('OS_AUTH_STRATEGY', None)
|
|
os.environ.pop('OS_AUTH_USER', None)
|
|
os.environ.pop('OS_AUTH_KEY', None)
|
|
|
|
@skip_if_disabled
|
|
def test_glance_cli_noauth_strategy(self):
|
|
"""
|
|
Test the CLI with the noauth strategy defaulted to.
|
|
"""
|
|
suffix = '--auth_token=%s' % keystone_utils.pattieblack_token
|
|
cmd = minimal_add_command(self.api_port, 'MyImage', suffix, False)
|
|
self._do_test_glance_cli(cmd)
|
|
|
|
@skip_if_disabled
|
|
def test_glance_cli_keystone_strategy_switches(self):
|
|
"""
|
|
Test the CLI with the keystone (v1) strategy enabled via
|
|
command line switches.
|
|
"""
|
|
substitutions = (self.auth_port, 'keystone',
|
|
'pattieblack', 'secrete')
|
|
suffix = ("--auth_url=http://localhost:%d/v1.0 "
|
|
"--auth_strategy=%s --username=%s --password=%s "
|
|
% substitutions)
|
|
cmd = minimal_add_command(self.api_port, 'MyImage', suffix, False)
|
|
self._do_test_glance_cli(cmd)
|
|
|
|
@skip_if_disabled
|
|
def test_glance_cli_keystone_strategy_environment(self):
|
|
"""
|
|
Test the CLI with the keystone strategy enabled via
|
|
environment variables.
|
|
"""
|
|
os.environ['OS_AUTH_URL'] = 'http://localhost:%d/v1.0' % self.auth_port
|
|
os.environ['OS_AUTH_STRATEGY'] = 'keystone'
|
|
os.environ['OS_AUTH_USER'] = 'pattieblack'
|
|
os.environ['OS_AUTH_KEY'] = 'secrete'
|
|
cmd = minimal_add_command(self.api_port, 'MyImage', public=False)
|
|
self._do_test_glance_cli(cmd)
|
|
|
|
@skip_if_disabled
|
|
def test_glance_cli_keystone_strategy_without_auth_url(self):
|
|
"""
|
|
Test the CLI with the keystone strategy enabled but
|
|
auth url missing.
|
|
"""
|
|
substitutions = (self.api_port, 'keystone', 'pattieblack', 'secrete')
|
|
cmd = ("bin/glance --port=%d --auth_strategy=%s "
|
|
"--username=%s --password=%s index" % substitutions)
|
|
|
|
exitcode, out, err = execute(cmd, raise_error=False)
|
|
|
|
self.assertEqual(1, exitcode)
|
|
|
|
msg = ("--auth_url option or OS_AUTH_URL environment variable "
|
|
"required when keystone authentication strategy is enabled")
|
|
self.assertTrue(msg in err, 'expected "%s" in "%s"' % (msg, err))
|