Drop Glance Client

* Completely drop the legacy Glance client tool
* bin/glance is gone
* glance/client.py is gone
* Drop relevant tests

Implements bp separate-client

Change-Id: Ifcb0bd9bb537e0243aeb5daf466f46868d522986
This commit is contained in:
Brian Waldon 2012-09-07 17:21:34 -07:00
parent 31338e4ac7
commit 76c3620c7e
10 changed files with 72 additions and 4200 deletions

1087
bin/glance

File diff suppressed because it is too large Load Diff

View File

@ -1,451 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010-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.
"""
Client classes for callers of a Glance system
"""
import errno
import httplib
import json
import os
import socket
import sys
import warnings
import glance.api.v1
from glance.common import animation
from glance.common import client as base_client
from glance.common import exception
from glance.common import utils
SUPPORTED_PARAMS = glance.api.v1.SUPPORTED_PARAMS
SUPPORTED_FILTERS = glance.api.v1.SUPPORTED_FILTERS
warn_msg = ("The 'glance.client' module is deprecated in favor of the "
"'glanceclient' module provided by python-glanceclient (see "
"http://github.com/openstack/python-glanceclient).")
warnings.warn(warn_msg, stacklevel=2)
class V1Client(base_client.BaseClient):
"""Main client class for accessing Glance resources"""
DEFAULT_PORT = 9292
DEFAULT_DOC_ROOT = "/v1"
def get_images(self, **kwargs):
"""
Returns a list of image id/name mappings from Registry
:param filters: dictionary of attributes by which the resulting
collection of images should be filtered
:param marker: id after which to start the page of images
:param limit: maximum number of items to return
:param sort_key: results will be ordered by this image attribute
:param sort_dir: direction in which to to order results (asc, desc)
"""
params = self._extract_params(kwargs, SUPPORTED_PARAMS)
res = self.do_request("GET", "/images", params=params)
data = json.loads(res.read())['images']
return data
def get_images_detailed(self, **kwargs):
"""
Returns a list of detailed image data mappings from Registry
:param filters: dictionary of attributes by which the resulting
collection of images should be filtered
:param marker: id after which to start the page of images
:param limit: maximum number of items to return
:param sort_key: results will be ordered by this image attribute
:param sort_dir: direction in which to to order results (asc, desc)
"""
params = self._extract_params(kwargs, SUPPORTED_PARAMS)
res = self.do_request("GET", "/images/detail", params=params)
data = json.loads(res.read())['images']
return data
def get_image(self, image_id):
"""
Returns a tuple with the image's metadata and the raw disk image as
a mime-encoded blob stream for the supplied opaque image identifier.
:param image_id: The opaque image identifier
:retval Tuple containing (image_meta, image_blob)
:raises exception.NotFound if image is not found
"""
res = self.do_request("GET", "/images/%s" % image_id)
image = utils.get_image_meta_from_headers(res)
return image, base_client.ImageBodyIterator(res)
def get_image_meta(self, image_id):
"""
Returns a mapping of image metadata from Registry
:raises exception.NotFound if image is not in registry
"""
res = self.do_request("HEAD", "/images/%s" % image_id)
image = utils.get_image_meta_from_headers(res)
return image
def _get_image_size(self, image_data):
"""
Analyzes the incoming image file and attempts to determine
its size.
:param image_data: The input to the client, typically a file
redirected from stdin.
:retval The image file's size or None if it cannot be determined.
"""
# For large images, we need to supply the size of the
# image file. See LP Bugs #827660 and #845788.
if hasattr(image_data, 'seek') and hasattr(image_data, 'tell'):
try:
image_data.seek(0, os.SEEK_END)
image_size = image_data.tell()
image_data.seek(0)
return image_size
except IOError, e:
if e.errno == errno.ESPIPE:
# Illegal seek. This means the user is trying
# to pipe image data to the client, e.g.
# echo testdata | bin/glance add blah..., or
# that stdin is empty
return None
else:
raise
def add_image(self, image_meta=None, image_data=None, features=None):
"""
Tells Glance about an image's metadata as well
as optionally the image_data itself
:param image_meta: Optional Mapping of information about the
image
:param image_data: Optional string of raw image data
or file-like object that can be
used to read the image data
:param features: Optional map of features
:retval The newly-stored image's metadata.
"""
headers = utils.image_meta_to_http_headers(image_meta or {})
if image_data:
body = image_data
headers['content-type'] = 'application/octet-stream'
image_size = self._get_image_size(image_data)
if image_size:
headers['x-image-meta-size'] = image_size
headers['content-length'] = image_size
else:
body = None
utils.add_features_to_http_headers(features, headers)
res = self.do_request("POST", "/images", body, headers)
data = json.loads(res.read())
return data['image']
def update_image(self, image_id, image_meta=None, image_data=None,
features=None):
"""
Updates Glance's information about an image
:param image_id: Required image ID
:param image_meta: Optional Mapping of information about the
image
:param image_data: Optional string of raw image data
or file-like object that can be
used to read the image data
:param features: Optional map of features
"""
if image_meta is None:
image_meta = {}
headers = utils.image_meta_to_http_headers(image_meta)
if image_data:
body = image_data
headers['content-type'] = 'application/octet-stream'
image_size = self._get_image_size(image_data)
if image_size:
headers['x-image-meta-size'] = image_size
headers['content-length'] = image_size
else:
body = None
utils.add_features_to_http_headers(features, headers)
res = self.do_request("PUT", "/images/%s" % image_id, body, headers)
data = json.loads(res.read())
return data['image']
def delete_image(self, image_id):
"""
Deletes Glance's information about an image
"""
self.do_request("DELETE", "/images/%s" % image_id)
return True
def get_cached_images(self, **kwargs):
"""
Returns a list of images stored in the image cache.
"""
res = self.do_request("GET", "/cached_images")
data = json.loads(res.read())['cached_images']
return data
def get_queued_images(self, **kwargs):
"""
Returns a list of images queued for caching
"""
res = self.do_request("GET", "/queued_images")
data = json.loads(res.read())['queued_images']
return data
def delete_cached_image(self, image_id):
"""
Delete a specified image from the cache
"""
self.do_request("DELETE", "/cached_images/%s" % image_id)
return True
def delete_all_cached_images(self):
"""
Delete all cached images
"""
res = self.do_request("DELETE", "/cached_images")
data = json.loads(res.read())
num_deleted = data['num_deleted']
return num_deleted
def queue_image_for_caching(self, image_id):
"""
Queue an image for prefetching into cache
"""
self.do_request("PUT", "/queued_images/%s" % image_id)
return True
def delete_queued_image(self, image_id):
"""
Delete a specified image from the cache queue
"""
self.do_request("DELETE", "/queued_images/%s" % image_id)
return True
def delete_all_queued_images(self):
"""
Delete all queued images
"""
res = self.do_request("DELETE", "/queued_images")
data = json.loads(res.read())
num_deleted = data['num_deleted']
return num_deleted
def get_image_members(self, image_id):
"""Returns a mapping of image memberships from Registry"""
res = self.do_request("GET", "/images/%s/members" % image_id)
data = json.loads(res.read())['members']
return data
def get_member_images(self, member_id):
"""Returns a mapping of image memberships from Registry"""
res = self.do_request("GET", "/shared-images/%s" % member_id)
data = json.loads(res.read())['shared_images']
return data
def _validate_assocs(self, assocs):
"""
Validates membership associations and returns an appropriate
list of associations to send to the server.
"""
validated = []
for assoc in assocs:
assoc_data = dict(member_id=assoc['member_id'])
if 'can_share' in assoc:
assoc_data['can_share'] = bool(assoc['can_share'])
validated.append(assoc_data)
return validated
def replace_members(self, image_id, *assocs):
"""
Replaces the membership associations for a given image_id.
Each subsequent argument is a dictionary mapping containing a
'member_id' that should have access to the image_id. A
'can_share' boolean can also be specified to allow the member
to further share the image. An example invocation allowing
'rackspace' to access image 1 and 'google' to access image 1
with permission to share::
c = glance.client.Client(...)
c.update_members(1, {'member_id': 'rackspace'},
{'member_id': 'google', 'can_share': True})
"""
# Understand the associations
body = json.dumps(self._validate_assocs(assocs))
self.do_request("PUT", "/images/%s/members" % image_id, body,
{'content-type': 'application/json'})
return True
def add_member(self, image_id, member_id, can_share=None):
"""
Adds a membership association between image_id and member_id.
If can_share is not specified and the association already
exists, no change is made; if the association does not already
exist, one is created with can_share defaulting to False.
When can_share is specified, the association is created if it
doesn't already exist, and the can_share attribute is set
accordingly. Example invocations allowing 'rackspace' to
access image 1 and 'google' to access image 1 with permission
to share::
c = glance.client.Client(...)
c.add_member(1, 'rackspace')
c.add_member(1, 'google', True)
"""
body = None
headers = {}
# Generate the body if appropriate
if can_share is not None:
body = json.dumps(dict(member=dict(can_share=bool(can_share))))
headers['content-type'] = 'application/json'
self.do_request("PUT", "/images/%s/members/%s" %
(image_id, member_id), body, headers)
return True
def delete_member(self, image_id, member_id):
"""
Deletes the membership assocation. If the
association does not exist, no action is taken; otherwise, the
indicated association is deleted. An example invocation
removing the accesses of 'rackspace' to image 1 and 'google'
to image 2::
c = glance.client.Client(...)
c.delete_member(1, 'rackspace')
c.delete_member(2, 'google')
"""
self.do_request("DELETE", "/images/%s/members/%s" %
(image_id, member_id))
return True
class ProgressIteratorWrapper(object):
def __init__(self, wrapped, transfer_info):
self.wrapped = wrapped
self.transfer_info = transfer_info
self.prev_len = 0L
def __iter__(self):
for chunk in self.wrapped:
if self.prev_len:
self.transfer_info['so_far'] += self.prev_len
self.prev_len = len(chunk)
yield chunk
# report final chunk
self.transfer_info['so_far'] += self.prev_len
class ProgressClient(V1Client):
"""
Specialized class that adds progress bar output/interaction into the
TTY of the calling client
"""
def image_iterator(self, connection, headers, body):
wrapped = super(ProgressClient, self).image_iterator(connection,
headers,
body)
try:
# spawn the animation thread if the connection is good
connection.connect()
return ProgressIteratorWrapper(wrapped,
self.start_animation(headers))
except (httplib.HTTPResponse, socket.error):
# the connection is out, just "pass"
# and let the "glance add" fail with [Errno 111] Connection refused
pass
def start_animation(self, headers):
transfer_info = {
'so_far': 0L,
'size': headers.get('x-image-meta-size', 0L)
}
pg = animation.UploadProgressStatus(transfer_info)
if transfer_info['size'] == 0L:
sys.stdout.write("The progressbar doesn't show-up because "
"the headers[x-meta-size] is zero or missing\n")
sys.stdout.write("Uploading image '%s'\n" %
headers.get('x-image-meta-name', ''))
pg.start()
return transfer_info
Client = V1Client
def get_client(host, port=None, timeout=None, use_ssl=False, username=None,
password=None, tenant=None,
auth_url=None, auth_strategy=None,
auth_token=None, region=None,
is_silent_upload=False, insecure=False):
"""
Returns a new client Glance client object based on common kwargs.
If an option isn't specified falls back to common environment variable
defaults.
"""
if auth_url or os.getenv('OS_AUTH_URL'):
force_strategy = 'keystone'
else:
force_strategy = None
creds = dict(username=username or
os.getenv('OS_AUTH_USER', os.getenv('OS_USERNAME')),
password=password or
os.getenv('OS_AUTH_KEY', os.getenv('OS_PASSWORD')),
tenant=tenant or
os.getenv('OS_AUTH_TENANT',
os.getenv('OS_TENANT_NAME')),
auth_url=auth_url or os.getenv('OS_AUTH_URL'),
strategy=force_strategy or auth_strategy or
os.getenv('OS_AUTH_STRATEGY', 'noauth'),
region=region or os.getenv('OS_REGION_NAME'),
)
if creds['strategy'] == 'keystone' and not creds['auth_url']:
msg = ("--os_auth_url option or OS_AUTH_URL environment variable "
"required when keystone authentication strategy is enabled\n")
raise exception.ClientConfigurationError(msg)
client = (ProgressClient if not is_silent_upload else Client)
return client(host=host,
port=port,
timeout=timeout,
use_ssl=use_ssl,
auth_tok=auth_token or
os.getenv('OS_TOKEN'),
creds=creds,
insecure=insecure)

