Fix health policy tests

- Change clustering client delete and trigger_action to use request
  method instead of delete or post directly.  This way exceptions will
  be propagated.
- Modified clustering client wait_for_status to accept list of expected
  statuses.
- Use tenacity to retry delete cluster and detach policy calls if it
  returns Conflict because of locked cluster.
- Wait for action status to become SUCCEEDED or FAILED when calling
  delete cluster or detach policy.
- Use custom detach policy as cleanup action so that it waits for 5
  seconds to allow the health manager to stop health check threads
- Use separate prepare_and_cleanup_nova_server function call for
  nova_server_cluster test and health_policy tests to avoid race
  condition between the tests.
- Make list of dictionary keys before indexing (need for python 3)

Depends-On: https://review.openstack.org/#/c/624248/
Closes-Bug: 1797270
Change-Id: If34be5d01fc9e58d548f68e85b691d3ed7bd4c51
This commit is contained in:
Duc Truong 2018-11-28 22:40:34 +00:00
parent c7fbb42f17
commit f61b01f746
6 changed files with 138 additions and 47 deletions

View File

@ -10,3 +10,4 @@ oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0
oslo.utils>=3.33.0 # Apache-2.0
six>=1.10.0 # MIT
tempest>=17.1.0 # Apache-2.0
tenacity>=4.9.0 # Apache-2.0

View File

@ -84,7 +84,7 @@ class ClusteringAPIClient(rest_client.RestClient):
def delete_obj(self, obj_type, obj_id):
uri = '{0}/{1}/{2}'.format(self.version, obj_type, obj_id)
resp, body = self.delete(uri)
resp, body = self.request('DELETE', uri)
return self.get_resp(resp, body)
@ -106,7 +106,7 @@ class ClusteringAPIClient(rest_client.RestClient):
uri = '{0}/{1}/{2}/actions'.format(self.version, obj_type, obj_id)
if params is not None:
params = jsonutils.dumps(params)
resp, body = self.post(uri, body=params)
resp, body = self.request('POST', uri, body=params)
return self.get_resp(resp, body)
@ -148,6 +148,11 @@ class ClusteringAPIClient(rest_client.RestClient):
return self.get_resp(resp, body)
def wait_for_status(self, obj_type, obj_id, expected_status, timeout=None):
if isinstance(expected_status, list):
expected_status_list = expected_status
else:
expected_status_list = [expected_status]
if timeout is None:
timeout = CONF.clustering.wait_timeout
@ -155,7 +160,7 @@ class ClusteringAPIClient(rest_client.RestClient):
while timeout > 0:
time.sleep(5)
res = self.get_obj(obj_type, obj_id)
if res['body']['status'] == expected_status:
if res['body']['status'] in expected_status_list:
return res
timeout = timeout_watch.leftover(True)

View File

