Skip to content

Commit

Permalink
Allow connecting to a ssl-based glance
Browse files Browse the repository at this point in the history
This introduces a new glance_api_insecure setting that can be used to
not verify the certificate of the glance server against the certificate
authorities.

Fix bug 1042081.

Change-Id: I0a9f081425854e9c01e00dfd641e42276c878c67
  • Loading branch information
vuntz committed Aug 27, 2012
1 parent 68e9a9e commit fa5be44
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 37 deletions.
6 changes: 5 additions & 1 deletion etc/nova/nova.conf.sample
Expand Up @@ -125,9 +125,13 @@
#### (IntOpt) default glance port

# glance_api_servers=$glance_host:$glance_port
#### (ListOpt) A list of the glance api servers available to nova
#### (ListOpt) A list of the glance api servers available to nova.
#### Prefix with https:// for ssl-based glance api servers.
#### ([hostname|ip]:port)

# glance_api_insecure=false
#### (BoolOpt) If passed, allow to perform insecure SSL (https) requests to glance

# glance_num_retries=0
#### (IntOpt) Number retries when downloading an image from glance

Expand Down
7 changes: 6 additions & 1 deletion nova/flags.py
Expand Up @@ -137,8 +137,13 @@ def _get_my_ip():
help='default glance port'),
cfg.ListOpt('glance_api_servers',
default=['$glance_host:$glance_port'],
help='A list of the glance api servers available to nova '
help='A list of the glance api servers available to nova. '
'Prefix with https:// for ssl-based glance api servers. '
'([hostname|ip]:port)'),
cfg.BoolOpt('glance_api_insecure',
default=False,
help='Allow to perform insecure SSL (https) requests to '
'glance'),
cfg.IntOpt('glance_num_retries',
default=0,
help='Number retries when downloading an image from glance'),
Expand Down
42 changes: 29 additions & 13 deletions nova/image/glance.py
Expand Up @@ -52,15 +52,21 @@ def _parse_image_ref(image_href):
port = o.port or 80
host = o.netloc.split(':', 1)[0]
image_id = o.path.split('/')[-1]
return (image_id, host, port)
use_ssl = (o.scheme == 'https')
return (image_id, host, port, use_ssl)


def _create_glance_client(context, host, port):
def _create_glance_client(context, host, port, use_ssl):
"""Instantiate a new glanceclient.Client object"""
if use_ssl:
scheme = 'https'
else:
scheme = 'http'
params = {}
params['insecure'] = FLAGS.glance_api_insecure
if FLAGS.auth_strategy == 'keystone':
params['token'] = context.auth_token
endpoint = 'http://%s:%s' % (host, port)
endpoint = '%s://%s:%s' % (scheme, host, port)
return glanceclient.Client('1', endpoint, **params)


Expand All @@ -72,34 +78,43 @@ def get_api_servers():
"""
api_servers = []
for api_server in FLAGS.glance_api_servers:
host, port_str = api_server.split(':')
api_servers.append((host, int(port_str)))
if '//' not in api_server:
api_server = 'http://' + api_server
o = urlparse.urlparse(api_server)
port = o.port or 80
host = o.netloc.split(':', 1)[0]
use_ssl = (o.scheme == 'https')
api_servers.append((host, port, use_ssl))
random.shuffle(api_servers)
return itertools.cycle(api_servers)


class GlanceClientWrapper(object):
"""Glance client wrapper class that implements retries."""

def __init__(self, context=None, host=None, port=None):
def __init__(self, context=None, host=None, port=None, use_ssl=False):
if host is not None:
self.client = self._create_static_client(context, host, port)
self.client = self._create_static_client(context,
host, port, use_ssl)
else:
self.client = None
self.api_servers = None

def _create_static_client(self, context, host, port):
def _create_static_client(self, context, host, port, use_ssl):
"""Create a client that we'll use for every call."""
self.host = host
self.port = port
return _create_glance_client(context, self.host, self.port)
self.use_ssl = use_ssl
return _create_glance_client(context,
self.host, self.port, self.use_ssl)

def _create_onetime_client(self, context):
"""Create a client that will be used for one call."""
if self.api_servers is None:
self.api_servers = get_api_servers()
self.host, self.port = self.api_servers.next()
return _create_glance_client(context, self.host, self.port)
self.host, self.port, self.use_ssl = self.api_servers.next()
return _create_glance_client(context,
self.host, self.port, self.use_ssl)