View File

@ -86,76 +86,6 @@ def handle_redirects(func):
return wrapped return wrapped
class ImageBodyIterator(object):
"""
A class that acts as an iterator over an image file's
chunks of data. This is returned as part of the result
tuple from `glance.client.Client.get_image`
"""
def __init__(self, source):
"""
Constructs the object from a readable image source
(such as an HTTPResponse or file-like object)
"""
self.source = source
def __iter__(self):
"""
Exposes an iterator over the chunks of data in the
image file.
"""
while True:
chunk = self.source.read(CHUNKSIZE)
if chunk:
yield chunk
else:
break
class SendFileIterator:
"""
Emulate iterator pattern over sendfile, in order to allow
send progress be followed by wrapping the iteration.
"""
def __init__(self, connection, body):
self.connection = connection
self.body = body
self.offset = 0
self.sending = True
def __iter__(self):
class OfLength:
def __init__(self, len):
self.len = len
def __len__(self):
return self.len
while self.sending:
try:
sent = sendfile.sendfile(self.connection.sock.fileno(),
self.body.fileno(),
self.offset,
CHUNKSIZE)
except OSError as e:
# suprisingly, sendfile may fail transiently instead of
# blocking, in which case we select on the socket in order
# to wait on its return to a writeable state before resuming
# the send loop
if e.errno in (errno.EAGAIN, errno.EBUSY):
wlist = [self.connection.sock.fileno()]
rfds, wfds, efds = select.select([], wlist, [])
if wfds:
continue
raise
self.sending = (sent != 0)
self.offset += sent
yield OfLength(sent)
class HTTPSClientAuthConnection(httplib.HTTPSConnection): class HTTPSClientAuthConnection(httplib.HTTPSConnection):
""" """
Class to make a HTTPS connection, with support for Class to make a HTTPS connection, with support for

