Tests use free TCP/IP ports

- test.KeystoneTest loads server (in ClientTests only) on free TCP/IP
  port and sets globals in keystone.test.client to point to the server.
  Client tests pick that info up and use the provided server ports.
- All defaults to 35357/5000 remain

Change-Id: Ifc4ff023e3004cf886d26eaefeb42e1b469e9b81
This commit is contained in:
Ziad Sawalha 2011-12-21 18:03:35 -06:00
parent a5e7587c8d
commit 1c6c351566
12 changed files with 169 additions and 56 deletions

View File

@ -16,9 +16,12 @@
import httplib
import json
import logging
import keystone.common.exception
LOG = logging.getLogger(__name__)
class ServiceClient(object):
"""Keystone v2.0 HTTP API client for normal service function.
@ -52,6 +55,7 @@ class ServiceClient(object):
:returns: httplib.HTTPResponse object
"""
LOG.debug("Connecting to %s" % self.auth_address)
if (self.is_ssl):
connection = httplib.HTTPSConnection(self.auth_address,
cert_file=self.cert_file)

View File

@ -50,6 +50,7 @@ HTTP_X_AUTHORIZATION
the client identity being passed in
"""
import logging
import sys
import optparse
@ -58,6 +59,8 @@ from keystone.routers.service import ServiceApi
from keystone.routers.admin import AdminApi
from keystone import version
logger = logging.getLogger('keystone.server')
def service_app_factory(global_conf, **local_conf):
"""paste.deploy app factory for creating OpenStack API server apps"""
@ -125,6 +128,9 @@ class Server():
self.config = config_name or self.name
self.key = None
self.server = None
self.port = None
self.host = None
self.protocol = None
def start(self, host=None, port=None, wait=True):
"""Starts the Keystone server
@ -179,19 +185,21 @@ class Server():
ca_certs=ca_certs,
cert_required=cert_required,
key=self.key)
self.protocol = 'https'
else:
self.server = wsgi.Server()
self.server.start(app, port, host,
key="%s-%s:%s" % (self.config, host, port))
self.protocol = 'http'
self.port = port
self.host = host
print "%s listening on %s://%s:%s" % (
self.name, ['http', 'https'][service_ssl], host, port)
# Wait until done
if wait:
# For Debugging LDAP
#from keystone.test import sampledata
#sampledata.load_fixture()
self.server.wait()
def stop(self):

View File

