From fa5be443bae880ab15d5079caa28d6862cbd13b9 Mon Sep 17 00:00:00 2001 From: Vincent Untz Date: Wed, 22 Aug 2012 15:40:59 +0200 Subject: Allow connecting to a ssl-based glance 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 --- etc/nova/nova.conf.sample | 6 +++- nova/flags.py | 7 +++- nova/image/glance.py | 42 +++++++++++++++------- nova/tests/image/test_glance.py | 39 +++++++++++++------- nova/virt/xenapi/vm_utils.py | 6 ++-- plugins/xenserver/xenapi/etc/xapi.d/plugins/glance | 30 ++++++++++++---- 6 files changed, 93 insertions(+), 37 deletions(-) diff --git a/etc/nova/nova.conf.sample b/etc/nova/nova.conf.sample index d31d1e9e6..93e24877e 100644 --- a/etc/nova/nova.conf.sample +++ b/etc/nova/nova.conf.sample @@ -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 diff --git a/nova/flags.py b/nova/flags.py index 08a136963..9ca1577df 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -137,8 +137,13 @@ global_opts = [ 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'), diff --git a/nova/image/glance.py b/nova/image/glance.py index 7f2dd74f1..38508a12c 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -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) @@ -72,8 +78,13 @@ 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) @@ -81,25 +92,29 @@ def get_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): """ @@ -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) diff --git a/nova/tests/image/test_glance.py b/nova/tests/image/test_glance.py index 5b695d2f7..35b3648bb 100644 --- a/nova/tests/image/test_glance.py +++ b/nova/tests/image/test_glance.py @@ -105,7 +105,7 @@ class TestGlanceImageService(test.TestCase): 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', @@ -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): @@ -564,19 +565,21 @@ class TestGlanceClientWrapper(test.TestCase): 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) @@ -588,15 +591,17 @@ class TestGlanceClientWrapper(test.TestCase): 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) @@ -611,7 +616,8 @@ class TestGlanceClientWrapper(test.TestCase): info = {'num_calls': 0, 'host': 'host2', - 'port': 9293} + 'port': 9293, + 'use_ssl': True} def _fake_shuffle2(servers): # fake shuffle in a known manner @@ -629,19 +635,21 @@ class TestGlanceClientWrapper(test.TestCase): 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) @@ -653,17 +661,20 @@ class TestGlanceClientWrapper(test.TestCase): 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) @@ -684,8 +695,10 @@ class TestGlanceClientWrapper(test.TestCase): 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) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 744770528..97650b248 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -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 @@ -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} @@ -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( diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance b/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance index 2f0050f11..a574bb406 100755 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance +++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance @@ -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. """ @@ -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) @@ -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 @@ -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"] @@ -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) @@ -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"] @@ -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) -- cgit