# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation # Copyright 2012 Red Hat, Inc # 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. """ Utility methods to set testcases up for Swift and/or S3 tests. """ from __future__ import print_function import BaseHTTPServer import ConfigParser import httplib import os import random import swiftclient import thread from glance.store.s3 import get_s3_location, get_calling_format FIVE_KB = 5 * 1024 class RemoteImageHandler(BaseHTTPServer.BaseHTTPRequestHandler): def do_HEAD(self): """ Respond to an image HEAD request fake metadata """ if 'images' in self.path: self.send_response(200) self.send_header('Content-Type', 'application/octet-stream') self.send_header('Content-Length', FIVE_KB) self.end_headers() return else: self.send_error(404, 'File Not Found: %s' % self.path) return def do_GET(self): """ Respond to an image GET request with fake image content. """ if 'images' in self.path: self.send_response(200) self.send_header('Content-Type', 'application/octet-stream') self.send_header('Content-Length', FIVE_KB) self.end_headers() image_data = '*' * FIVE_KB self.wfile.write(image_data) self.wfile.close() return else: self.send_error(404, 'File Not Found: %s' % self.path) return def log_message(self, format, *args): """ Simple override to prevent writing crap to stderr... """ pass def setup_http(test): server_class = BaseHTTPServer.HTTPServer remote_server = server_class(('127.0.0.1', 0), RemoteImageHandler) remote_ip, remote_port = remote_server.server_address def serve_requests(httpd): httpd.serve_forever() thread.start_new_thread(serve_requests, (remote_server,)) test.http_server = remote_server test.http_ip = remote_ip test.http_port = remote_port test.addCleanup(test.http_server.shutdown) def get_http_uri(test, image_id): uri = 'http://%(http_ip)s:%(http_port)d/images/' % test.__dict__ uri += image_id return uri def _uniq(value): return '%s.%d' % (value, random.randint(0, 99999)) def setup_swift(test): # Test machines can set the GLANCE_TEST_SWIFT_CONF variable # to override the location of the config file for migration testing CONFIG_FILE_PATH = os.environ.get('GLANCE_TEST_SWIFT_CONF') if not CONFIG_FILE_PATH: test.disabled_message = "GLANCE_TEST_SWIFT_CONF environ not set." print("GLANCE_TEST_SWIFT_CONF environ not set.") test.disabled = True return if os.path.exists(CONFIG_FILE_PATH): cp = ConfigParser.RawConfigParser() try: cp.read(CONFIG_FILE_PATH) defaults = cp.defaults() for key, value in defaults.items(): if key == 'swift_store_container': test.__dict__[key] = (_uniq(value)) else: test.__dict__[key] = value except ConfigParser.ParsingError as e: test.disabled_message = ("Failed to read test_swift.conf " "file. Got error: %s" % e) test.disabled = True return try: swift_host = test.swift_store_auth_address if not swift_host.startswith('http'): swift_host = 'https://' + swift_host user = test.swift_store_user key = test.swift_store_key container_name = test.swift_store_container except AttributeError as e: test.disabled_message = ("Failed to find required configuration " "options for Swift store. " "Got error: %s" % e) test.disabled = True return swift_conn = swiftclient.Connection( authurl=swift_host, auth_version=test.swift_store_auth_version, user=user, key=key, snet=False, retries=1) try: _resp_headers, containers = swift_conn.get_account() except Exception as e: test.disabled_message = ("Failed to get_account from Swift " "Got error: %s" % e) test.disabled = True return try: for container in containers: if container == container_name: swift_conn.delete_container(container) except swiftclient.ClientException as e: test.disabled_message = ("Failed to delete container from Swift " "Got error: %s" % e) test.disabled = True return test.swift_conn = swift_conn try: swift_conn.put_container(container_name) except swiftclient.ClientException as e: test.disabled_message = ("Failed to create container. " "Got error: %s" % e) test.disabled = True return def teardown_swift(test): swift_conn = test.swift_conn container_name = test.swift_store_container if not test.disabled: try: _resp_headers, containers = swift_conn.get_account() # Delete all containers matching the container name prefix for container in containers: if container == container_name: swift_conn.delete_container(container) except swiftclient.ClientException as e: if e.http_status == httplib.CONFLICT: pass else: raise test.swift_conn.put_container(test.swift_store_container) def get_swift_uri(test, image_id): # Apparently we must use HTTPS with Cloud Files now, otherwise # we will get a 301 Moved.... :( user = swiftclient.quote('%(swift_store_user)s' % test.__dict__) creds = (user + ':%(swift_store_key)s' % test.__dict__) uri = 'swift+https://' + creds uri += ('@%(swift_store_auth_address)s/%(swift_store_container)s/' % test.__dict__) uri += image_id uri = uri.replace('@http://', '@') uri = uri.replace('@https://', '@') return uri def setup_s3(test): # Test machines can set the GLANCE_TEST_S3_CONF variable # to override the location of the config file for S3 testing CONFIG_FILE_PATH = os.environ.get('GLANCE_TEST_S3_CONF') if not CONFIG_FILE_PATH: test.disabled_message = "GLANCE_TEST_S3_CONF environ not set." test.disabled = True return if os.path.exists(CONFIG_FILE_PATH): cp = ConfigParser.RawConfigParser() try: cp.read(CONFIG_FILE_PATH) defaults = cp.defaults() for key, value in defaults.items(): test.__dict__[key] = (_uniq(value) if key == 's3_store_bucket' else value) except ConfigParser.ParsingError as e: test.disabled_message = ("Failed to read test_s3.conf config " "file. Got error: %s" % e) test.disabled = True return from boto.s3.connection import S3Connection from boto.exception import S3ResponseError try: s3_host = test.s3_store_host access_key = test.s3_store_access_key secret_key = test.s3_store_secret_key bucket_name = test.s3_store_bucket except AttributeError as e: test.disabled_message = ("Failed to find required configuration " "options for S3 store. Got error: %s" % e) test.disabled = True return calling_format = get_calling_format(test.s3_store_bucket_url_format) s3_conn = S3Connection(access_key, secret_key, host=s3_host, is_secure=False, calling_format=calling_format) test.bucket = None try: buckets = s3_conn.get_all_buckets() for bucket in buckets: if bucket.name == bucket_name: test.bucket = bucket except S3ResponseError as e: test.disabled_message = ("Failed to connect to S3 with " "credentials, to find bucket. " "Got error: %s" % e) test.disabled = True return except TypeError as e: # This hack is necessary because of a bug in boto 1.9b: # http://code.google.com/p/boto/issues/detail?id=540 test.disabled_message = ("Failed to connect to S3 with " "credentials. Got error: %s" % e) test.disabled = True return test.s3_conn = s3_conn if not test.bucket: location = get_s3_location(test.s3_store_host) try: test.bucket = s3_conn.create_bucket(bucket_name, location=location) except S3ResponseError as e: test.disabled_message = ("Failed to create bucket. " "Got error: %s" % e) test.disabled = True return else: for key in test.bucket.list(): key.delete() def teardown_s3(test): if not test.disabled: # It's not possible to simply clear a bucket. You # need to loop over all the keys and delete them # all first... for key in test.bucket.list(): key.delete() test.s3_conn.delete_bucket(test.s3_store_bucket) def get_s3_uri(test, image_id): uri = ('s3://%(s3_store_access_key)s:%(s3_store_secret_key)s' % test.__dict__) uri += '@%(s3_conn)s/' % test.__dict__ uri += '%(s3_store_bucket)s/' % test.__dict__ uri += image_id return uri.replace('S3Connection:', '')