@ -48,6 +48,7 @@ test suites"""
import cgitb
import heapq
import logging
from nose import config as noseconfig
from nose import core
from nose import result
@ -63,11 +64,15 @@ import keystone
import keystone.server
import keystone.version
from keystone.common import config
from keystone.test import utils
from keystone.test import client as client_tests
TEST_DIR = os.path.abspath(os.path.dirname(__file__))
BASE_DIR = os.path.abspath(os.path.join(TEST_DIR, os.pardir, os.pardir))
TEST_CERT = os.path.join(BASE_DIR, 'examples/ssl/certs/middleware-key.pem')
logger = logging.getLogger('test')
class _AnsiColorizer(object):
"""
@ -346,7 +351,7 @@ class KeystoneTest(object):
end of test execution from the temporary space used to run these
tests).
"""
CONF_PARAMS = {'test_dir': TEST_DIR, 'base_dir': BASE_DIR}
config_params = {'test_dir': TEST_DIR, 'base_dir': BASE_DIR}
isSsl = False
config_name = None
test_files = None
@ -371,7 +376,9 @@ class KeystoneTest(object):
"""Populates a configuration template, and writes to a file pointer."""
template_fpath = os.path.join(TEST_DIR, 'etc', self.config_name)
conf_contents = open(template_fpath).read()
conf_contents = conf_contents % self.CONF_PARAMS
self.config_params['service_port'] = utils.get_unused_port()
self.config_params['admin_port'] = utils.get_unused_port()
conf_contents = conf_contents % self.config_params
self.conf_fp = tempfile.NamedTemporaryFile()
self.conf_fp.write(conf_contents)
self.conf_fp.flush()
@ -380,6 +387,7 @@ class KeystoneTest(object):
pass
def startServer(self):
""" Starts a Keystone server on random ports for testing """
self.server = None
self.admin_server = None
@ -391,7 +399,7 @@ class KeystoneTest(object):
os.environ['cert_file'] = TEST_CERT
# run the keystone server
print "Starting the keystone server..."
logger.info("Starting the keystone server...")
parser = optparse.OptionParser(version='%%prog %s' %
keystone.version.version())
@ -414,17 +422,33 @@ class KeystoneTest(object):
config_name='keystone-legacy-auth',
options=options, args=args)
service.start(wait=False)
# Client tests will use these globals to find out where
# the server is
client_tests.TEST_TARGET_SERVER_SERVICE_PROTOCOL = service.protocol
client_tests.TEST_TARGET_SERVER_SERVICE_ADDRESS = service.host
client_tests.TEST_TARGET_SERVER_SERVICE_PORT = service.port
except RuntimeError, e:
sys.exit("ERROR: %s" % e)
try:
# Load Admin API server
port = options.get('admin_port', None)
host = options.get('bind_host', None)
port = options.get('admin_port',
client_tests.TEST_TARGET_SERVER_ADMIN_PORT)
host = options.get('bind_host',
client_tests.TEST_TARGET_SERVER_ADMIN_ADDRESS)
admin = keystone.server.Server(name='Admin API',
config_name='admin',
options=options, args=args)
admin.start(host=host, port=port, wait=False)
# Client tests will use these globals to find out where
# the server is
client_tests.TEST_TARGET_SERVER_ADMIN_PROTOCOL = admin.protocol
client_tests.TEST_TARGET_SERVER_ADMIN_ADDRESS = admin.host
client_tests.TEST_TARGET_SERVER_ADMIN_PORT = admin.port
except RuntimeError, e:
service.stop()
sys.exit("ERROR: %s" % e)

View File

@ -0,0 +1,37 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 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 Tests
Client tests are tests that use HTTP(S) calls to a Keystone server to exercise
request/response test cases.
In order to avoid port conflicts, client tests use the global settings below
to know which server to talk to.
When a server is started for testing purposes (usually by the
keystone.test.KeystoneTest class) it will update these values so client tests
know where to find the server
"""
TEST_TARGET_SERVER_ADMIN_PROTOCOL = 'http'
TEST_TARGET_SERVER_ADMIN_ADDRESS = '127.0.0.1'
TEST_TARGET_SERVER_ADMIN_PORT = 35357
TEST_TARGET_SERVER_SERVICE_PROTOCOL = 'http'
TEST_TARGET_SERVER_SERVICE_ADDRESS = '127.0.0.1'
TEST_TARGET_SERVER_SERVICE_PORT = 5000

View File

@ -3,6 +3,7 @@ import unittest
import keystone.common.exception
import keystone.client
from keystone.test.functional.common import isSsl
from keystone.test import client as client_tests
class TestAdminClient(unittest.TestCase):
@ -16,11 +17,13 @@ class TestAdminClient(unittest.TestCase):
Run before each test.
"""
cert_file = isSsl()
self.client = keystone.client.AdminClient("127.0.0.1",
is_ssl=(cert_file != None),
cert_file=cert_file,
admin_name="admin",
admin_pass="secrete")
self.client = keystone.client.AdminClient(
client_tests.TEST_TARGET_SERVER_ADMIN_ADDRESS,
port=client_tests.TEST_TARGET_SERVER_ADMIN_PORT,
is_ssl=(cert_file is not None),
cert_file=cert_file,
admin_name="admin",
admin_pass="secrete")
def test_admin_validate_token(self):
"""
@ -77,9 +80,11 @@ class TestServiceClient(unittest.TestCase):
Run before each test.
"""
cert_file = isSsl()
self.client = keystone.client.ServiceClient("127.0.0.1",
is_ssl=(cert_file != None),
cert_file=cert_file)
self.client = keystone.client.ServiceClient(
client_tests.TEST_TARGET_SERVER_SERVICE_ADDRESS,
port=client_tests.TEST_TARGET_SERVER_SERVICE_PORT,
is_ssl=(cert_file is not None),
cert_file=cert_file)
def test_admin_get_token(self):
"""

View File