File diff suppressed because it is too large Load Diff

View File

@ -15,12 +15,12 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import json
import time import time
from glance.tests import functional import httplib2
from glance import client from glance.tests import functional
from glance.registry import client as registry_client
from glance.tests.utils import execute from glance.tests.utils import execute
@ -35,95 +35,55 @@ class TestScrubber(functional.FunctionalTest):
"""Test that delayed_delete works and the scrubber deletes""" """Test that delayed_delete works and the scrubber deletes"""
def _get_client(self):
return client.Client("localhost", self.api_port)
def _get_registry_client(self):
return registry_client.RegistryClient('localhost',
self.registry_port)
def test_immediate_delete(self):
"""
test that images get deleted immediately by default
"""
self.cleanup()
self.start_servers(**self.__dict__.copy())
client = self._get_client()
registry = self._get_registry_client()
meta = client.add_image(TEST_IMAGE_META, TEST_IMAGE_DATA)
id = meta['id']
filters = {'deleted': True, 'is_public': 'none',
'status': 'pending_delete'}
recs = registry.get_images_detailed(filters=filters)
self.assertFalse(recs)
client.delete_image(id)
recs = registry.get_images_detailed(filters=filters)
self.assertFalse(recs)
filters = {'deleted': True, 'is_public': 'none', 'status': 'deleted'}
recs = registry.get_images_detailed(filters=filters)
self.assertTrue(recs)
for rec in recs:
self.assertEqual(rec['status'], 'deleted')
self.stop_servers()
def test_delayed_delete(self): def test_delayed_delete(self):
""" """
test that images don't get deleted immediatly and that the scrubber test that images don't get deleted immediatly and that the scrubber
scrubs them scrubs them
""" """
self.cleanup() self.cleanup()
self.start_servers(delayed_delete=True, daemon=True) self.start_servers(delayed_delete=True, daemon=True)
client = self._get_client() headers = {
registry = self._get_registry_client() 'x-image-meta-name': 'test_image',
meta = client.add_image(TEST_IMAGE_META, TEST_IMAGE_DATA) 'x-image-meta-is_public': 'true',
id = meta['id'] 'x-image-meta-disk_format': 'raw',
'x-image-meta-container_format': 'ovf',
'content-type': 'application/octet-stream',
}
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'POST', body='XXX',
headers=headers)
self.assertEqual(response.status, 201)
image = json.loads(content)['image']
self.assertEqual('active', image['status'])
image_id = image['id']
filters = {'deleted': True, 'is_public': 'none', path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
'status': 'pending_delete'} image_id)
recs = registry.get_images_detailed(filters=filters) http = httplib2.Http()
self.assertFalse(recs) response, content = http.request(path, 'DELETE')
self.assertEqual(response.status, 200)
client.delete_image(id) response, content = http.request(path, 'HEAD')
recs = registry.get_images_detailed(filters=filters) self.assertEqual(response.status, 200)
self.assertTrue(recs) self.assertEqual('pending_delete', response['x-image-meta-status'])
filters = {'deleted': True, 'is_public': 'none'}
recs = registry.get_images_detailed(filters=filters)
self.assertTrue(recs)
for rec in recs:
self.assertEqual(rec['status'], 'pending_delete')
# NOTE(jkoelker) The build servers sometimes take longer than # NOTE(jkoelker) The build servers sometimes take longer than
# 15 seconds to scrub. Give it up to 5 min, checking # 15 seconds to scrub. Give it up to 5 min, checking
# checking every 15 seconds. When/if it flips to # checking every 15 seconds. When/if it flips to
# deleted, bail immediatly. # deleted, bail immediatly.
deleted = set()
recs = []
for _ in xrange(3): for _ in xrange(3):
time.sleep(5) time.sleep(5)
recs = registry.get_images_detailed(filters=filters) response, content = http.request(path, 'HEAD')
self.assertTrue(recs) if response['x-image-meta-status'] == 'deleted' and \
response['x-image-meta-deleted'] == 'True':
# NOTE(jkoelker) Reset the deleted set for this loop
deleted = set()
for rec in recs:
deleted.add(rec['status'] == 'deleted')
if False not in deleted:
break break
else:
self.assertTrue(recs) continue
for rec in recs: else:
self.assertEqual(rec['status'], 'deleted') self.fail('image was never scrubbed')
self.stop_servers() self.stop_servers()
@ -135,31 +95,31 @@ class TestScrubber(functional.FunctionalTest):
self.cleanup() self.cleanup()
self.start_servers(delayed_delete=True, daemon=False) self.start_servers(delayed_delete=True, daemon=False)
client = self._get_client() headers = {
registry = self._get_registry_client() 'x-image-meta-name': 'test_image',
'x-image-meta-is_public': 'true',
'x-image-meta-disk_format': 'raw',
'x-image-meta-container_format': 'ovf',
'content-type': 'application/octet-stream',
}
path = "http://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http()
response, content = http.request(path, 'POST', body='XXX',
headers=headers)
self.assertEqual(response.status, 201)
image = json.loads(content)['image']
self.assertEqual('active', image['status'])
image_id = image['id']
# add some images and ensure it was successful path = "http://%s:%d/v1/images/%s" % ("127.0.0.1", self.api_port,
img_ids = [] image_id)
for i in range(0, 3): http = httplib2.Http()
meta = client.add_image(TEST_IMAGE_META, TEST_IMAGE_DATA) response, content = http.request(path, 'DELETE')
id = meta['id'] self.assertEqual(response.status, 200)
img_ids.append(id)
filters = {'deleted': True, 'is_public': 'none',
'status': 'pending_delete'}
recs = registry.get_images_detailed(filters=filters)
self.assertFalse(recs)
# delete those images response, content = http.request(path, 'HEAD')
for img_id in img_ids: self.assertEqual(response.status, 200)
client.delete_image(img_id) self.assertEqual('pending_delete', response['x-image-meta-status'])
recs = registry.get_images_detailed(filters=filters)
self.assertTrue(recs)
filters = {'deleted': True, 'is_public': 'none'}
recs = registry.get_images_detailed(filters=filters)
self.assertTrue(recs)
for rec in recs:
self.assertEqual(rec['status'], 'pending_delete')
# wait for the scrub time on the image to pass # wait for the scrub time on the image to pass
time.sleep(self.api_server.scrub_time) time.sleep(self.api_server.scrub_time)
@ -170,10 +130,20 @@ class TestScrubber(functional.FunctionalTest):
exitcode, out, err = execute(cmd, raise_error=False) exitcode, out, err = execute(cmd, raise_error=False)
self.assertEqual(0, exitcode) self.assertEqual(0, exitcode)
filters = {'deleted': True, 'is_public': 'none'} # NOTE(jkoelker) The build servers sometimes take longer than
recs = registry.get_images_detailed(filters=filters) # 15 seconds to scrub. Give it up to 5 min, checking
self.assertTrue(recs) # checking every 15 seconds. When/if it flips to
for rec in recs: # deleted, bail immediatly.
self.assertEqual(rec['status'], 'deleted') for _ in xrange(3):
time.sleep(5)
response, content = http.request(path, 'HEAD')
if response['x-image-meta-status'] == 'deleted' and \
response['x-image-meta-deleted'] == 'True':
break
else:
continue
else:
self.fail('image was never scrubbed')
self.stop_servers() self.stop_servers()

