Allows exposing image location based on config

It would be good to allow exposing image location to trusted clients,
say Nova. Thus, eventually helping for internal deployments. This merge
prop enables us to set a config value which will let us either expose
the image location or not expose it.

Edits from markwash:
 * unit test refactoring
 * should not return direct_url as None, that would violate the schema
   which requires direct_url to, when present, be a string

implements bp api-v2-store-access

Change-Id: I69da3b4ce3fc9261cbf448e797e48affa56281ef
This commit is contained in:
Nikhil Komawar 2012-08-05 10:24:16 -05:00 committed by Mark Washenberger
parent 19334f0f8d
commit ae6e288072
6 changed files with 213 additions and 0 deletions

View File

@ -273,6 +273,8 @@ class ResponseSerializer(wsgi.JSONResponseSerializer):
for key in ['id', 'name', 'created_at', 'updated_at', 'tags', 'size', for key in ['id', 'name', 'created_at', 'updated_at', 'tags', 'size',
'owner', 'checksum', 'status']: 'owner', 'checksum', 'status']:
_image[key] = image[key] _image[key] = image[key]
if CONF.show_image_direct_url and image['location']:
_image['direct_url'] = image['location']
_image['visibility'] = 'public' if image['is_public'] else 'private' _image['visibility'] = 'public' if image['is_public'] else 'private'
_image = self.schema.filter(_image) _image = self.schema.filter(_image)
_image['self'] = self._get_image_href(image) _image['self'] = self._get_image_href(image)
@ -380,6 +382,10 @@ _BASE_PROPERTIES = {
'maxLength': 255, 'maxLength': 255,
}, },
}, },
'direct_url': {
'type': 'string',
'description': 'URL to access the image file kept in external store',
},
'self': {'type': 'string'}, 'self': {'type': 'string'},
'access': {'type': 'string'}, 'access': {'type': 'string'},
'file': {'type': 'string'}, 'file': {'type': 'string'},

View File

@ -47,6 +47,10 @@ common_opts = [
cfg.IntOpt('api_limit_max', default=1000, cfg.IntOpt('api_limit_max', default=1000,
help=_('Maximum permissible number of items that could be ' help=_('Maximum permissible number of items that could be '
'returned by a request')), 'returned by a request')),
cfg.BoolOpt('show_image_direct_url', default=False,
help=_('Whether to include the backend image storage location '
'in image properties. Revealing storage location can be a '
'security risk, so use this setting with caution!')),
] ]
CONF = cfg.CONF CONF = cfg.CONF

View File

@ -66,6 +66,7 @@ class Server(object):
self.server_control = './bin/glance-control' self.server_control = './bin/glance-control'
self.exec_env = None self.exec_env = None
self.deployment_flavor = '' self.deployment_flavor = ''
self.show_image_direct_url = False
self.server_control_options = '' self.server_control_options = ''
self.needs_database = False self.needs_database = False
@ -253,6 +254,7 @@ policy_file = %(policy_file)s
policy_default_rule = %(policy_default_rule)s policy_default_rule = %(policy_default_rule)s
db_auto_create = False db_auto_create = False
sql_connection = %(sql_connection)s sql_connection = %(sql_connection)s
show_image_direct_url = %(show_image_direct_url)s
[paste_deploy] [paste_deploy]
flavor = %(deployment_flavor)s flavor = %(deployment_flavor)s
""" """

View File

