Update GlanceClient, GlanceImageService, and Glance Xen plugin to work with Glance keystone.

This commit is contained in:
Dan Prince
2011-09-12 20:32:09 +00:00
committed by Tarmac
19 changed files with 124 additions and 168 deletions

View File

@@ -70,6 +70,7 @@ class KeystoneContext(wsgi.Middleware):
project_id,
roles=roles,
auth_token=auth_token,
strategy='keystone',
remote_address=remote_address)
req.environ['nova.context'] = ctx

View File

@@ -92,7 +92,8 @@ class CreateInstanceHelper(object):
if str(image_href).startswith(req.application_url):
image_href = image_href.split('/').pop()
try:
image_service, image_id = nova.image.get_image_service(image_href)
image_service, image_id = nova.image.get_image_service(context,
image_href)
kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image(
req, image_service, image_id)
images = set([str(x['id']) for x in image_service.index(context)])

View File

@@ -202,7 +202,8 @@ class API(base.Base):
self._check_injected_file_quota(context, injected_files)
self._check_requested_networks(context, requested_networks)
(image_service, image_id) = nova.image.get_image_service(image_href)
(image_service, image_id) = nova.image.get_image_service(context,
image_href)
image = image_service.show(context, image_id)
config_drive_id = None

View File

@@ -322,7 +322,8 @@ class ComputeManager(manager.SchedulerDependentManager):
# used by the image service. This should be refactored to be
# consistent.
image_href = instance['image_ref']
image_service, image_id = nova.image.get_image_service(image_href)
image_service, image_id = nova.image.get_image_service(context,
image_href)
image_meta = image_service.show(context, image_id)
try:

View File

@@ -32,7 +32,7 @@ class RequestContext(object):
def __init__(self, user_id, project_id, is_admin=None, read_deleted=False,
roles=None, remote_address=None, timestamp=None,
request_id=None, auth_token=None):
request_id=None, auth_token=None, strategy='noauth'):
self.user_id = user_id
self.project_id = project_id
self.roles = roles or []
@@ -50,6 +50,7 @@ class RequestContext(object):
request_id = unicode(uuid.uuid4())
self.request_id = request_id
self.auth_token = auth_token
self.strategy = strategy
def to_dict(self):
return {'user_id': self.user_id,
@@ -60,7 +61,8 @@ class RequestContext(object):
'remote_address': self.remote_address,
'timestamp': utils.strtime(self.timestamp),
'request_id': self.request_id,
'auth_token': self.auth_token}
'auth_token': self.auth_token,
'strategy': self.strategy}
@classmethod
def from_dict(cls, values):
@@ -77,7 +79,8 @@ class RequestContext(object):
remote_address=self.remote_address,
timestamp=self.timestamp,
request_id=self.request_id,
auth_token=self.auth_token)
auth_token=self.auth_token,
strategy=self.strategy)
def get_admin_context(read_deleted=False):

View File

