Consolidate glance.utils into glance.common.utils
Change-Id: I2512b1d3d47f7d3f45707aa5774d9b4b3cb5c26e
This commit is contained in:
parent
45c7280783
commit
bd00433233
@ -43,8 +43,8 @@ gettext.install('glance', unicode=1)
|
|||||||
from glance import client as glance_client
|
from glance import client as glance_client
|
||||||
from glance import version
|
from glance import version
|
||||||
from glance.common import exception
|
from glance.common import exception
|
||||||
from glance.common import utils as common_utils
|
from glance.common import utils
|
||||||
from glance import utils
|
|
||||||
|
|
||||||
SUCCESS = 0
|
SUCCESS = 0
|
||||||
FAILURE = 1
|
FAILURE = 1
|
||||||
@ -214,7 +214,7 @@ EXAMPLES
|
|||||||
return FAILURE
|
return FAILURE
|
||||||
|
|
||||||
image_meta = {'name': fields.pop('name'),
|
image_meta = {'name': fields.pop('name'),
|
||||||
'is_public': common_utils.bool_from_string(
|
'is_public': utils.bool_from_string(
|
||||||
fields.pop('is_public', False)),
|
fields.pop('is_public', False)),
|
||||||
'disk_format': fields.pop('disk_format', 'raw'),
|
'disk_format': fields.pop('disk_format', 'raw'),
|
||||||
'min_disk': fields.pop('min_disk', 0),
|
'min_disk': fields.pop('min_disk', 0),
|
||||||
@ -340,7 +340,7 @@ to spell field names correctly. :)"""
|
|||||||
|
|
||||||
# Have to handle "boolean" values specially...
|
# Have to handle "boolean" values specially...
|
||||||
if 'is_public' in fields:
|
if 'is_public' in fields:
|
||||||
image_meta['is_public'] = common_utils.bool_from_string(
|
image_meta['is_public'] = utils.bool_from_string(
|
||||||
fields.pop('is_public'))
|
fields.pop('is_public'))
|
||||||
|
|
||||||
# Add custom attributes, which are all the arguments remaining
|
# Add custom attributes, which are all the arguments remaining
|
||||||
|
@ -47,7 +47,6 @@ from glance.store import (get_from_backend,
|
|||||||
get_store_from_scheme,
|
get_store_from_scheme,
|
||||||
UnsupportedBackend)
|
UnsupportedBackend)
|
||||||
from glance import registry
|
from glance import registry
|
||||||
from glance import utils
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger('glance.api.v1.images')
|
logger = logging.getLogger('glance.api.v1.images')
|
||||||
@ -596,7 +595,7 @@ class ImageDeserializer(wsgi.JSONRequestDeserializer):
|
|||||||
|
|
||||||
def _deserialize(self, request):
|
def _deserialize(self, request):
|
||||||
result = {}
|
result = {}
|
||||||
result['image_meta'] = utils.get_image_meta_from_headers(request)
|
result['image_meta'] = wsgi.get_image_meta_from_headers(request)
|
||||||
data = request.body_file if self.has_body(request) else None
|
data = request.body_file if self.has_body(request) else None
|
||||||
result['image_data'] = data
|
result['image_data'] = data
|
||||||
return result
|
return result
|
||||||
@ -630,7 +629,7 @@ class ImageSerializer(wsgi.JSONResponseSerializer):
|
|||||||
:param response: The Webob Response object
|
:param response: The Webob Response object
|
||||||
:param image_meta: Mapping of image metadata
|
:param image_meta: Mapping of image metadata
|
||||||
"""
|
"""
|
||||||
headers = utils.image_meta_to_http_headers(image_meta)
|
headers = wsgi.image_meta_to_http_headers(image_meta)
|
||||||
|
|
||||||
for k, v in headers.items():
|
for k, v in headers.items():
|
||||||
response.headers[k] = v
|
response.headers[k] = v
|
||||||
|
@ -26,7 +26,7 @@ import os
|
|||||||
from glance.api.v1 import images as v1_images
|
from glance.api.v1 import images as v1_images
|
||||||
from glance.common import client as base_client
|
from glance.common import client as base_client
|
||||||
from glance.common import exception
|
from glance.common import exception
|
||||||
from glance import utils
|
from glance.common import wsgi
|
||||||
|
|
||||||
#TODO(jaypipes) Allow a logger param for client classes
|
#TODO(jaypipes) Allow a logger param for client classes
|
||||||
|
|
||||||
@ -82,7 +82,7 @@ class V1Client(base_client.BaseClient):
|
|||||||
"""
|
"""
|
||||||
res = self.do_request("GET", "/images/%s" % image_id)
|
res = self.do_request("GET", "/images/%s" % image_id)
|
||||||
|
|
||||||
image = utils.get_image_meta_from_headers(res)
|
image = wsgi.get_image_meta_from_headers(res)
|
||||||
return image, base_client.ImageBodyIterator(res)
|
return image, base_client.ImageBodyIterator(res)
|
||||||
|
|
||||||
def get_image_meta(self, image_id):
|
def get_image_meta(self, image_id):
|
||||||
@ -93,7 +93,7 @@ class V1Client(base_client.BaseClient):
|
|||||||
"""
|
"""
|
||||||
res = self.do_request("HEAD", "/images/%s" % image_id)
|
res = self.do_request("HEAD", "/images/%s" % image_id)
|
||||||
|
|
||||||
image = utils.get_image_meta_from_headers(res)
|
image = wsgi.get_image_meta_from_headers(res)
|
||||||
return image
|
return image
|
||||||
|
|
||||||
def _get_image_size(self, image_data):
|
def _get_image_size(self, image_data):
|
||||||
@ -136,7 +136,7 @@ class V1Client(base_client.BaseClient):
|
|||||||
|
|
||||||
:retval The newly-stored image's metadata.
|
:retval The newly-stored image's metadata.
|
||||||
"""
|
"""
|
||||||
headers = utils.image_meta_to_http_headers(image_meta or {})
|
headers = wsgi.image_meta_to_http_headers(image_meta or {})
|
||||||
|
|
||||||
if image_data:
|
if image_data:
|
||||||
body = image_data
|
body = image_data
|
||||||
@ -159,7 +159,7 @@ class V1Client(base_client.BaseClient):
|
|||||||
if image_meta is None:
|
if image_meta is None:
|
||||||
image_meta = {}
|
image_meta = {}
|
||||||
|
|
||||||
headers = utils.image_meta_to_http_headers(image_meta)
|
headers = wsgi.image_meta_to_http_headers(image_meta)
|
||||||
|
|
||||||
if image_data:
|
if image_data:
|
||||||
body = image_data
|
body = image_data
|
||||||
@ -275,7 +275,7 @@ class V1Client(base_client.BaseClient):
|
|||||||
Pre-fetch a specified image from the cache
|
Pre-fetch a specified image from the cache
|
||||||
"""
|
"""
|
||||||
res = self.do_request("HEAD", "/images/%s" % image_id)
|
res = self.do_request("HEAD", "/images/%s" % image_id)
|
||||||
image = utils.get_image_meta_from_headers(res)
|
image = wsgi.get_image_meta_from_headers(res)
|
||||||
self.do_request("PUT", "/cached_images/%s" % image_id)
|
self.do_request("PUT", "/cached_images/%s" % image_id)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -33,6 +33,9 @@ import uuid
|
|||||||
|
|
||||||
from glance.common import exception
|
from glance.common import exception
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger('glance.utils')
|
||||||
|
|
||||||
TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
|
TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
|
||||||
|
|
||||||
|
|
||||||
@ -128,3 +131,65 @@ def safe_remove(path):
|
|||||||
except OSError, e:
|
except OSError, e:
|
||||||
if e.errno != errno.ENOENT:
|
if e.errno != errno.ENOENT:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
class PrettyTable(object):
|
||||||
|
"""Creates an ASCII art table for use in bin/glance
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
ID Name Size Hits
|
||||||
|
--- ----------------- ------------ -----
|
||||||
|
122 image 22 0
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
self.columns = []
|
||||||
|
|
||||||
|
def add_column(self, width, label="", just='l'):
|
||||||
|
"""Add a column to the table
|
||||||
|
|
||||||
|
:param width: number of characters wide the column should be
|
||||||
|
:param label: column heading
|
||||||
|
:param just: justification for the column, 'l' for left,
|
||||||
|
'r' for right
|
||||||
|
"""
|
||||||
|
self.columns.append((width, label, just))
|
||||||
|
|
||||||
|
def make_header(self):
|
||||||
|
label_parts = []
|
||||||
|
break_parts = []
|
||||||
|
for width, label, _ in self.columns:
|
||||||
|
# NOTE(sirp): headers are always left justified
|
||||||
|
label_part = self._clip_and_justify(label, width, 'l')
|
||||||
|
label_parts.append(label_part)
|
||||||
|
|
||||||
|
break_part = '-' * width
|
||||||
|
break_parts.append(break_part)
|
||||||
|
|
||||||
|
label_line = ' '.join(label_parts)
|
||||||
|
break_line = ' '.join(break_parts)
|
||||||
|
return '\n'.join([label_line, break_line])
|
||||||
|
|
||||||
|
def make_row(self, *args):
|
||||||
|
row = args
|
||||||
|
row_parts = []
|
||||||
|
for data, (width, _, just) in zip(row, self.columns):
|
||||||
|
row_part = self._clip_and_justify(data, width, just)
|
||||||
|
row_parts.append(row_part)
|
||||||
|
|
||||||
|
row_line = ' '.join(row_parts)
|
||||||
|
return row_line
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _clip_and_justify(data, width, just):
|
||||||
|
# clip field to column width
|
||||||
|
clipped_data = str(data)[:width]
|
||||||
|
|
||||||
|
if just == 'r':
|
||||||
|
# right justify
|
||||||
|
justified = clipped_data.rjust(width)
|
||||||
|
else:
|
||||||
|
# left justify
|
||||||
|
justified = clipped_data.ljust(width)
|
||||||
|
|
||||||
|
return justified
|
||||||
|
@ -405,3 +405,73 @@ class Resource(object):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
def image_meta_to_http_headers(image_meta):
|
||||||
|
"""
|
||||||
|
Returns a set of image metadata into a dict
|
||||||
|
of HTTP headers that can be fed to either a Webob
|
||||||
|
Request object or an httplib.HTTP(S)Connection object
|
||||||
|
|
||||||
|
:param image_meta: Mapping of image metadata
|
||||||
|
"""
|
||||||
|
headers = {}
|
||||||
|
for k, v in image_meta.items():
|
||||||
|
if v is None:
|
||||||
|
v = ''
|
||||||
|
if k == 'properties':
|
||||||
|
for pk, pv in v.items():
|
||||||
|
if pv is None:
|
||||||
|
pv = ''
|
||||||
|
headers["x-image-meta-property-%s"
|
||||||
|
% pk.lower()] = unicode(pv)
|
||||||
|
else:
|
||||||
|
headers["x-image-meta-%s" % k.lower()] = unicode(v)
|
||||||
|
return headers
|
||||||
|
|
||||||
|
|
||||||
|
def get_image_meta_from_headers(response):
|
||||||
|
"""
|
||||||
|
Processes HTTP headers from a supplied response that
|
||||||
|
match the x-image-meta and x-image-meta-property and
|
||||||
|
returns a mapping of image metadata and properties
|
||||||
|
|
||||||
|
:param response: Response to process
|
||||||
|
"""
|
||||||
|
result = {}
|
||||||
|
properties = {}
|
||||||
|
|
||||||
|
if hasattr(response, 'getheaders'): # httplib.HTTPResponse
|
||||||
|
headers = response.getheaders()
|
||||||
|
else: # webob.Response
|
||||||
|
headers = response.headers.items()
|
||||||
|
|
||||||
|
for key, value in headers:
|
||||||
|
key = str(key.lower())
|
||||||
|
if key.startswith('x-image-meta-property-'):
|
||||||
|
field_name = key[len('x-image-meta-property-'):].replace('-', '_')
|
||||||
|
properties[field_name] = value or None
|
||||||
|
elif key.startswith('x-image-meta-'):
|
||||||
|
field_name = key[len('x-image-meta-'):].replace('-', '_')
|
||||||
|
result[field_name] = value or None
|
||||||
|
result['properties'] = properties
|
||||||
|
if 'size' in result:
|
||||||
|
result['size'] = int(result['size'])
|
||||||
|
if 'is_public' in result:
|
||||||
|
result['is_public'] = bool_from_header_value(result['is_public'])
|
||||||
|
if 'deleted' in result:
|
||||||
|
result['deleted'] = bool_from_header_value(result['deleted'])
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def bool_from_header_value(value):
|
||||||
|
"""
|
||||||
|
Returns True if value is a boolean True or the
|
||||||
|
string 'true', case-insensitive, False otherwise
|
||||||
|
"""
|
||||||
|
if isinstance(value, bool):
|
||||||
|
return value
|
||||||
|
elif isinstance(value, (basestring, unicode)):
|
||||||
|
if str(value).lower() == 'true':
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
@ -17,100 +17,15 @@
|
|||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from glance import utils
|
from glance.common import utils
|
||||||
from glance.common import utils as common_utils
|
|
||||||
|
|
||||||
|
|
||||||
class TestUtils(unittest.TestCase):
|
class TestUtils(unittest.TestCase):
|
||||||
|
|
||||||
"""Test routines in glance.utils"""
|
"""Test routines in glance.utils"""
|
||||||
|
|
||||||
def test_headers_are_unicode(self):
|
|
||||||
"""
|
|
||||||
Verifies that the headers returned by conversion code are unicode.
|
|
||||||
|
|
||||||
Headers are passed via http in non-testing mode, which automatically
|
|
||||||
converts them to unicode. Verifying that the method does the
|
|
||||||
conversion proves that we aren't passing data that works in tests
|
|
||||||
but will fail in production.
|
|
||||||
"""
|
|
||||||
fixture = {'name': 'fake public image',
|
|
||||||
'is_public': True,
|
|
||||||
'type': 'kernel',
|
|
||||||
'size': 19,
|
|
||||||
'location': "file:///tmp/glance-tests/2",
|
|
||||||
'properties': {'distro': 'Ubuntu 10.04 LTS'}}
|
|
||||||
headers = utils.image_meta_to_http_headers(fixture)
|
|
||||||
for k, v in headers.iteritems():
|
|
||||||
self.assert_(isinstance(v, unicode), "%s is not unicode" % v)
|
|
||||||
|
|
||||||
def test_data_passed_properly_through_headers(self):
|
|
||||||
"""
|
|
||||||
Verifies that data is the same after being passed through headers
|
|
||||||
"""
|
|
||||||
fixture = {'name': 'fake public image',
|
|
||||||
'is_public': True,
|
|
||||||
'deleted': False,
|
|
||||||
'type': 'kernel',
|
|
||||||
'name': None,
|
|
||||||
'size': 19,
|
|
||||||
'location': "file:///tmp/glance-tests/2",
|
|
||||||
'properties': {'distro': 'Ubuntu 10.04 LTS'}}
|
|
||||||
headers = utils.image_meta_to_http_headers(fixture)
|
|
||||||
|
|
||||||
class FakeResponse():
|
|
||||||
pass
|
|
||||||
|
|
||||||
response = FakeResponse()
|
|
||||||
response.headers = headers
|
|
||||||
result = utils.get_image_meta_from_headers(response)
|
|
||||||
for k, v in fixture.iteritems():
|
|
||||||
self.assertEqual(v, result[k])
|
|
||||||
|
|
||||||
def test_boolean_header_values(self):
|
|
||||||
"""
|
|
||||||
Tests that boolean headers like is_public can be set
|
|
||||||
to True if any of ('True', 'On', 1) is provided, case-insensitive
|
|
||||||
"""
|
|
||||||
fixtures = [{'is_public': True},
|
|
||||||
{'is_public': 'True'},
|
|
||||||
{'is_public': 'true'}]
|
|
||||||
|
|
||||||
expected = {'is_public': True}
|
|
||||||
|
|
||||||
class FakeResponse():
|
|
||||||
pass
|
|
||||||
|
|
||||||
for fixture in fixtures:
|
|
||||||
headers = utils.image_meta_to_http_headers(fixture)
|
|
||||||
|
|
||||||
response = FakeResponse()
|
|
||||||
response.headers = headers
|
|
||||||
result = utils.get_image_meta_from_headers(response)
|
|
||||||
for k, v in expected.items():
|
|
||||||
self.assertEqual(v, result[k])
|
|
||||||
|
|
||||||
# Ensure False for other values...
|
|
||||||
fixtures = [{'is_public': False},
|
|
||||||
{'is_public': 'Off'},
|
|
||||||
{'is_public': 'on'},
|
|
||||||
{'is_public': '1'},
|
|
||||||
{'is_public': 'False'}]
|
|
||||||
|
|
||||||
expected = {'is_public': False}
|
|
||||||
|
|
||||||
for fixture in fixtures:
|
|
||||||
headers = utils.image_meta_to_http_headers(fixture)
|
|
||||||
|
|
||||||
response = FakeResponse()
|
|
||||||
response.headers = headers
|
|
||||||
result = utils.get_image_meta_from_headers(response)
|
|
||||||
for k, v in expected.items():
|
|
||||||
self.assertEqual(v, result[k])
|
|
||||||
|
|
||||||
def test_generate_uuid_format(self):
|
def test_generate_uuid_format(self):
|
||||||
"""Check the format of a uuid"""
|
"""Check the format of a uuid"""
|
||||||
uuid = common_utils.generate_uuid()
|
uuid = utils.generate_uuid()
|
||||||
self.assertTrue(isinstance(uuid, basestring))
|
self.assertTrue(isinstance(uuid, basestring))
|
||||||
self.assertTrue(len(uuid), 36)
|
self.assertTrue(len(uuid), 36)
|
||||||
# make sure there are 4 dashes
|
# make sure there are 4 dashes
|
||||||
@ -118,15 +33,15 @@ class TestUtils(unittest.TestCase):
|
|||||||
|
|
||||||
def test_generate_uuid_unique(self):
|
def test_generate_uuid_unique(self):
|
||||||
"""Ensure generate_uuid will return unique values"""
|
"""Ensure generate_uuid will return unique values"""
|
||||||
uuids = [common_utils.generate_uuid() for i in range(5)]
|
uuids = [utils.generate_uuid() for i in range(5)]
|
||||||
# casting to set will drop duplicate values
|
# casting to set will drop duplicate values
|
||||||
unique = set(uuids)
|
unique = set(uuids)
|
||||||
self.assertEqual(len(uuids), len(list(unique)))
|
self.assertEqual(len(uuids), len(list(unique)))
|
||||||
|
|
||||||
def test_is_uuid_like_success(self):
|
def test_is_uuid_like_success(self):
|
||||||
fixture = 'b694bf02-6b01-4905-a50e-fcf7bce7e4d2'
|
fixture = 'b694bf02-6b01-4905-a50e-fcf7bce7e4d2'
|
||||||
self.assertTrue(common_utils.is_uuid_like(fixture))
|
self.assertTrue(utils.is_uuid_like(fixture))
|
||||||
|
|
||||||
def test_is_uuid_like_fails(self):
|
def test_is_uuid_like_fails(self):
|
||||||
fixture = 'pants'
|
fixture = 'pants'
|
||||||
self.assertFalse(common_utils.is_uuid_like(fixture))
|
self.assertFalse(utils.is_uuid_like(fixture))
|
||||||
|
@ -182,3 +182,89 @@ class JSONRequestDeserializerTest(unittest.TestCase):
|
|||||||
actual = wsgi.JSONRequestDeserializer().default(request)
|
actual = wsgi.JSONRequestDeserializer().default(request)
|
||||||
expected = {"body": {"key": "value"}}
|
expected = {"body": {"key": "value"}}
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
|
|
||||||
|
class TestHelpers(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_headers_are_unicode(self):
|
||||||
|
"""
|
||||||
|
Verifies that the headers returned by conversion code are unicode.
|
||||||
|
|
||||||
|
Headers are passed via http in non-testing mode, which automatically
|
||||||
|
converts them to unicode. Verifying that the method does the
|
||||||
|
conversion proves that we aren't passing data that works in tests
|
||||||
|
but will fail in production.
|
||||||
|
"""
|
||||||
|
fixture = {'name': 'fake public image',
|
||||||
|
'is_public': True,
|
||||||
|
'type': 'kernel',
|
||||||
|
'size': 19,
|
||||||
|
'location': "file:///tmp/glance-tests/2",
|
||||||
|
'properties': {'distro': 'Ubuntu 10.04 LTS'}}
|
||||||
|
headers = wsgi.image_meta_to_http_headers(fixture)
|
||||||
|
for k, v in headers.iteritems():
|
||||||
|
self.assert_(isinstance(v, unicode), "%s is not unicode" % v)
|
||||||
|
|
||||||
|
def test_data_passed_properly_through_headers(self):
|
||||||
|
"""
|
||||||
|
Verifies that data is the same after being passed through headers
|
||||||
|
"""
|
||||||
|
fixture = {'name': 'fake public image',
|
||||||
|
'is_public': True,
|
||||||
|
'deleted': False,
|
||||||
|
'type': 'kernel',
|
||||||
|
'name': None,
|
||||||
|
'size': 19,
|
||||||
|
'location': "file:///tmp/glance-tests/2",
|
||||||
|
'properties': {'distro': 'Ubuntu 10.04 LTS'}}
|
||||||
|
headers = wsgi.image_meta_to_http_headers(fixture)
|
||||||
|
|
||||||
|
class FakeResponse():
|
||||||
|
pass
|
||||||
|
|
||||||
|
response = FakeResponse()
|
||||||
|
response.headers = headers
|
||||||
|
result = wsgi.get_image_meta_from_headers(response)
|
||||||
|
for k, v in fixture.iteritems():
|
||||||
|
self.assertEqual(v, result[k])
|
||||||
|
|
||||||
|
def test_boolean_header_values(self):
|
||||||
|
"""
|
||||||
|
Tests that boolean headers like is_public can be set
|
||||||
|
to True if any of ('True', 'On', 1) is provided, case-insensitive
|
||||||
|
"""
|
||||||
|
fixtures = [{'is_public': True},
|
||||||
|
{'is_public': 'True'},
|
||||||
|
{'is_public': 'true'}]
|
||||||
|
|
||||||
|
expected = {'is_public': True}
|
||||||
|
|
||||||
|
class FakeResponse():
|
||||||
|
pass
|
||||||
|
|
||||||
|
for fixture in fixtures:
|
||||||
|
headers = wsgi.image_meta_to_http_headers(fixture)
|
||||||
|
|
||||||
|
response = FakeResponse()
|
||||||
|
response.headers = headers
|
||||||
|
result = wsgi.get_image_meta_from_headers(response)
|
||||||
|
for k, v in expected.items():
|
||||||
|
self.assertEqual(v, result[k])
|
||||||
|
|
||||||
|
# Ensure False for other values...
|
||||||
|
fixtures = [{'is_public': False},
|
||||||
|
{'is_public': 'Off'},
|
||||||
|
{'is_public': 'on'},
|
||||||
|
{'is_public': '1'},
|
||||||
|
{'is_public': 'False'}]
|
||||||
|
|
||||||
|
expected = {'is_public': False}
|
||||||
|
|
||||||
|
for fixture in fixtures:
|
||||||
|
headers = wsgi.image_meta_to_http_headers(fixture)
|
||||||
|
|
||||||
|
response = FakeResponse()
|
||||||
|
response.headers = headers
|
||||||
|
result = wsgi.get_image_meta_from_headers(response)
|
||||||
|
for k, v in expected.items():
|
||||||
|
self.assertEqual(v, result[k])
|
||||||
|
165
glance/utils.py
165
glance/utils.py
@ -1,165 +0,0 @@
|
|||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2010 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.
|
|
||||||
|
|
||||||
"""
|
|
||||||
A few utility routines used throughout Glance
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
logger = logging.getLogger('glance.utils')
|
|
||||||
|
|
||||||
|
|
||||||
def image_meta_to_http_headers(image_meta):
|
|
||||||
"""
|
|
||||||
Returns a set of image metadata into a dict
|
|
||||||
of HTTP headers that can be fed to either a Webob
|
|
||||||
Request object or an httplib.HTTP(S)Connection object
|
|
||||||
|
|
||||||
:param image_meta: Mapping of image metadata
|
|
||||||
"""
|
|
||||||
headers = {}
|
|
||||||
for k, v in image_meta.items():
|
|
||||||
if v is None:
|
|
||||||
v = ''
|
|
||||||
if k == 'properties':
|
|
||||||
for pk, pv in v.items():
|
|
||||||
if pv is None:
|
|
||||||
pv = ''
|
|
||||||
headers["x-image-meta-property-%s"
|
|
||||||
% pk.lower()] = unicode(pv)
|
|
||||||
else:
|
|
||||||
headers["x-image-meta-%s" % k.lower()] = unicode(v)
|
|
||||||
return headers
|
|
||||||
|
|
||||||
|
|
||||||
def get_image_meta_from_headers(response):
|
|
||||||
"""
|
|
||||||
Processes HTTP headers from a supplied response that
|
|
||||||
match the x-image-meta and x-image-meta-property and
|
|
||||||
returns a mapping of image metadata and properties
|
|
||||||
|
|
||||||
:param response: Response to process
|
|
||||||
"""
|
|
||||||
result = {}
|
|
||||||
properties = {}
|
|
||||||
|
|
||||||
if hasattr(response, 'getheaders'): # httplib.HTTPResponse
|
|
||||||
headers = response.getheaders()
|
|
||||||
else: # webob.Response
|
|
||||||
headers = response.headers.items()
|
|
||||||
|
|
||||||
for key, value in headers:
|
|
||||||
key = str(key.lower())
|
|
||||||
if key.startswith('x-image-meta-property-'):
|
|
||||||
field_name = key[len('x-image-meta-property-'):].replace('-', '_')
|
|
||||||
properties[field_name] = value or None
|
|
||||||
elif key.startswith('x-image-meta-'):
|
|
||||||
field_name = key[len('x-image-meta-'):].replace('-', '_')
|
|
||||||
result[field_name] = value or None
|
|
||||||
result['properties'] = properties
|
|
||||||
if 'size' in result:
|
|
||||||
result['size'] = int(result['size'])
|
|
||||||
if 'is_public' in result:
|
|
||||||
result['is_public'] = bool_from_header_value(result['is_public'])
|
|
||||||
if 'deleted' in result:
|
|
||||||
result['deleted'] = bool_from_header_value(result['deleted'])
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def bool_from_header_value(value):
|
|
||||||
"""
|
|
||||||
Returns True if value is a boolean True or the
|
|
||||||
string 'true', case-insensitive, False otherwise
|
|
||||||
"""
|
|
||||||
if isinstance(value, bool):
|
|
||||||
return value
|
|
||||||
elif isinstance(value, (basestring, unicode)):
|
|
||||||
if str(value).lower() == 'true':
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def has_body(req):
|
|
||||||
"""
|
|
||||||
Returns whether a Webob.Request object will possess an entity body.
|
|
||||||
|
|
||||||
:param req: Webob.Request object
|
|
||||||
"""
|
|
||||||
return req.content_length or 'transfer-encoding' in req.headers
|
|
||||||
|
|
||||||
|
|
||||||
class PrettyTable(object):
|
|
||||||
"""Creates an ASCII art table for use in bin/glance
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
ID Name Size Hits
|
|
||||||
--- ----------------- ------------ -----
|
|
||||||
122 image 22 0
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
self.columns = []
|
|
||||||
|
|
||||||
def add_column(self, width, label="", just='l'):
|
|
||||||
"""Add a column to the table
|
|
||||||
|
|
||||||
:param width: number of characters wide the column should be
|
|
||||||
:param label: column heading
|
|
||||||
:param just: justification for the column, 'l' for left,
|
|
||||||
'r' for right
|
|
||||||
"""
|
|
||||||
self.columns.append((width, label, just))
|
|
||||||
|
|
||||||
def make_header(self):
|
|
||||||
label_parts = []
|
|
||||||
break_parts = []
|
|
||||||
for width, label, _ in self.columns:
|
|
||||||
# NOTE(sirp): headers are always left justified
|
|
||||||
label_part = self._clip_and_justify(label, width, 'l')
|
|
||||||
label_parts.append(label_part)
|
|
||||||
|
|
||||||
break_part = '-' * width
|
|
||||||
break_parts.append(break_part)
|
|
||||||
|
|
||||||
label_line = ' '.join(label_parts)
|
|
||||||
break_line = ' '.join(break_parts)
|
|
||||||
return '\n'.join([label_line, break_line])
|
|
||||||
|
|
||||||
def make_row(self, *args):
|
|
||||||
row = args
|
|
||||||
row_parts = []
|
|
||||||
for data, (width, _, just) in zip(row, self.columns):
|
|
||||||
row_part = self._clip_and_justify(data, width, just)
|
|
||||||
row_parts.append(row_part)
|
|
||||||
|
|
||||||
row_line = ' '.join(row_parts)
|
|
||||||
return row_line
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _clip_and_justify(data, width, just):
|
|
||||||
# clip field to column width
|
|
||||||
clipped_data = str(data)[:width]
|
|
||||||
|
|
||||||
if just == 'r':
|
|
||||||
# right justify
|
|
||||||
justified = clipped_data.rjust(width)
|
|
||||||
else:
|
|
||||||
# left justify
|
|
||||||
justified = clipped_data.ljust(width)
|
|
||||||
|
|
||||||
return justified
|
|
Loading…
Reference in New Issue
Block a user