@ -2,6 +2,7 @@ import unittest2 as unittest
import keystone.common.exception
from keystone.test.functional import common
from keystone.test import client as client_tests
#
# Auth Token
@ -55,10 +56,14 @@ class TestQuantumMiddleware(common.MiddlewareTestCase):
json['access']
self.admin_token = access['token']['id']
settings = {'delay_auth_decision': '0',
'auth_host': '127.0.0.1',
'auth_port': '35357',
'auth_protocol': 'http',
'auth_uri': 'http://localhost:35357/',
'auth_host': client_tests.TEST_TARGET_SERVER_ADMIN_ADDRESS,
'auth_port': client_tests.TEST_TARGET_SERVER_ADMIN_PORT,
'auth_protocol':
client_tests.TEST_TARGET_SERVER_ADMIN_PROTOCOL,
'auth_uri': ('%s://%s:%s/' % \
(client_tests.TEST_TARGET_SERVER_SERVICE_PROTOCOL,
client_tests.TEST_TARGET_SERVER_SERVICE_ADDRESS,
client_tests.TEST_TARGET_SERVER_SERVICE_PORT)),
'auth_version': '2.0',
'auth_admin_token': self.admin_token,
'auth_admin_user': self.admin_username,

View File

@ -11,10 +11,10 @@ service-header-mappings = {
'swift' : 'X-Storage-Url',
'cdn' : 'X-CDN-Management-Url'}
service_host = 0.0.0.0
service_port = 5000
service_port = %(service_port)s
service_ssl = False
admin_host = 0.0.0.0
admin_port = 35357
admin_port = %(admin_port)s
admin_ssl = False
keystone-admin-role = Admin
keystone-service-admin-role = KeystoneServiceAdmin

View File

@ -11,10 +11,10 @@ service-header-mappings = {
'swift' : 'X-Storage-Url',
'cdn' : 'X-CDN-Management-Url'}
service_host = 0.0.0.0
service_port = 5000
service_port = %(service_port)s
service_ssl = False
admin_host = 0.0.0.0
admin_port = 35357
admin_port = %(admin_port)s
admin_ssl = False
keystone-admin-role = Admin
keystone-service-admin-role = KeystoneServiceAdmin

View File

@ -11,10 +11,10 @@ service-header-mappings = {
'swift' : 'X-Storage-Url',
'cdn' : 'X-CDN-Management-Url'}
service_host = 0.0.0.0
service_port = 5000
service_port = %(service_port)s
service_ssl = False
admin_host = 0.0.0.0
admin_port = 35357
admin_port = %(admin_port)s
admin_ssl = False
keystone-admin-role = Admin
keystone-service-admin-role = KeystoneServiceAdmin

View File

@ -10,10 +10,10 @@ service-header-mappings = {
'swift' : 'X-Storage-Url',
'cdn' : 'X-CDN-Management-Url'}
service_host = 0.0.0.0
service_port = 5000
service_port = %(service_port)s
service_ssl = True
admin_host = 0.0.0.0
admin_port = 35357
admin_port = %(admin_port)s
admin_ssl = True
keystone-admin-role = Admin
keystone-service-admin-role = KeystoneServiceAdmin

View File

@ -10,6 +10,7 @@ from xml.etree import ElementTree
from keystone import server
import keystone.backends.api as db_api
from keystone.test import client as client_tests
logger = logging.getLogger('test.functional.common')
@ -31,15 +32,16 @@ class HttpTestCase(unittest.TestCase):
* assertResponseStatus
"""
def request(self, host='127.0.0.1', port=80, method='GET', path='/',
headers=None, body=None, assert_status=None):
def request(self, host='127.0.0.1', protocol='http', port=80, method='GET',
path='/', headers=None, body=None, assert_status=None):
"""Perform request and fetch httplib.HTTPResponse from the server"""
# Initialize headers dictionary
headers = {} if not headers else headers
cert_file = isSsl()
if (cert_file is not None):
logger.debug("Connecting to %s://%s:%s", protocol, host, port)
if protocol == 'https':
cert_file = isSsl()
connection = httplib.HTTPSConnection(host, port,
cert_file=cert_file,
timeout=20)
@ -324,14 +326,17 @@ class ApiTestCase(RestfulTestCase):
def tearDown(self):
pass
def request(self, host='127.0.0.1', port=80, method='GET', path='/',
headers=None, body=None, assert_status=None, server=None):
def request(self, host='127.0.0.1', protocol='http', port=80, method='GET',
path='/', headers=None, body=None, assert_status=None,
server=None):
"""Overrides HttpTestCase and uses local calls"""
if self.use_server:
# Call a real server (bypass the override)
return super(ApiTestCase, self).request(host=host, port=port,
method=method, path=path, headers=headers,
body=body, assert_status=assert_status)
protocol=protocol, method=method,
path=path, headers=headers, body=body,
assert_status=assert_status)
req = Request.blank(path)
req.method = method
req.headers = headers
@ -413,8 +418,8 @@ class ApiTestCase(RestfulTestCase):
'Status code %s is not %s, as expected)\n\n%s' %
(response.status_int, assert_status, response.body))
def service_request(self, version='2.0', path='', port=5000, headers=None,
**kwargs):
def service_request(self, version='2.0', path='', port=None, headers=None,
host=None, protocol=None, **kwargs):
"""Returns a request to the service API"""
# Initialize headers dictionary
@ -422,17 +427,23 @@ class ApiTestCase(RestfulTestCase):
if self.use_server:
path = ApiTestCase._version_path(version, path)
if port is None:
port = client_tests.TEST_TARGET_SERVER_SERVICE_PORT
if host is None:
host = client_tests.TEST_TARGET_SERVER_SERVICE_ADDRESS
if protocol is None:
protocol = client_tests.TEST_TARGET_SERVER_SERVICE_PROTOCOL
if self.service_token:
headers['X-Auth-Token'] = self.service_token
elif self.admin_token:
headers['X-Auth-Token'] = self.admin_token
return self.restful_request(port=port, path=path, headers=headers,
server=self.service_api, **kwargs)
return self.restful_request(host=host, protocol=protocol, port=port,
path=path, headers=headers, server=self.service_api, **kwargs)
def admin_request(self, version='2.0', path='', port=35357, headers=None,
**kwargs):
def admin_request(self, version='2.0', path='', port=None, headers=None,
host=None, protocol=None, **kwargs):
"""Returns a request to the admin API"""
# Initialize headers dictionary
@ -440,12 +451,18 @@ class ApiTestCase(RestfulTestCase):
if self.use_server:
path = ApiTestCase._version_path(version, path)
if port is None:
port = client_tests.TEST_TARGET_SERVER_ADMIN_PORT
if host is None:
host = client_tests.TEST_TARGET_SERVER_ADMIN_ADDRESS
if protocol is None:
protocol = client_tests.TEST_TARGET_SERVER_ADMIN_PROTOCOL
if self.admin_token:
headers['X-Auth-Token'] = self.admin_token
return self.restful_request(port=port, path=path, headers=headers,
server=self.admin_api, **kwargs)
return self.restful_request(host=host, protocol=protocol, port=port,
path=path, headers=headers, server=self.admin_api, **kwargs)
@staticmethod
def _version_path(version, path):
@ -1490,18 +1507,20 @@ class MiddlewareTestCase(FunctionalTestCase):
super(MiddlewareTestCase, self).setUp()
if settings is None:
settings = {'delay_auth_decision': '0',
'auth_host': '127.0.0.1',
'auth_port': '35357',
'auth_protocol': 'http',
'auth_uri': 'http://localhost:35357/',
'auth_host': client_tests.TEST_TARGET_SERVER_ADMIN_ADDRESS,
'auth_port': client_tests.TEST_TARGET_SERVER_ADMIN_PORT,
'auth_protocol':
client_tests.TEST_TARGET_SERVER_ADMIN_PROTOCOL,
'auth_uri': ('%s://%s:%s/' % \
(client_tests.TEST_TARGET_SERVER_SERVICE_PROTOCOL,
client_tests.TEST_TARGET_SERVER_SERVICE_ADDRESS,
client_tests.TEST_TARGET_SERVER_SERVICE_PORT)),
'admin_token': self.admin_token,
'auth_admin_user': self.admin_username,
'auth_admin_password': self.admin_password}
cert_file = isSsl()
if cert_file:
settings['auth_protocol'] = 'https'
settings['certfile'] = cert_file
settings['auth_uri'] = 'https://localhost:35357/'
if isinstance(middleware, tuple):
self.test_middleware = HeaderApp()
for filter in middleware:
@ -1543,12 +1562,11 @@ class MiddlewareTestCase(FunctionalTestCase):
self.assertEquals(resp.status_int, 401)
headers = resp.headers
self.assertTrue("WWW-Authenticate" in headers)
if isSsl():
self.assertEquals(headers['WWW-Authenticate'],
"Keystone uri='https://localhost:35357/'")
else:
self.assertEquals(headers['WWW-Authenticate'],
"Keystone uri='http://localhost:35357/'")
self.assertEquals(headers['WWW-Authenticate'],
"Keystone uri='%s://%s:%s/'" % \
(client_tests.TEST_TARGET_SERVER_SERVICE_PROTOCOL,
client_tests.TEST_TARGET_SERVER_SERVICE_ADDRESS,
client_tests.TEST_TARGET_SERVER_SERVICE_PORT))
def test_401_bad_token(self):
resp = Request.blank('/',

View File

@ -1,4 +1,5 @@
import re
import socket
import string
from lxml import etree
@ -46,3 +47,14 @@ class XMLTools():
return False
return True
def get_unused_port():
"""
Returns an unused port on localhost.
"""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('localhost', 0))
addr, port = s.getsockname()
s.close()
return port