View File

@ -124,39 +124,3 @@ class TestMiscellaneous(functional.FunctionalTest):
"in output: %s" % out) "in output: %s" % out)
self.stop_servers() self.stop_servers()
def test_api_treats_size_as_a_normal_property(self):
"""
A test for LP bug #825024 -- glance client currently
treats size as a normal property.
"""
self.cleanup()
self.start_servers()
# 1. POST /images with public image named Image1
# attribute and no custom properties. Verify a 200 OK is returned
with tempfile.NamedTemporaryFile() as image_file:
image_file.write("XXX")
image_file.flush()
image_file_name = image_file.name
suffix = 'size=12345 --silent-upload < %s' % image_file_name
cmd = minimal_add_command(self.api_port, 'MyImage', suffix)
exitcode, out, err = execute(cmd)
image_id = out.strip().split(':')[1].strip()
self.assertEqual(0, exitcode)
self.assertTrue('Found non-settable field size. Removing.' in out)
self.assertTrue('Added new image with ID: %s' % image_id in out)
# 2. Verify image added as public image
cmd = "bin/glance --port=%d show %s" % (self.api_port, image_id)
exitcode, out, err = execute(cmd)
self.assertEqual(0, exitcode)
lines = out.split("\n")[2:-1]
self.assertFalse("12345" in out)
self.stop_servers()

