summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--glanceclient/v1/images.py52
-rw-r--r--glanceclient/v1/shell.py64
-rw-r--r--setup.py16
-rw-r--r--tests/v1/test_images.py38
-rw-r--r--tests/v1/utils.py16
5 files changed, 142 insertions, 44 deletions
diff --git a/glanceclient/v1/images.py b/glanceclient/v1/images.py
index 44b0aaf..459fde0 100644
--- a/glanceclient/v1/images.py
+++ b/glanceclient/v1/images.py
@@ -13,10 +13,15 @@
# License for the specific language governing permissions and limitations
# under the License.
+import copy
import urllib
from glanceclient.common import base
+CREATE_PARAMS = ['id', 'name', 'disk_format', 'container_format', 'min_disk',
+ 'min_ram', 'owner', 'size', 'is_public', 'protected',
+ 'location', 'checksum', 'copy_from', 'properties']
+
class Image(base.Resource):
def __repr__(self):
@@ -45,11 +50,11 @@ class ImageManager(base.Manager):
def _image_meta_to_headers(self, fields):
headers = {}
- for key, value in fields.iteritems():
- if key == 'properties':
- headers['x-image-meta-property-%s' % key] = value
- else:
- headers['x-image-meta-%s' % key] = value
+ fields_copy = copy.deepcopy(fields)
+ for key, value in fields_copy.pop('properties', {}).iteritems():
+ headers['x-image-meta-property-%s' % key] = str(value)
+ for key, value in fields_copy.iteritems():
+ headers['x-image-meta-%s' % key] = str(value)
return headers
def get(self, image_id):
@@ -73,7 +78,7 @@ class ImageManager(base.Manager):
if limit:
params['limit'] = int(limit)
if marker:
- params['marker'] = int(marker)
+ params['marker'] = marker
query = '?%s' % urllib.urlencode(params) if params else ''
return self._list('/v1/images/detail%s' % query, "images")
@@ -82,13 +87,25 @@ class ImageManager(base.Manager):
self._delete("/v1/images/%s" % base.getid(image))
def create(self, **kwargs):
- """Create an image"""
+ """Create an image
+
+ TODO(bcwaldon): document accepted params
+ """
fields = {}
- if 'name' in kwargs:
- fields['name'] = kwargs['name']
- resp, body = self.api.post('/v1/images', body={'image': fields})
- meta = self._image_meta_from_headers(resp)
- return Image(self, meta)
+ for field in kwargs:
+ if field in CREATE_PARAMS:
+ fields[field] = kwargs[field]
+ else:
+ msg = 'create() got an unexpected keyword argument \'%s\''
+ raise TypeError(msg % field)
+
+ copy_from = fields.pop('copy_from', None)
+ hdrs = self._image_meta_to_headers(fields)
+ if copy_from is not None:
+ hdrs['x-glance-api-copy-from'] = copy_from
+
+ resp, body = self.api.post('/v1/images', headers=hdrs)
+ return Image(self, body['image'])
def update(self, image, **kwargs):
"""Update an image"""
@@ -100,14 +117,3 @@ class ImageManager(base.Manager):
resp, body = self.api.put(url, headers=send_meta)
recv_meta = self._image_meta_from_headers(resp)
return Image(self, recv_meta)
-
- def delete_member(self, image, image_member):
- """Remove a member from an image"""
- image_id = base.getid(image)
- try:
- member_id = image_member.member_id
- except AttributeError:
- member_id = image_member
-
- url = '/v1/images/%s/members/%s' % (image_id, member_id)
- resp, body = self.api.delete(url)
diff --git a/glanceclient/v1/shell.py b/glanceclient/v1/shell.py
index 14488ee..be32fff 100644
--- a/glanceclient/v1/shell.py
+++ b/glanceclient/v1/shell.py
@@ -16,6 +16,7 @@
import copy
from glanceclient.common import utils
+import glanceclient.v1.images
def do_image_list(gc, args):
@@ -26,7 +27,7 @@ def do_image_list(gc, args):
def _image_show(image):
- # Flatten image properties dict
+ # Flatten image properties dict for display
info = copy.deepcopy(image._info)
for (k, v) in info.pop('properties').iteritems():
info['Property \'%s\'' % k] = v
@@ -41,10 +42,61 @@ def do_image_show(gc, args):
_image_show(image)
-@utils.arg('--id', metavar='<IMAGE_ID>', help='ID of image to reserve.')
-@utils.arg('--name', metavar='<NAME>', help='Name of image.')
-@utils.arg('--disk_format', metavar='<CONTAINER_FORMAT>', help='Disk format of image.')
-@utils.arg('--container_format', metavar='<DISK_FORMAT>', help='Container format of image.')
+@utils.arg('--id', metavar='<IMAGE_ID>',
+ help='ID of image to reserve.')
+@utils.arg('--name', metavar='<NAME>',
+ help='Name of image.')
+@utils.arg('--disk_format', metavar='<CONTAINER_FORMAT>',
+ help='Disk format of image.')
+@utils.arg('--container_format', metavar='<DISK_FORMAT>',
+ help='Container format of image.')
+@utils.arg('--owner', metavar='<TENANT_ID>',
+ help='Tenant who should own image.')
+@utils.arg('--size', metavar='<SIZE>',
+ help='Size of image data (in bytes).')
+@utils.arg('--min_disk', metavar='<DISK_GB>',
+ help='Minimum size of disk needed to boot image (in gigabytes).')
+@utils.arg('--min_ram', metavar='<DISK_RAM>',
+ help='Minimum amount of ram needed to boot image (in megabytes).')
+@utils.arg('--location', metavar='<IMAGE_URL>',
+ help=('URL where the data for this image already resides.'
+ ' For example, if the image data is stored in the filesystem'
+ ' local to the glance server at \'/usr/share/image.tar.gz\','
+ ' you would specify \'file:///usr/share/image.tar.gz\'.'))
+@utils.arg('--checksum', metavar='<CHECKSUM>',
+ help='Hash of image data used Glance can use for verification.')
+@utils.arg('--copy_from', metavar='<IMAGE_URL>',
+ help=('Similar to \'--location\' in usage, but this indicates that'
+ ' the Glance server should immediately copy the data and'
+ ' store it in its configured image store.'))
+@utils.arg('--public', action='store_true', default=False,
+ help='Make image accessible to the public.')
+@utils.arg('--protected', action='store_true', default=False,
+ help='Prevent image from being deleted.')
+@utils.arg('--property', metavar="<key=value>", action='append', default=[],
+ help=("Arbitrary property to associate with image. "
+ "May be used multiple times."))
def do_image_create(gc, args):
- image = gc.images.create(*args)
+ # Filter out None values
+ fields = dict(filter(lambda x: x[1] is not None, vars(args).items()))
+
+ fields['is_public'] = fields.pop('public')
+
+ raw_properties = fields.pop('property')
+ fields['properties'] = {}
+ for datum in raw_properties:
+ key, value = datum.split('=', 1)
+ fields['properties'][key] = value
+
+ # Filter out values we can't use
+ CREATE_PARAMS = glanceclient.v1.images.CREATE_PARAMS
+ fields = dict(filter(lambda x: x[0] in CREATE_PARAMS, fields.items()))
+
+ image = gc.images.create(**fields)
_image_show(image)
+
+
+@utils.arg('id', metavar='<IMAGE_ID>', help='ID of image to delete.')
+def do_image_delete(gc, args):
+ """Delete a specific image."""
+ gc.images.delete(args.id)
diff --git a/setup.py b/setup.py
index 932c1fb..03cfc3e 100644
--- a/setup.py
+++ b/setup.py
@@ -2,15 +2,15 @@ import os
import setuptools
-from glanceclient.openstack.common.setup import parse_requirements
-from glanceclient.openstack.common.setup import parse_dependency_links
-from glanceclient.openstack.common.setup import write_requirements
+#from glanceclient.openstack.common.setup import parse_requirements
+#from glanceclient.openstack.common.setup import parse_dependency_links
+#from glanceclient.openstack.common.setup import write_requirements
from glanceclient.openstack.common.setup import write_git_changelog
-requires = parse_requirements()
-dependency_links = parse_dependency_links()
-write_requirements()
+#requires = parse_requirements()
+#dependency_links = parse_dependency_links()
+#write_requirements()
write_git_changelog()
@@ -36,8 +36,8 @@ setuptools.setup(
'Operating System :: OS Independent',
'Programming Language :: Python',
],
- install_requires=requires,
- dependency_links=dependency_links,
+# install_requires=requires,
+# dependency_links=dependency_links,
test_suite="nose.collector",
entry_points={'console_scripts': ['glance = glanceclient.shell:main']},
)
diff --git a/tests/v1/test_images.py b/tests/v1/test_images.py
index 8ade8ab..1c51fab 100644
--- a/tests/v1/test_images.py
+++ b/tests/v1/test_images.py
@@ -43,12 +43,44 @@ class ImageManagerTest(unittest.TestCase):
expect = [('DELETE', '/v1/images/1', {}, None)]
self.assertEqual(self.api.calls, expect)
- def test_create(self):
- image = self.mgr.create(name='image-1')
- expect = [('POST', '/v1/images', {}, {'image': {'name': 'image-1'}})]
+ def test_create_no_data(self):
+ params = {
+ 'id': '1',
+ 'name': 'image-1',
+ 'container_format': 'ovf',
+ 'disk_format': 'vhd',
+ 'owner': 'asdf',
+ 'size': 1024,
+ 'min_ram': 512,
+ 'min_disk': 10,
+ 'copy_from': 'http://example.com',
+ 'properties': {'a': 'b', 'c': 'd'},
+ }
+ image = self.mgr.create(**params)
+ expect_headers = {
+ 'x-image-meta-id': '1',
+ 'x-image-meta-name': 'image-1',
+ 'x-image-meta-container_format': 'ovf',
+ 'x-image-meta-disk_format': 'vhd',
+ 'x-image-meta-owner': 'asdf',
+ 'x-image-meta-size': '1024',
+ 'x-image-meta-min_ram': '512',
+ 'x-image-meta-min_disk': '10',
+ 'x-glance-api-copy-from': 'http://example.com',
+ 'x-image-meta-property-a': 'b',
+ 'x-image-meta-property-c': 'd',
+ }
+ expect = [('POST', '/v1/images', expect_headers, None)]
self.assertEqual(self.api.calls, expect)
self.assertEqual(image.id, '1')
self.assertEqual(image.name, 'image-1')
+ self.assertEqual(image.container_format, 'ovf')
+ self.assertEqual(image.disk_format, 'vhd')
+ self.assertEqual(image.owner, 'asdf')
+ self.assertEqual(image.size, '1024')
+ self.assertEqual(image.min_ram, '512')
+ self.assertEqual(image.min_disk, '10')
+ self.assertEqual(image.properties, {'a': 'b', 'c': 'd'})
def test_update(self):
image = self.mgr.update('1', name='image-2')
diff --git a/tests/v1/utils.py b/tests/v1/utils.py
index c06b075..7598aa5 100644
--- a/tests/v1/utils.py
+++ b/tests/v1/utils.py
@@ -18,11 +18,19 @@ fixtures = {
'POST': (
{
'location': '/v1/images/1',
- 'x-image-meta-id': '1',
- 'x-image-meta-name': 'image-1',
- 'x-image-meta-property-arch': 'x86_64',
},
- None),
+ {'image': {
+ 'id': '1',
+ 'name': 'image-1',
+ 'container_format': 'ovf',
+ 'disk_format': 'vhd',
+ 'owner': 'asdf',
+ 'size': '1024',
+ 'min_ram': '512',
+ 'min_disk': '10',
+ 'properties': {'a': 'b', 'c': 'd'},
+
+ }}),
},
'/v1/images/detail': {
'GET': (