@ -12,7 +12,12 @@
# limitations under the License.
import functools
import subprocess
import os
import signal
from six.moves import BaseHTTPServer
from six.moves import http_client as http
import tempfile
import tenacity
from tempest.lib.common.utils import data_utils
from tempest.lib import exceptions
@ -33,9 +38,13 @@ def api_microversion(api_microversion):
return decorator
def prepare_and_cleanup_for_nova_server(base):
def prepare_and_cleanup_for_nova_server(base, cidr, spec=None):
keypair_name = create_a_keypair(base, is_admin_manager=False)
base.spec = constants.spec_nova_server
if spec is None:
base.spec = constants.spec_nova_server
else:
base.spec = spec
base.spec['properties']['key_name'] = keypair_name
base.addCleanup(delete_a_keypair, base, keypair_name,
is_admin_manager=False)
@ -44,7 +53,7 @@ def prepare_and_cleanup_for_nova_server(base):
network_id = create_a_network(base, name=n_name)
base.addCleanup(delete_a_network, base, network_id)
subnet_id = create_a_subnet(base, network_id, "192.168.199.0/24")
subnet_id = create_a_subnet(base, network_id, cidr)
base.addCleanup(delete_a_subnet, base, subnet_id)
@ -147,14 +156,29 @@ def list_clusters(base):
return res['body']
def _return_last_value(retry_state):
return retry_state.outcome.result()
@tenacity.retry(
retry=(tenacity.retry_if_exception_type(exceptions.Conflict) |
tenacity.retry_if_result(lambda x: x is False)),
wait=tenacity.wait_fixed(2),
retry_error_callback=_return_last_value,
stop=tenacity.stop_after_attempt(5)
)
def delete_a_cluster(base, cluster_id, wait_timeout=None):
"""Utility function that deletes a Senlin cluster."""
res = base.client.delete_obj('clusters', cluster_id)
action_id = res['location'].split('/actions/')[1]
base.client.wait_for_status('actions', action_id, 'SUCCEEDED',
wait_timeout)
action = base.client.wait_for_status(
'actions', action_id, ['SUCCEEDED', 'FAILED'], wait_timeout)
if action['body']['status'] == 'FAILED':
return False
base.client.wait_for_delete("clusters", cluster_id, wait_timeout)
return
return True
def create_a_node(base, profile_id, cluster_id=None, metadata=None,
@ -285,6 +309,13 @@ def cluster_attach_policy(base, cluster_id, policy_id,
return res['body']['status_reason']
@tenacity.retry(
retry=(tenacity.retry_if_exception_type(exceptions.Conflict) |
tenacity.retry_if_result(lambda x: x is False)),
wait=tenacity.wait_fixed(2),
retry_error_callback=_return_last_value,
stop=tenacity.stop_after_attempt(5)
)
def cluster_detach_policy(base, cluster_id, policy_id,
expected_status='SUCCEEDED', wait_timeout=None):
"""Utility function that detach a policy from cluster."""
@ -296,8 +327,12 @@ def cluster_detach_policy(base, cluster_id, policy_id,
}
res = base.client.trigger_action('clusters', cluster_id, params=params)
action_id = res['location'].split('/actions/')[1]
res = base.client.wait_for_status('actions', action_id, expected_status,
wait_timeout)
res = base.client.wait_for_status(
'actions', action_id, ['SUCCEEDED', 'FAILED'], wait_timeout)
if res['body']['status'] == 'FAILED':
return False
return res['body']['status_reason']
@ -590,16 +625,46 @@ def post_messages(base, queue_name, messages):
raise Exception(msg)
def start_http_server(port):
return subprocess.Popen(
['python', '-m', 'SimpleHTTPServer', port], cwd='/tmp',
stdout=subprocess.PIPE, stderr=subprocess.STDOUT
)
def start_http_server(port=5050):
def _get_http_handler_class(filename):
class StaticHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def do_GET(self):
data = b'healthy\n'
self.send_response(http.OK)
self.send_header('Content-Length', str(len(data)))
self.end_headers()
self.wfile.write(data)
return
def log_message(self, fmt, *args):
msg = fmt % args
with open(filename, 'a+') as tmp:
tmp.write("%s\n" % msg)
return
return StaticHTTPRequestHandler
server_address = ('127.0.0.1', port)
new_file, filename = tempfile.mkstemp()
handler_class = _get_http_handler_class(filename)
httpd = BaseHTTPServer.HTTPServer(server_address, handler_class)
pid = os.fork()
if pid == 0:
httpd.serve_forever()
else:
return pid, filename
def terminate_http_server(p):
if not p.poll():
p.terminate()
return p.stdout.read()
def terminate_http_server(pid, filename):
os.kill(pid, signal.SIGKILL)
return
if not os.path.isfile(filename):
return ''
with open(filename, 'r') as f:
contents = f.read()
os.remove(filename)
return contents

View File

@ -19,7 +19,6 @@ from senlin_tempest_plugin.common import utils
from senlin_tempest_plugin.tests.functional import base
@decorators.skip_because(bug=1797270)
class TestHealthPolicy(base.BaseSenlinFunctionalTest):
def setUp(self):
super(TestHealthPolicy, self).setUp()
@ -39,19 +38,30 @@ class TestHealthPolicy(base.BaseSenlinFunctionalTest):
policy_id = utils.create_a_policy(self, spec)
del_policy = utils.get_a_policy(self, policy_id)
self.addCleanup(utils.delete_a_policy, self, del_policy['id'], True)
http_server = utils.start_http_server('5050')
self.addCleanup(utils.terminate_http_server, http_server)
http_server, log_file = utils.start_http_server()
self.addCleanup(utils.terminate_http_server, http_server, log_file)
def detach_policy():
# ignore BadRequest exceptions that are raised because
# policy is not attached
try:
utils.cluster_detach_policy(self, self.cluster_id,
del_policy['id'])
# wait to let health checks stop
time.sleep(5)
except exc.BadRequest:
pass
# Attach health policy to cluster
utils.cluster_attach_policy(self, self.cluster_id, del_policy['id'])
self.addCleanup(utils.cluster_detach_policy, self, self.cluster_id,
del_policy['id'])
self.addCleanup(detach_policy)
# wait for health checks to run
time.sleep(15)
# check that URL was queried for each node as part of health check
out = utils.terminate_http_server(http_server)
out = utils.terminate_http_server(http_server, log_file)
self.assertTrue(out.count('GET') >= 1)
@decorators.attr(type=['functional'])

View File

@ -19,11 +19,14 @@ from senlin_tempest_plugin.common import utils
from senlin_tempest_plugin.tests.integration import base
@decorators.skip_because(bug=1797270)
class TestHealthPolicy(base.BaseSenlinIntegrationTest):
def setUp(self):
super(TestHealthPolicy, self).setUp()
spec = constants.spec_nova_server
spec['properties']['networks'][0]['network'] = 'private-hp'
utils.prepare_and_cleanup_for_nova_server(self, "192.168.199.0/24",
spec)
self.profile_id = utils.create_a_profile(self)
self.addCleanup(utils.delete_a_profile, self, self.profile_id)
self.cluster_id = utils.create_a_cluster(self, self.profile_id,
@ -31,6 +34,17 @@ class TestHealthPolicy(base.BaseSenlinIntegrationTest):
desired_capacity=1)
self.addCleanup(utils.delete_a_cluster, self, self.cluster_id)
def _detach_policy(self, policy_id):
# ignore BadRequest exceptions that are raised because
# policy is not attached
try:
utils.cluster_detach_policy(self, self.cluster_id, policy_id)
# wait to let health checks stop
time.sleep(5)
except exceptions.BadRequest:
pass
@decorators.attr(type=['integration'])
def test_health_policy(self):
# Create a health policy
@ -38,19 +52,18 @@ class TestHealthPolicy(base.BaseSenlinIntegrationTest):
policy_id = utils.create_a_policy(self, spec)
del_policy = utils.get_a_policy(self, policy_id)
self.addCleanup(utils.delete_a_policy, self, del_policy['id'], True)
http_server = utils.start_http_server('5050')
self.addCleanup(utils.terminate_http_server, http_server)
http_server, log_file = utils.start_http_server()
self.addCleanup(utils.terminate_http_server, http_server, log_file)
# Attach health policy to cluster
utils.cluster_attach_policy(self, self.cluster_id, del_policy['id'])
self.addCleanup(utils.cluster_detach_policy, self, self.cluster_id,
del_policy['id'])
self.addCleanup(self._detach_policy, del_policy['id'])
# wait for health checks to run
time.sleep(5)
# check that URL was queried for each node as part of health check
out = utils.terminate_http_server(http_server)
out = utils.terminate_http_server(http_server, log_file)
self.assertTrue(out.count('GET') == 1)
def _get_node(self, expected_len, index):
@ -62,7 +75,7 @@ class TestHealthPolicy(base.BaseSenlinIntegrationTest):
}
self.assertTrue(len(nodes) == expected_len)
return nodes.keys()[index], nodes.values()[index]
return list(nodes.keys())[index], list(nodes.values())[index]
@decorators.attr(type=['integration'])
def test_multiple_detection_modes_any(self):
@ -72,8 +85,8 @@ class TestHealthPolicy(base.BaseSenlinIntegrationTest):
policy_id = utils.create_a_policy(self, spec)
del_policy = utils.get_a_policy(self, policy_id)
self.addCleanup(utils.delete_a_policy, self, del_policy['id'], True)
http_server = utils.start_http_server('5050')
self.addCleanup(utils.terminate_http_server, http_server)
http_server, log_file = utils.start_http_server()
self.addCleanup(utils.terminate_http_server, http_server, log_file)
# manually shutdown server
node_id, server_id = self._get_node(1, 0)
@ -86,8 +99,7 @@ class TestHealthPolicy(base.BaseSenlinIntegrationTest):
# Attach health policy to cluster
utils.cluster_attach_policy(self, self.cluster_id, del_policy['id'])
self.addCleanup(utils.cluster_detach_policy, self, self.cluster_id,
del_policy['id'])
self.addCleanup(self._detach_policy, del_policy['id'])
# wait for health checks to run and recover node
time.sleep(15)
@ -114,8 +126,8 @@ class TestHealthPolicy(base.BaseSenlinIntegrationTest):
policy_id = utils.create_a_policy(self, spec)
del_policy = utils.get_a_policy(self, policy_id)
self.addCleanup(utils.delete_a_policy, self, del_policy['id'], True)
http_server = utils.start_http_server('5050')
self.addCleanup(utils.terminate_http_server, http_server)
http_server, log_file = utils.start_http_server()
self.addCleanup(utils.terminate_http_server, http_server, log_file)
# manually shutdown server
node_id, server_id = self._get_node(1, 0)
@ -128,8 +140,7 @@ class TestHealthPolicy(base.BaseSenlinIntegrationTest):
# Attach health policy to cluster
utils.cluster_attach_policy(self, self.cluster_id, del_policy['id'])
self.addCleanup(utils.cluster_detach_policy, self, self.cluster_id,
del_policy['id'])
self.addCleanup(self._detach_policy, del_policy['id'])
# wait for health checks to run
time.sleep(15)
@ -144,7 +155,7 @@ class TestHealthPolicy(base.BaseSenlinIntegrationTest):
# check that URL was queried because ALL_FAILED
# was specified in the policy
out = utils.terminate_http_server(http_server)
out = utils.terminate_http_server(http_server, log_file)
self.assertTrue(out.count('GET') >= 0)
# wait for health checks to run and recover node
@ -178,8 +189,7 @@ class TestHealthPolicy(base.BaseSenlinIntegrationTest):
# Attach health policy to cluster without http server running
utils.cluster_attach_policy(self, self.cluster_id, del_policy['id'])
self.addCleanup(utils.cluster_detach_policy, self, self.cluster_id,
del_policy['id'])
self.addCleanup(self._detach_policy, del_policy['id'])
# wait for health checks to run
time.sleep(15)

View File

@ -23,7 +23,7 @@ class TestNovaServerCluster(base.BaseSenlinIntegrationNonAdminTest):
def setUp(self):
super(TestNovaServerCluster, self).setUp()
utils.prepare_and_cleanup_for_nova_server(self)
utils.prepare_and_cleanup_for_nova_server(self, "192.168.199.0/24")
self.profile_id = utils.create_a_profile(self, self.spec)
self.addCleanup(utils.delete_a_profile, self, self.profile_id)