@ -535,3 +535,123 @@ class TestImages(functional.FunctionalTest):
self.assertEqual(400, response.status_code) self.assertEqual(400, response.status_code)
self.stop_servers() self.stop_servers()
class TestImageDirectURLVisibility(functional.FunctionalTest):
def setUp(self):
super(TestImageDirectURLVisibility, self).setUp()
self.cleanup()
self.api_server.deployment_flavor = 'noauth'
def _url(self, path):
return 'http://127.0.0.1:%d%s' % (self.api_port, path)
def _headers(self, custom_headers=None):
base_headers = {
'X-Identity-Status': 'Confirmed',
'X-Auth-Token': '932c5c84-02ac-4fe5-a9ba-620af0e2bb96',
'X-User-Id': 'f9a41d13-0c13-47e9-bee2-ce4e8bfe958e',
'X-Tenant-Id': TENANT1,
'X-Roles': 'member',
}
base_headers.update(custom_headers or {})
return base_headers
def test_image_direct_url_visible(self):
self.api_server.show_image_direct_url = True
self.start_servers(**self.__dict__.copy())
# Image list should be empty
path = self._url('/v2/images')
response = requests.get(path, headers=self._headers())
self.assertEqual(200, response.status_code)
images = json.loads(response.text)['images']
self.assertEqual(0, len(images))
# Create an image
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json'})
data = json.dumps({'name': 'image-1', 'type': 'kernel', 'foo': 'bar'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(200, response.status_code)
# Get the image id
image = json.loads(response.text)
image_id = image['id']
# Image direct_url should not be visible before location is set
path = self._url('/v2/images/%s' % image_id)
headers = self._headers({'Content-Type': 'application/json'})
response = requests.get(path, headers=headers)
self.assertEqual(200, response.status_code)
image = json.loads(response.text)
self.assertFalse('direct_url' in image)
# Upload some image data, setting the image location
path = self._url('/v2/images/%s/file' % image_id)
headers = self._headers({'Content-Type': 'application/octet-stream'})
response = requests.put(path, headers=headers, data='ZZZZZ')
self.assertEqual(200, response.status_code)
# Image direct_url should be visible
path = self._url('/v2/images/%s' % image_id)
headers = self._headers({'Content-Type': 'application/json'})
response = requests.get(path, headers=headers)
self.assertEqual(200, response.status_code)
image = json.loads(response.text)
self.assertTrue('direct_url' in image)
# Image direct_url should be visible in a list
path = self._url('/v2/images')
headers = self._headers({'Content-Type': 'application/json'})
response = requests.get(path, headers=headers)
self.assertEqual(200, response.status_code)
image = json.loads(response.text)['images'][0]
self.assertTrue('direct_url' in image)
def test_image_direct_url_not_visible(self):
self.api_server.show_image_direct_url = False
self.start_servers(**self.__dict__.copy())
# Image list should be empty
path = self._url('/v2/images')
response = requests.get(path, headers=self._headers())
self.assertEqual(200, response.status_code)
images = json.loads(response.text)['images']
self.assertEqual(0, len(images))
# Create an image
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json'})
data = json.dumps({'name': 'image-1', 'type': 'kernel', 'foo': 'bar'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(200, response.status_code)
# Get the image id
image = json.loads(response.text)
image_id = image['id']
# Upload some image data, setting the image location
path = self._url('/v2/images/%s/file' % image_id)
headers = self._headers({'Content-Type': 'application/octet-stream'})
response = requests.put(path, headers=headers, data='ZZZZZ')
self.assertEqual(200, response.status_code)
# Image direct_url should not be visible
path = self._url('/v2/images/%s' % image_id)
headers = self._headers({'Content-Type': 'application/json'})
response = requests.get(path, headers=headers)
self.assertEqual(200, response.status_code)
image = json.loads(response.text)
self.assertFalse('direct_url' in image)
# Image direct_url should not be visible in a list
path = self._url('/v2/images')
headers = self._headers({'Content-Type': 'application/json'})
response = requests.get(path, headers=headers)
self.assertEqual(200, response.status_code)
image = json.loads(response.text)['images'][0]
self.assertFalse('direct_url' in image)

View File

@ -52,6 +52,7 @@ class TestSchemas(functional.FunctionalTest):
'status', 'status',
'access', 'access',
'schema', 'schema',
'direct_url',
]) ])
self.assertEqual(expected, set(image_schema['properties'].keys())) self.assertEqual(expected, set(image_schema['properties'].keys()))

View File

@ -1284,3 +1284,83 @@ class TestImagesSerializerWithAdditionalProperties(test_utils.BaseTestCase):
response = webob.Response() response = webob.Response()
serializer.show(response, self.fixture) serializer.show(response, self.fixture)
self.assertEqual(expected, json.loads(response.body)) self.assertEqual(expected, json.loads(response.body))
class TestImagesSerializerDirectUrl(test_utils.BaseTestCase):
def setUp(self):
super(TestImagesSerializerDirectUrl, self).setUp()
self.serializer = glance.api.v2.images.ResponseSerializer()
self.active_image = {
'id': unit_test_utils.UUID1,
'name': 'image-1',
'is_public': True,
'properties': {},
'checksum': None,
'owner': TENANT1,
'status': 'active',
'created_at': DATETIME,
'updated_at': DATETIME,
'tags': ['one', 'two'],
'size': 1024,
'location': 'http://some/fake/location',
}
self.queued_image = {
'id': unit_test_utils.UUID2,
'name': 'image-2',
'is_public': False,
'owner': None,
'status': 'queued',
'properties': {},
'checksum': 'ca425b88f047ce8ec45ee90e813ada91',
'created_at': DATETIME,
'updated_at': DATETIME,
'tags': [],
'size': None,
'location': None,
}
self.fixtures = [self.active_image, self.queued_image]
def _do_index(self):
request = webob.Request.blank('/v2/images')
response = webob.Response(request=request)
self.serializer.index(response, {'images': self.fixtures})
return json.loads(response.body)['images']
def _do_show(self, image):
request = webob.Request.blank('/v2/images')
response = webob.Response(request=request)
self.serializer.show(response, image)
return json.loads(response.body)
def test_index_store_location_enabled(self):
self.config(show_image_direct_url=True)
images = self._do_index()
# NOTE(markwash): ordering sanity check
self.assertEqual(images[0]['id'], unit_test_utils.UUID1)
self.assertEqual(images[1]['id'], unit_test_utils.UUID2)
self.assertEqual(images[0]['direct_url'], 'http://some/fake/location')
self.assertFalse('direct_url' in images[1])
def test_index_store_location_explicitly_disabled(self):
self.config(show_image_direct_url=False)
images = self._do_index()
self.assertFalse('direct_url' in images[0])
self.assertFalse('direct_url' in images[1])
def test_show_location_enabled(self):
self.config(show_image_direct_url=True)
image = self._do_show(self.active_image)
self.assertEqual(image['direct_url'], 'http://some/fake/location')
def test_show_location_enabled_but_not_set(self):
self.config(show_image_direct_url=True)
image = self._do_show(self.queued_image)
self.assertFalse('direct_url' in image)
def test_show_location_explicitly_disabled(self):
self.config(show_image_direct_url=False)
image = self._do_show(self.active_image)
self.assertFalse('direct_url' in image)