View File

@ -37,8 +37,6 @@ import json
import os import os
import tempfile import tempfile
from glance import client as glance_client
from glance.common import exception
from glance.common import utils from glance.common import utils
from glance.openstack.common import timeutils from glance.openstack.common import timeutils
from glance.tests import functional from glance.tests import functional
@ -1234,96 +1232,3 @@ class TestSSL(functional.FunctionalTest):
self.assertEqual(response.status, 404) self.assertEqual(response.status, 404)
self.stop_servers() self.stop_servers()
@skip_if_disabled
def test_certificate_validation(self):
"""
Check SSL client cerificate verification
"""
self.cleanup()
self.start_servers(**self.__dict__.copy())
# 0. GET /images
# Verify no public images
path = "https://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
https = httplib2.Http(disable_ssl_certificate_validation=True)
response, content = https.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 = "https://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
https = httplib2.Http(disable_ssl_certificate_validation=True)
response, content = https.request(path, 'POST', headers=headers)
self.assertEqual(response.status, 201)
data = json.loads(content)
image_id = data['image']['id']
# 2. Attempt to delete the image *without* CA file
path = "https://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
secure_cli = glance_client.Client(host="127.0.0.1", port=self.api_port,
use_ssl=True, insecure=False)
try:
secure_cli.delete_image(image_id)
self.fail("Client with no CA file deleted image %s" % image_id)
except exception.ClientConnectionError, e:
pass
# 3. Delete the image with a secure client *with* CA file
secure_cli2 = glance_client.Client(host="127.0.0.1",
port=self.api_port, use_ssl=True,
ca_file=self.ca_file,
insecure=False)
try:
secure_cli2.delete_image(image_id)
except exception.ClientConnectionError, e:
self.fail("Secure client failed to delete image %s" % image_id)
# Verify image is deleted
path = "https://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
https = httplib2.Http(disable_ssl_certificate_validation=True)
response, content = https.request(path, 'GET')
self.assertEqual(response.status, 200)
self.assertEqual(content, '{"images": []}')
# 4. POST another image
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 = "https://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
https = httplib2.Http(disable_ssl_certificate_validation=True)
response, content = https.request(path, 'POST', headers=headers)
self.assertEqual(response.status, 201)
data = json.loads(content)
image_id = data['image']['id']
# 5. Delete the image with an insecure client
insecure_cli = glance_client.Client(host="127.0.0.1",
port=self.api_port, use_ssl=True,
insecure=True)
try:
insecure_cli.delete_image(image_id)
except exception.ClientConnectionError, e:
self.fail("Insecure client failed to delete image")
# Verify image is deleted
path = "https://%s:%d/v1/images" % ("127.0.0.1", self.api_port)
https = httplib2.Http(disable_ssl_certificate_validation=True)
response, content = https.request(path, 'GET')
self.assertEqual(response.status, 200)
self.assertEqual(content, '{"images": []}')
self.stop_servers()