@@ -16,70 +16,20 @@
# under the License.
from urlparse import urlparse
import nova
from nova import exception
from nova import utils
from nova import flags
from nova.image import glance as glance_image_service
from nova.image import glance
FLAGS = flags.FLAGS
GlanceClient = utils.import_class('glance.client.Client')
def _parse_image_ref(image_href):
"""Parse an image href into composite parts.
:param image_href: href of an image
:returns: a tuple of the form (image_id, host, port)
:raises ValueError
"""
o = urlparse(image_href)
port = o.port or 80
host = o.netloc.split(':', 1)[0]
image_id = int(o.path.split('/')[-1])
return (image_id, host, port)
def get_default_image_service():
ImageService = utils.import_class(FLAGS.image_service)
return ImageService()
# FIXME(sirp): perhaps this should be moved to nova/images/glance so that we
# keep Glance specific code together for the most part
def get_glance_client(image_href):
"""Get the correct glance client and id for the given image_href.
The image_href param can be an href of the form
http://myglanceserver:9292/images/42, or just an int such as 42. If the
image_href is an int, then flags are used to create the default
glance client.
:param image_href: image ref/id for an image
:returns: a tuple of the form (glance_client, image_id)
"""
image_href = image_href or 0
if str(image_href).isdigit():
glance_host, glance_port = \
glance_image_service.pick_glance_api_server()
glance_client = GlanceClient(glance_host, glance_port)
return (glance_client, int(image_href))
try:
(image_id, host, port) = _parse_image_ref(image_href)
except ValueError:
raise exception.InvalidImageRef(image_href=image_href)
glance_client = GlanceClient(host, port)
return (glance_client, image_id)
def get_image_service(image_href):
def get_image_service(context, image_href):
"""Get the proper image_service and id for the given image_href.
The image_href param can be an href of the form
@@ -94,6 +44,6 @@ def get_image_service(image_href):
if str(image_href).isdigit():
return (get_default_image_service(), int(image_href))
(glance_client, image_id) = get_glance_client(image_href)
(glance_client, image_id) = glance.get_glance_client(context, image_href)
image_service = nova.image.glance.GlanceImageService(glance_client)
return (image_service, image_id)

View File

@@ -23,6 +23,7 @@ import copy
import datetime
import json
import random
from urlparse import urlparse
from glance.common import exception as glance_exception
@@ -42,6 +43,35 @@ FLAGS = flags.FLAGS
GlanceClient = utils.import_class('glance.client.Client')
def _parse_image_ref(image_href):
"""Parse an image href into composite parts.
:param image_href: href of an image
:returns: a tuple of the form (image_id, host, port)
:raises ValueError
"""
o = urlparse(image_href)
port = o.port or 80
host = o.netloc.split(':', 1)[0]
image_id = int(o.path.split('/')[-1])
return (image_id, host, port)
def _create_glance_client(context, host, port):
if context.strategy == 'keystone':
# NOTE(dprince): Glance client just needs auth_tok right? Should we
# add username and tenant to the creds below?
creds = {'strategy': 'keystone',
'username': context.user_id,
'tenant': context.project_id}
glance_client = GlanceClient(host, port, auth_tok=context.auth_token,
creds=creds)
else:
glance_client = GlanceClient(host, port)
return glance_client
def pick_glance_api_server():
"""Return which Glance API server to use for the request
@@ -57,6 +87,33 @@ def pick_glance_api_server():
return host, port
def get_glance_client(context, image_href):
"""Get the correct glance client and id for the given image_href.
The image_href param can be an href of the form
http://myglanceserver:9292/images/42, or just an int such as 42. If the
image_href is an int, then flags are used to create the default
glance client.
:param image_href: image ref/id for an image
:returns: a tuple of the form (glance_client, image_id)
"""
image_href = image_href or 0
if str(image_href).isdigit():
glance_host, glance_port = pick_glance_api_server()
glance_client = _create_glance_client(context, glance_host,
glance_port)
return (glance_client, int(image_href))
try:
(image_id, host, port) = _parse_image_ref(image_href)
except ValueError:
raise exception.InvalidImageRef(image_href=image_href)
glance_client = _create_glance_client(context, glance_host, glance_port)
return (glance_client, image_id)
class GlanceImageService(service.BaseImageService):
"""Provides storage and retrieval of disk image objects within Glance."""
@@ -71,23 +128,14 @@ class GlanceImageService(service.BaseImageService):
def __init__(self, client=None):
self._client = client
def _get_client(self):
def _get_client(self, context):
# NOTE(sirp): we want to load balance each request across glance
# servers. Since GlanceImageService is a long-lived object, `client`
# is made to choose a new server each time via this property.
if self._client is not None:
return self._client
glance_host, glance_port = pick_glance_api_server()
return GlanceClient(glance_host, glance_port)
def _set_client(self, client):
self._client = client
client = property(_get_client, _set_client)
def _set_client_context(self, context):
"""Sets the client's auth token."""
self.client.set_auth_token(context.auth_token)
return _create_glance_client(context, glance_host, glance_port)
def index(self, context, **kwargs):
"""Calls out to Glance for a list of images available."""
@@ -128,14 +176,14 @@ class GlanceImageService(service.BaseImageService):
def _get_images(self, context, **kwargs):
"""Get image entitites from images service"""
self._set_client_context(context)
# ensure filters is a dict
kwargs['filters'] = kwargs.get('filters') or {}
# NOTE(vish): don't filter out private images
kwargs['filters'].setdefault('is_public', 'none')
return self._fetch_images(self.client.get_images_detailed, **kwargs)
client = self._get_client(context)
return self._fetch_images(client.get_images_detailed, **kwargs)
def _fetch_images(self, fetch_func, **kwargs):
"""Paginate through results from glance server"""
@@ -168,9 +216,8 @@ class GlanceImageService(service.BaseImageService):
def show(self, context, image_id):
"""Returns a dict with image data for the given opaque image id."""
self._set_client_context(context)
try:
image_meta = self.client.get_image_meta(image_id)
image_meta = self._get_client(context).get_image_meta(image_id)
except glance_exception.NotFound:
raise exception.ImageNotFound(image_id=image_id)
@@ -192,9 +239,9 @@ class GlanceImageService(service.BaseImageService):
def get(self, context, image_id, data):
"""Calls out to Glance for metadata and data and writes data."""
self._set_client_context(context)
try:
image_meta, image_chunks = self.client.get_image(image_id)
client = self._get_client(context)
image_meta, image_chunks = client.get_image(image_id)
except glance_exception.NotFound:
raise exception.ImageNotFound(image_id=image_id)
@@ -210,7 +257,6 @@ class GlanceImageService(service.BaseImageService):
:raises: AlreadyExists if the image already exist.
"""
self._set_client_context(context)
# Translate Base -> Service
LOG.debug(_('Creating image in Glance. Metadata passed in %s'),
image_meta)
@@ -218,7 +264,7 @@ class GlanceImageService(service.BaseImageService):
LOG.debug(_('Metadata after formatting for Glance %s'),
sent_service_image_meta)
recv_service_image_meta = self.client.add_image(
recv_service_image_meta = self._get_client(context).add_image(
sent_service_image_meta, data)
# Translate Service -> Base
@@ -233,12 +279,12 @@ class GlanceImageService(service.BaseImageService):
:raises: ImageNotFound if the image does not exist.
"""
self._set_client_context(context)
# NOTE(vish): show is to check if image is available
self.show(context, image_id)
image_meta = _convert_to_string(image_meta)
try:
image_meta = self.client.update_image(image_id, image_meta, data)
client = self._get_client(context)
image_meta = client.update_image(image_id, image_meta, data)
except glance_exception.NotFound:
raise exception.ImageNotFound(image_id=image_id)
@@ -251,11 +297,10 @@ class GlanceImageService(service.BaseImageService):
:raises: ImageNotFound if the image does not exist.
"""
self._set_client_context(context)
# NOTE(vish): show is to check if image is available
self.show(context, image_id)
try:
result = self.client.delete_image(image_id)
result = self._get_client(context).delete_image(image_id)
except glance_exception.NotFound:
raise exception.ImageNotFound(image_id=image_id)
return result

View File

@@ -124,7 +124,7 @@ def stub_out_key_pair_funcs(stubs, have_key_pair=True):
def stub_out_image_service(stubs):
def fake_get_image_service(image_href):
def fake_get_image_service(context, image_href):
return (nova.image.fake.FakeImageService(), image_href)
stubs.Set(nova.image, 'get_image_service', fake_get_image_service)
stubs.Set(nova.image, 'get_default_image_service',

View File

@@ -16,14 +16,14 @@
import StringIO
import nova.image
from nova.image import glance
def stubout_glance_client(stubs):
def fake_get_glance_client(image_href):
def fake_get_glance_client(context, image_href):
image_id = int(str(image_href).split('/')[-1])
return (FakeGlance('foo'), image_id)
stubs.Set(nova.image, 'get_glance_client', fake_get_glance_client)
stubs.Set(glance, 'get_glance_client', fake_get_glance_client)
class FakeGlance(object):

View File

@@ -64,7 +64,7 @@ class _IntegratedTestBase(test.TestCase):
self.flags(**f)
self.flags(verbose=True)
def fake_get_image_service(image_href):
def fake_get_image_service(context, image_href):
image_id = int(str(image_href).split('/')[-1])
return (nova.image.fake.FakeImageService(), image_id)
self.stubs.Set(nova.image, 'get_image_service', fake_get_image_service)

View File

@@ -932,8 +932,9 @@ class XenAPIDetermineDiskImageTestCase(test.TestCase):
self.fake_instance.architecture = 'x86-64'
def assert_disk_type(self, disk_type):
ctx = context.RequestContext('fake', 'fake')
dt = vm_utils.VMHelper.determine_disk_image_type(
self.fake_instance)
self.fake_instance, ctx)
self.assertEqual(disk_type, dt)
def test_instance_disk(self):

View File

@@ -37,7 +37,8 @@ def fetch(context, image_href, path, _user_id, _project_id):
# when it is added to glance. Right now there is no
# auth checking in glance, so we assume that access was
# checked before we got here.
(image_service, image_id) = nova.image.get_image_service(image_href)
(image_service, image_id) = nova.image.get_image_service(context,
image_href)
with open(path, "wb") as image_file:
metadata = image_service.get(context, image_id, image_file)
return metadata

View File

@@ -398,10 +398,10 @@ class LibvirtConnection(driver.ComputeDriver):
virt_dom = self._lookup_by_name(instance['name'])
(image_service, image_id) = nova.image.get_image_service(
instance['image_ref'])
context, instance['image_ref'])
base = image_service.show(context, image_id)
(snapshot_image_service, snapshot_image_id) = \
nova.image.get_image_service(image_href)
nova.image.get_image_service(context, image_href)
snapshot = snapshot_image_service.show(context, snapshot_image_id)
metadata = {'is_public': False,

View File

@@ -412,7 +412,7 @@ def fake_get_network(*args, **kwargs):
return [{'type': 'fake'}]
def fake_fetch_image(image, instance, **kwargs):
def fake_fetch_image(context, image, instance, **kwargs):
"""Fakes fetch image call. Just adds a reference to the db for the file."""
ds_name = kwargs.get("datastore_name")
file_path = kwargs.get("file_path")
@@ -420,12 +420,12 @@ def fake_fetch_image(image, instance, **kwargs):
_add_file(ds_file_path)
def fake_upload_image(image, instance, **kwargs):
def fake_upload_image(context, image, instance, **kwargs):
"""Fakes the upload of an image."""
pass
def fake_get_vmdk_size_and_properties(image_id, instance):
def fake_get_vmdk_size_and_properties(context, image_id, instance):
"""Fakes the file size and properties fetch for the image file."""
props = {"vmware_ostype": "otherGuest",
"vmware_adaptertype": "lsiLogic"}

View File

@@ -157,7 +157,7 @@ class VMWareVMOps(object):
repository.
"""
image_size, image_properties = \
vmware_images.get_vmdk_size_and_properties(
vmware_images.get_vmdk_size_and_properties(context,
instance.image_ref, instance)
vmdk_file_size_in_kb = int(image_size) / 1024
os_type = image_properties.get("vmware_ostype", "otherGuest")
@@ -282,6 +282,7 @@ class VMWareVMOps(object):
# Upload the -flat.vmdk file whose meta-data file we just created
# above
vmware_images.fetch_image(
context,
instance.image_ref,
instance,
host=self._session._host_ip,
@@ -448,6 +449,7 @@ class VMWareVMOps(object):
# Upload the contents of -flat.vmdk file which has the disk data.
LOG.debug(_("Uploading image %s") % snapshot_name)
vmware_images.upload_image(
context,
snapshot_name,
instance,
os_type=os_type,

View File

@@ -20,15 +20,13 @@ Utility functions for Image transfer.
from nova import exception
from nova import flags
import nova.image
from nova.image import glance
from nova import log as logging
from nova.virt.vmwareapi import io_util
from nova.virt.vmwareapi import read_write_util
LOG = logging.getLogger("nova.virt.vmwareapi.vmware_images")
FLAGS = flags.FLAGS
QUEUE_BUFFER_SIZE = 10
@@ -87,36 +85,10 @@ def start_transfer(read_file_handle, data_size, write_file_handle=None,
write_file_handle.close()
def fetch_image(image, instance, **kwargs):
"""Fetch an image for attaching to the newly created VM."""
# Depending upon the image service, make appropriate image service call
if FLAGS.image_service == "nova.image.glance.GlanceImageService":
func = _get_glance_image
elif FLAGS.image_service == "nova.image.s3.S3ImageService":
func = _get_s3_image
else:
raise NotImplementedError(_("The Image Service %s is not implemented")
% FLAGS.image_service)
return func(image, instance, **kwargs)
def upload_image(image, instance, **kwargs):
"""Upload the newly snapshotted VM disk file."""
# Depending upon the image service, make appropriate image service call
if FLAGS.image_service == "nova.image.glance.GlanceImageService":
func = _put_glance_image
elif FLAGS.image_service == "nova.image.s3.S3ImageService":
func = _put_s3_image
else:
raise NotImplementedError(_("The Image Service %s is not implemented")
% FLAGS.image_service)
return func(image, instance, **kwargs)
def _get_glance_image(image, instance, **kwargs):
def fetch_image(context, image, instance, **kwargs):
"""Download image from the glance image server."""
LOG.debug(_("Downloading image %s from glance image server") % image)
(glance_client, image_id) = nova.image.get_glance_client(image)
(glance_client, image_id) = glance.get_glance_client(context, image)
metadata, read_iter = glance_client.get_image(image_id)
read_file_handle = read_write_util.GlanceFileRead(read_iter)
file_size = int(metadata['size'])
@@ -132,17 +104,7 @@ def _get_glance_image(image, instance, **kwargs):
LOG.debug(_("Downloaded image %s from glance image server") % image)
def _get_s3_image(image, instance, **kwargs):
"""Download image from the S3 image server."""
raise NotImplementedError
def _get_local_image(image, instance, **kwargs):
"""Download image from the local nova compute node."""
raise NotImplementedError
def _put_glance_image(image, instance, **kwargs):
def upload_image(context, image, instance, **kwargs):
"""Upload the snapshotted vm disk file to Glance image server."""
LOG.debug(_("Uploading image %s to the Glance image server") % image)
read_file_handle = read_write_util.VmWareHTTPReadFile(
@@ -152,7 +114,7 @@ def _put_glance_image(image, instance, **kwargs):
kwargs.get("cookies"),
kwargs.get("file_path"))
file_size = read_file_handle.get_size()
(glance_client, image_id) = nova.image.get_glance_client(image)
(glance_client, image_id) = glance.get_glance_client(context, image)
# The properties and other fields that we need to set for the image.
image_metadata = {"is_public": True,
"disk_format": "vmdk",
@@ -168,17 +130,7 @@ def _put_glance_image(image, instance, **kwargs):
LOG.debug(_("Uploaded image %s to the Glance image server") % image)
def _put_local_image(image, instance, **kwargs):
"""Upload the snapshotted vm disk file to the local nova compute node."""
raise NotImplementedError
def _put_s3_image(image, instance, **kwargs):
"""Upload the snapshotted vm disk file to S3 image server."""
raise NotImplementedError
def get_vmdk_size_and_properties(image, instance):
def get_vmdk_size_and_properties(context, image, instance):
"""
Get size of the vmdk file that is to be downloaded for attach in spawn.
Need this to create the dummy virtual disk for the meta-data file. The
@@ -186,12 +138,9 @@ def get_vmdk_size_and_properties(image, instance):
"""
LOG.debug(_("Getting image size for the image %s") % image)
if FLAGS.image_service == "nova.image.glance.GlanceImageService":
(glance_client, image_id) = nova.image.get_glance_client(image)
meta_data = glance_client.get_image_meta(image_id)
size, properties = meta_data["size"], meta_data["properties"]
elif FLAGS.image_service == "nova.image.s3.S3ImageService":
raise NotImplementedError
(glance_client, image_id) = glance.get_glance_client(context, image)
meta_data = glance_client.get_image_meta(image_id)
size, properties = meta_data["size"], meta_data["properties"]
LOG.debug(_("Got image size of %(size)s for the image %(image)s") %
locals())
return size, properties

View File

@@ -31,12 +31,10 @@ import urllib
import uuid
from xml.dom import minidom
import glance.client
from nova import db
from nova import exception
from nova import flags
import nova.image
from nova.image import glance as glance_image_service
from nova.image import glance
from nova import log as logging
from nova import utils
from nova.compute import instance_types
@@ -383,8 +381,7 @@ class VMHelper(HelperBase):
os_type = instance.os_type or FLAGS.default_os_type
glance_host, glance_port = \
glance_image_service.pick_glance_api_server()
glance_host, glance_port = glance.pick_glance_api_server()
params = {'vdi_uuids': vdi_uuids,
'image_id': image_id,
'glance_host': glance_host,
@@ -447,8 +444,7 @@ class VMHelper(HelperBase):
# pass them as arguments
uuid_stack = [str(uuid.uuid4()) for i in xrange(2)]
glance_host, glance_port = \
glance_image_service.pick_glance_api_server()
glance_host, glance_port = glance.pick_glance_api_server()
params = {'image_id': image,
'glance_host': glance_host,
'glance_port': glance_port,
@@ -546,7 +542,7 @@ class VMHelper(HelperBase):
else:
sr_ref = safe_find_sr(session)
glance_client, image_id = nova.image.get_glance_client(image)
glance_client, image_id = glance.get_glance_client(context, image)
glance_client.set_auth_token(getattr(context, 'auth_token', None))
meta, image_file = glance_client.get_image(image_id)
virtual_size = int(meta['size'])
@@ -606,7 +602,7 @@ class VMHelper(HelperBase):
raise e
@classmethod
def determine_disk_image_type(cls, instance):
def determine_disk_image_type(cls, instance, context):
"""Disk Image Types are used to determine where the kernel will reside
within an image. To figure out which type we're dealing with, we use
the following rules:
@@ -639,7 +635,8 @@ class VMHelper(HelperBase):
'vhd': ImageType.DISK_VHD,
'iso': ImageType.DISK_ISO}
image_ref = instance.image_ref
glance_client, image_id = nova.image.get_glance_client(image_ref)
glance_client, image_id = glance.get_glance_client(context,
image_ref)
meta = glance_client.get_image_meta(image_id)
disk_format = meta['disk_format']
try:

View File

@@ -135,7 +135,7 @@ class VMOps(object):
self._session.call_xenapi('VM.start', vm_ref, False, False)
def _create_disks(self, context, instance):
disk_image_type = VMHelper.determine_disk_image_type(instance)
disk_image_type = VMHelper.determine_disk_image_type(instance, context)
vdis = VMHelper.fetch_image(context, self._session,
instance, instance.image_ref,
instance.user_id, instance.project_id,
@@ -176,7 +176,7 @@ class VMOps(object):
power_state.SHUTDOWN)
return
disk_image_type = VMHelper.determine_disk_image_type(instance)
disk_image_type = VMHelper.determine_disk_image_type(instance, context)
kernel = None
ramdisk = None
try:

View File

@@ -252,7 +252,11 @@ def _upload_tarball(staging_path, image_id, glance_host, glance_port, os_type,
# NOTE(dprince): We need to resend any existing Glance meta/property
# headers so they are preserved in Glance. We obtain them here with a
# HEAD request.
conn.request('HEAD', '/v1/images/%s' % image_id)
conn.putrequest('HEAD', '/v1/images/%s' % image_id)
if auth_token:
conn.putheader('x-auth-token', auth_token)
conn.endheaders()
resp = conn.getresponse()
if resp.status != httplib.OK:
raise Exception("Unexpected response from Glance %i" % resp.status)