def call(self, context, method, *args, **kwargs):
"""
Expand Down Expand Up @@ -398,9 +413,10 @@ def get_remote_image_service(context, image_href):
return image_service, image_href

try:
(image_id, glance_host, glance_port) = _parse_image_ref(image_href)
(image_id, glance_host, glance_port, use_ssl) = \
_parse_image_ref(image_href)
glance_client = GlanceClientWrapper(context=context,
host=glance_host, port=glance_port)
host=glance_host, port=glance_port, use_ssl=use_ssl)
except ValueError:
raise exception.InvalidImageRef(image_href=image_href)

Expand Down
39 changes: 26 additions & 13 deletions nova/tests/image/test_glance.py
Expand Up @@ -105,7 +105,7 @@ def setUp(self):
self.context = context.RequestContext('fake', 'fake', auth_token=True)

def _create_image_service(self, client):
def _fake_create_glance_client(context, host, port):
def _fake_create_glance_client(context, host, port, use_ssl):
return client

self.stubs.Set(glance, '_create_glance_client',
Expand Down Expand Up @@ -550,8 +550,9 @@ class TestGlanceClientWrapper(test.TestCase):

def setUp(self):
super(TestGlanceClientWrapper, self).setUp()
self.flags(glance_api_servers=['host1:9292', 'host2:9293',
'host3:9294'])
# host1 has no scheme, which is http by default
self.flags(glance_api_servers=['host1:9292', 'https://host2:9293',
'http://host3:9294'])

# Make the test run fast
def _fake_sleep(secs):
Expand All @@ -564,19 +565,21 @@ def test_static_client_without_retries(self):
ctxt = context.RequestContext('fake', 'fake')
fake_host = 'host4'
fake_port = 9295
fake_use_ssl = False

info = {'num_calls': 0}

def _fake_create_glance_client(context, host, port):
def _fake_create_glance_client(context, host, port, use_ssl):
self.assertEqual(host, fake_host)
self.assertEqual(port, fake_port)
self.assertEqual(use_ssl, fake_use_ssl)
return _create_failing_glance_client(info)

self.stubs.Set(glance, '_create_glance_client',
_fake_create_glance_client)

client = glance.GlanceClientWrapper(context=ctxt,
host=fake_host, port=fake_port)
host=fake_host, port=fake_port, use_ssl=fake_use_ssl)
self.assertRaises(exception.GlanceConnectionFailed,
client.call, ctxt, 'get', 'meow')
self.assertEqual(info['num_calls'], 1)
Expand All @@ -588,15 +591,17 @@ def test_default_client_without_retries(self):

info = {'num_calls': 0,
'host': 'host1',
'port': 9292}
'port': 9292,
'use_ssl': False}

# Leave the list in a known-order
def _fake_shuffle(servers):
pass

def _fake_create_glance_client(context, host, port):
def _fake_create_glance_client(context, host, port, use_ssl):
self.assertEqual(host, info['host'])
self.assertEqual(port, info['port'])
self.assertEqual(use_ssl, info['use_ssl'])
return _create_failing_glance_client(info)

self.stubs.Set(random, 'shuffle', _fake_shuffle)
Expand All @@ -611,7 +616,8 @@ def _fake_create_glance_client(context, host, port):

info = {'num_calls': 0,
'host': 'host2',
'port': 9293}
'port': 9293,
'use_ssl': True}

def _fake_shuffle2(servers):
# fake shuffle in a known manner
Expand All @@ -629,19 +635,21 @@ def test_static_client_with_retries(self):
ctxt = context.RequestContext('fake', 'fake')
fake_host = 'host4'
fake_port = 9295
fake_use_ssl = False

info = {'num_calls': 0}

def _fake_create_glance_client(context, host, port):
def _fake_create_glance_client(context, host, port, use_ssl):
self.assertEqual(host, fake_host)
self.assertEqual(port, fake_port)
self.assertEqual(use_ssl, fake_use_ssl)
return _create_failing_glance_client(info)

self.stubs.Set(glance, '_create_glance_client',
_fake_create_glance_client)

client = glance.GlanceClientWrapper(context=ctxt,
host=fake_host, port=fake_port)
host=fake_host, port=fake_port, use_ssl=fake_use_ssl)
client.call(ctxt, 'get', 'meow')
self.assertEqual(info['num_calls'], 2)

Expand All @@ -653,17 +661,20 @@ def test_default_client_with_retries(self):
info = {'num_calls': 0,
'host0': 'host1',
'port0': 9292,
'use_ssl0': False,
'host1': 'host2',
'port1': 9293}
'port1': 9293,
'use_ssl1': True}

# Leave the list in a known-order
def _fake_shuffle(servers):
pass

def _fake_create_glance_client(context, host, port):
def _fake_create_glance_client(context, host, port, use_ssl):
attempt = info['num_calls']
self.assertEqual(host, info['host%s' % attempt])
self.assertEqual(port, info['port%s' % attempt])
self.assertEqual(use_ssl, info['use_ssl%s' % attempt])
return _create_failing_glance_client(info)

self.stubs.Set(random, 'shuffle', _fake_shuffle)
Expand All @@ -684,8 +695,10 @@ def _fake_shuffle2(servers):
info = {'num_calls': 0,
'host0': 'host2',
'port0': 9293,
'use_ssl0': True,
'host1': 'host3',
'port1': 9294}
'port1': 9294,
'use_ssl1': False}

client2.call(ctxt, 'get', 'meow')
self.assertEqual(info['num_calls'], 2)
6 changes: 4 additions & 2 deletions nova/virt/xenapi/vm_utils.py
Expand Up @@ -649,7 +649,7 @@ def upload_image(context, session, instance, vdi_uuids, image_id):
" ID %(image_id)s"), locals(), instance=instance)

glance_api_servers = glance.get_api_servers()
glance_host, glance_port = glance_api_servers.next()
glance_host, glance_port, glance_use_ssl = glance_api_servers.next()

# TODO(sirp): this inherit-image-property code should probably go in
# nova/compute/manager so it can be shared across hypervisors
Expand All @@ -669,6 +669,7 @@ def upload_image(context, session, instance, vdi_uuids, image_id):
'image_id': image_id,
'glance_host': glance_host,
'glance_port': glance_port,
'glance_use_ssl': glance_use_ssl,
'sr_path': get_sr_path(session),
'auth_token': getattr(context, 'auth_token', None),
'properties': properties}
Expand Down Expand Up @@ -1011,9 +1012,10 @@ def _fetch_vhd_image(context, session, instance, image_id):
glance_api_servers = glance.get_api_servers()

def pick_glance(params):
glance_host, glance_port = glance_api_servers.next()
glance_host, glance_port, glance_use_ssl = glance_api_servers.next()
params['glance_host'] = glance_host
params['glance_port'] = glance_port
params['glance_use_ssl'] = glance_use_ssl

plugin_name = 'glance'
vdis = _fetch_using_dom0_plugin_with_retry(
Expand Down
30 changes: 23 additions & 7 deletions plugins/xenserver/xenapi/etc/xapi.d/plugins/glance
Expand Up @@ -95,7 +95,7 @@ def _download_tarball_and_verify(request, staging_path):


def _download_tarball(sr_path, staging_path, image_id, glance_host,
glance_port, auth_token):
glance_port, glance_use_ssl, auth_token):
"""Download the tarball image from Glance and extract it into the staging
area. Retry if there is any failure.
"""
Expand All @@ -104,7 +104,12 @@ def _download_tarball(sr_path, staging_path, image_id, glance_host,
if auth_token:
headers['x-auth-token'] = auth_token

url = ("http://%(glance_host)s:%(glance_port)d/v1/images/"
if glance_use_ssl:
scheme = 'https'
else:
scheme = 'http'

url = ("%(scheme)s://%(glance_host)s:%(glance_port)d/v1/images/"
"%(image_id)s" % locals())
logging.info("Downloading %s" % url)

Expand All @@ -117,14 +122,23 @@ def _download_tarball(sr_path, staging_path, image_id, glance_host,


def _upload_tarball(staging_path, image_id, glance_host, glance_port,
auth_token, properties):
glance_use_ssl, auth_token, properties):
"""
Create a tarball of the image and then stream that into Glance
using chunked-transfer-encoded HTTP.
"""
url = 'http://%s:%s/v1/images/%s' % (glance_host, glance_port, image_id)
if glance_use_ssl:
scheme = 'https'
else:
scheme = 'http'

url = '%s://%s:%s/v1/images/%s' % (scheme, glance_host, glance_port,
image_id)
logging.info("Writing image data to %s" % url)
conn = httplib.HTTPConnection(glance_host, glance_port)
if glance_use_ssl:
conn = httplib.HTTPSConnection(glance_host, glance_port)
else:
conn = httplib.HTTPConnection(glance_host, glance_port)

# NOTE(sirp): httplib under python2.4 won't accept a file-like object
# to request
Expand Down Expand Up @@ -196,6 +210,7 @@ def download_vhd(session, args):
image_id = params["image_id"]
glance_host = params["glance_host"]
glance_port = params["glance_port"]
glance_use_ssl = params["glance_use_ssl"]
uuid_stack = params["uuid_stack"]
sr_path = params["sr_path"]
auth_token = params["auth_token"]
Expand All @@ -205,7 +220,7 @@ def download_vhd(session, args):
# Download tarball into staging area and extract it
_download_tarball(
sr_path, staging_path, image_id, glance_host, glance_port,
auth_token)
glance_use_ssl, auth_token)

# Move the VHDs from the staging area into the storage repository
imported_vhds = utils.import_vhds(sr_path, staging_path, uuid_stack)
Expand All @@ -225,6 +240,7 @@ def upload_vhd(session, args):
image_id = params["image_id"]
glance_host = params["glance_host"]
glance_port = params["glance_port"]
glance_use_ssl = params["glance_use_ssl"]
sr_path = params["sr_path"]
auth_token = params["auth_token"]
properties = params["properties"]
Expand All @@ -233,7 +249,7 @@ def upload_vhd(session, args):
try:
utils.prepare_staging_area(sr_path, staging_path, vdi_uuids)
_upload_tarball(staging_path, image_id, glance_host, glance_port,
auth_token, properties)
glance_use_ssl, auth_token, properties)
finally:
utils.cleanup_staging_area(staging_path)

Expand Down

0 comments on commit fa5be44

Please sign in to comment.