View File

@ -186,8 +186,6 @@ def stub_out_registry_and_store_server(stubs, base_dir):
glance.common.client.BaseClient._sendable) glance.common.client.BaseClient._sendable)
stubs.Set(glance.common.client.BaseClient, '_sendable', stubs.Set(glance.common.client.BaseClient, '_sendable',
fake_sendable) fake_sendable)
stubs.Set(glance.common.client.ImageBodyIterator, '__iter__',
fake_image_iter)
def stub_out_registry_server(stubs, **kwargs): def stub_out_registry_server(stubs, **kwargs):
@ -212,5 +210,3 @@ def stub_out_registry_server(stubs, **kwargs):
stubs.Set(glance.common.client.BaseClient, 'get_connection_type', stubs.Set(glance.common.client.BaseClient, 'get_connection_type',
fake_get_connection_type) fake_get_connection_type)
stubs.Set(glance.common.client.ImageBodyIterator, '__iter__',
fake_image_iter)

File diff suppressed because it is too large Load Diff

View File

@ -44,8 +44,7 @@ setuptools.setup(
'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.6',
'Environment :: No Input/Output (Daemon)', 'Environment :: No Input/Output (Daemon)',
], ],
scripts=['bin/glance', scripts=['bin/glance-api',
'bin/glance-api',
'bin/glance-cache-prefetcher', 'bin/glance-cache-prefetcher',
'bin/glance-cache-pruner', 'bin/glance-cache-pruner',
'bin/glance-cache-manage', 'bin/glance-cache-manage',