9a55baa2c8
This patch enables the py35 job in tox.ini to run using ostestr. It also fixes a bytes encoding issue in the 'test_wsgi' functional test to make progress towards the community goal of enabling python3.5. Two other functional tests remain disabled and will need to be addressed in a later patch in order to fully complete the community goal - 'test_ssl' and 'test_reload'. These tests fail due to SSL handshake not working in python3.5 when using self-signed certificate and authority. Change-Id: Ie9071f1a93d8201f754ae3042d6e51b5de6d2e63
263 lines
9.4 KiB
Python
263 lines
9.4 KiB
Python
# Copyright 2014 Hewlett-Packard Development Company, L.P.
|
|
# 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.
|
|
|
|
import os
|
|
import re
|
|
import time
|
|
import unittest
|
|
|
|
import psutil
|
|
import requests
|
|
import six
|
|
from six.moves import http_client as http
|
|
|
|
from glance.tests import functional
|
|
from glance.tests.utils import execute
|
|
|
|
TEST_VAR_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__),
|
|
'../', 'var'))
|
|
|
|
|
|
def set_config_value(filepath, key, value):
|
|
"""Set 'key = value' in config file"""
|
|
replacement_line = '%s = %s\n' % (key, value)
|
|
match = re.compile('^%s\s+=' % key).match
|
|
with open(filepath, 'r+') as f:
|
|
lines = f.readlines()
|
|
f.seek(0, 0)
|
|
f.truncate()
|
|
for line in lines:
|
|
f.write(line if not match(line) else replacement_line)
|
|
|
|
|
|
class TestReload(functional.FunctionalTest):
|
|
"""Test configuration reload"""
|
|
|
|
def setUp(self):
|
|
self.workers = 1
|
|
super(TestReload, self).setUp()
|
|
|
|
def tearDown(self):
|
|
self.stop_servers()
|
|
super(TestReload, self).tearDown()
|
|
|
|
def ticker(self, message, seconds=60, tick=0.01):
|
|
"""
|
|
Allows repeatedly testing for an expected result
|
|
for a finite amount of time.
|
|
|
|
:param message: Message to display on timeout
|
|
:param seconds: Time in seconds after which we timeout
|
|
:param tick: Time to sleep before rechecking for expected result
|
|
:returns: 'True' or fails the test with 'message' on timeout
|
|
"""
|
|
# We default to allowing 60 seconds timeout but
|
|
# typically only a few hundredths of a second
|
|
# are needed.
|
|
num_ticks = seconds * (1.0 / tick)
|
|
count = 0
|
|
while count < num_ticks:
|
|
count += 1
|
|
time.sleep(tick)
|
|
yield
|
|
self.fail(message)
|
|
|
|
def _get_children(self, server):
|
|
pid = None
|
|
pid = self._get_parent(server)
|
|
process = psutil.Process(pid)
|
|
try:
|
|
# psutils version >= 2
|
|
children = process.children()
|
|
except AttributeError:
|
|
# psutils version < 2
|
|
children = process.get_children()
|
|
pids = set()
|
|
for child in children:
|
|
pids.add(child.pid)
|
|
return pids
|
|
|
|
def _get_parent(self, server):
|
|
if server == 'api':
|
|
return self.api_server.process_pid
|
|
elif server == 'registry':
|
|
return self.registry_server.process_pid
|
|
|
|
def _conffile(self, service):
|
|
conf_dir = os.path.join(self.test_dir, 'etc')
|
|
conf_filepath = os.path.join(conf_dir, '%s.conf' % service)
|
|
return conf_filepath
|
|
|
|
def _url(self, protocol, path):
|
|
return '%s://127.0.0.1:%d%s' % (protocol, self.api_port, path)
|
|
|
|
@unittest.skipIf(six.PY3, 'SSL handshakes are broken in PY3')
|
|
def test_reload(self):
|
|
"""Test SIGHUP picks up new config values"""
|
|
def check_pids(pre, post=None, workers=2):
|
|
if post is None:
|
|
if len(pre) == workers:
|
|
return True
|
|
else:
|
|
return False
|
|
if len(post) == workers:
|
|
# Check new children have different pids
|
|
if post.intersection(pre) == set():
|
|
return True
|
|
return False
|
|
self.api_server.fork_socket = False
|
|
self.registry_server.fork_socket = False
|
|
self.start_servers(fork_socket=False, **vars(self))
|
|
|
|
pre_pids = {}
|
|
post_pids = {}
|
|
|
|
# Test changing the workers value creates all new children
|
|
# This recycles the existing socket
|
|
msg = 'Start timeout'
|
|
for _ in self.ticker(msg):
|
|
for server in ('api', 'registry'):
|
|
pre_pids[server] = self._get_children(server)
|
|
if check_pids(pre_pids['api'], workers=1):
|
|
if check_pids(pre_pids['registry'], workers=1):
|
|
break
|
|
|
|
for server in ('api', 'registry'):
|
|
# Labour costs have fallen
|
|
set_config_value(self._conffile(server), 'workers', '2')
|
|
cmd = "kill -HUP %s" % self._get_parent(server)
|
|
execute(cmd, raise_error=True)
|
|
|
|
msg = 'Worker change timeout'
|
|
for _ in self.ticker(msg):
|
|
for server in ('api', 'registry'):
|
|
post_pids[server] = self._get_children(server)
|
|
if check_pids(pre_pids['registry'], post_pids['registry']):
|
|
if check_pids(pre_pids['api'], post_pids['api']):
|
|
break
|
|
|
|
# Test changing from http to https
|
|
# This recycles the existing socket
|
|
path = self._url('http', '/')
|
|
response = requests.get(path)
|
|
self.assertEqual(http.MULTIPLE_CHOICES, response.status_code)
|
|
del response # close socket so that process audit is reliable
|
|
|
|
pre_pids['api'] = self._get_children('api')
|
|
key_file = os.path.join(TEST_VAR_DIR, 'privatekey.key')
|
|
set_config_value(self._conffile('api'), 'key_file', key_file)
|
|
cert_file = os.path.join(TEST_VAR_DIR, 'certificate.crt')
|
|
set_config_value(self._conffile('api'), 'cert_file', cert_file)
|
|
cmd = "kill -HUP %s" % self._get_parent('api')
|
|
execute(cmd, raise_error=True)
|
|
|
|
msg = 'http to https timeout'
|
|
for _ in self.ticker(msg):
|
|
post_pids['api'] = self._get_children('api')
|
|
if check_pids(pre_pids['api'], post_pids['api']):
|
|
break
|
|
|
|
ca_file = os.path.join(TEST_VAR_DIR, 'ca.crt')
|
|
path = self._url('https', '/')
|
|
response = requests.get(path, verify=ca_file)
|
|
self.assertEqual(http.MULTIPLE_CHOICES, response.status_code)
|
|
del response
|
|
|
|
# Test https restart
|
|
# This recycles the existing socket
|
|
pre_pids['api'] = self._get_children('api')
|
|
cmd = "kill -HUP %s" % self._get_parent('api')
|
|
execute(cmd, raise_error=True)
|
|
|
|
msg = 'https restart timeout'
|
|
for _ in self.ticker(msg):
|
|
post_pids['api'] = self._get_children('api')
|
|
if check_pids(pre_pids['api'], post_pids['api']):
|
|
break
|
|
|
|
ca_file = os.path.join(TEST_VAR_DIR, 'ca.crt')
|
|
path = self._url('https', '/')
|
|
response = requests.get(path, verify=ca_file)
|
|
self.assertEqual(http.MULTIPLE_CHOICES, response.status_code)
|
|
del response
|
|
|
|
# Test changing the https bind_host
|
|
# This requires a new socket
|
|
pre_pids['api'] = self._get_children('api')
|
|
set_config_value(self._conffile('api'), 'bind_host', '127.0.0.1')
|
|
cmd = "kill -HUP %s" % self._get_parent('api')
|
|
execute(cmd, raise_error=True)
|
|
|
|
msg = 'https bind_host timeout'
|
|
for _ in self.ticker(msg):
|
|
post_pids['api'] = self._get_children('api')
|
|
if check_pids(pre_pids['api'], post_pids['api']):
|
|
break
|
|
|
|
path = self._url('https', '/')
|
|
response = requests.get(path, verify=ca_file)
|
|
self.assertEqual(http.MULTIPLE_CHOICES, response.status_code)
|
|
del response
|
|
|
|
# Test https -> http
|
|
# This recycles the existing socket
|
|
pre_pids['api'] = self._get_children('api')
|
|
set_config_value(self._conffile('api'), 'key_file', '')
|
|
set_config_value(self._conffile('api'), 'cert_file', '')
|
|
cmd = "kill -HUP %s" % self._get_parent('api')
|
|
execute(cmd, raise_error=True)
|
|
|
|
msg = 'https to http timeout'
|
|
for _ in self.ticker(msg):
|
|
post_pids['api'] = self._get_children('api')
|
|
if check_pids(pre_pids['api'], post_pids['api']):
|
|
break
|
|
|
|
path = self._url('http', '/')
|
|
response = requests.get(path)
|
|
self.assertEqual(http.MULTIPLE_CHOICES, response.status_code)
|
|
del response
|
|
|
|
# Test changing the http bind_host
|
|
# This requires a new socket
|
|
pre_pids['api'] = self._get_children('api')
|
|
set_config_value(self._conffile('api'), 'bind_host', '127.0.0.1')
|
|
cmd = "kill -HUP %s" % self._get_parent('api')
|
|
execute(cmd, raise_error=True)
|
|
|
|
msg = 'http bind_host timeout'
|
|
for _ in self.ticker(msg):
|
|
post_pids['api'] = self._get_children('api')
|
|
if check_pids(pre_pids['api'], post_pids['api']):
|
|
break
|
|
|
|
path = self._url('http', '/')
|
|
response = requests.get(path)
|
|
self.assertEqual(http.MULTIPLE_CHOICES, response.status_code)
|
|
del response
|
|
|
|
# Test logging configuration change
|
|
# This recycles the existing socket
|
|
conf_dir = os.path.join(self.test_dir, 'etc')
|
|
log_file = conf_dir + 'new.log'
|
|
self.assertFalse(os.path.exists(log_file))
|
|
set_config_value(self._conffile('api'), 'log_file', log_file)
|
|
cmd = "kill -HUP %s" % self._get_parent('api')
|
|
execute(cmd, raise_error=True)
|
|
msg = 'No new log file created'
|
|
for _ in self.ticker(msg):
|
|
if os.path.exists(log_file):
|
|
break
|