blob: d170eb8c5f17ab2c79aacdfc23992c113ee9fe78 [file] [log] [blame]
David Kranzb9d97502013-05-01 15:55:04 -04001# Copyright 2013 Quanta Research Cambridge, Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import importlib
16import logging
17import multiprocessing
18import time
19
20from tempest import clients
21from tempest.common import ssh
22from tempest.common.utils.data_utils import rand_name
23from tempest import exceptions
24from tempest.stress import cleanup
25
26admin_manager = clients.AdminManager()
27
28# setup logging to file
29logging.basicConfig(
30 format='%(asctime)s %(process)d %(name)-20s %(levelname)-8s %(message)s',
31 datefmt='%m-%d %H:%M:%S',
32 filename="stress.debug.log",
33 filemode="w",
34 level=logging.DEBUG,
35)
36
37# define a Handler which writes INFO messages or higher to the sys.stdout
38_console = logging.StreamHandler()
39_console.setLevel(logging.INFO)
40# set a format which is simpler for console use
41format_str = '%(asctime)s %(process)d %(name)-20s: %(levelname)-8s %(message)s'
42_formatter = logging.Formatter(format_str)
43# tell the handler to use this format
44_console.setFormatter(_formatter)
45# add the handler to the root logger
46logger = logging.getLogger('tempest.stress')
47logger.addHandler(_console)
48
49
50def do_ssh(command, host):
51 username = admin_manager.config.stress.target_ssh_user
52 key_filename = admin_manager.config.stress.target_private_key_path
53 if not (username and key_filename):
54 return None
55 ssh_client = ssh.Client(host, username, key_filename=key_filename)
56 try:
57 return ssh_client.exec_command(command)
58 except exceptions.SSHExecCommandFailed:
59 return None
60
61
62def _get_compute_nodes(controller):
63 """
64 Returns a list of active compute nodes. List is generated by running
65 nova-manage on the controller.
66 """
67 nodes = []
68 cmd = "nova-manage service list | grep ^nova-compute"
69 output = do_ssh(cmd, controller)
70 if not output:
71 return nodes
72 # For example: nova-compute xg11eth0 nova enabled :-) 2011-10-31 18:57:46
73 # This is fragile but there is, at present, no other way to get this info.
74 for line in output.split('\n'):
75 words = line.split()
76 if len(words) > 0 and words[4] == ":-)":
77 nodes.append(words[1])
78 return nodes
79
80
81def _error_in_logs(logfiles, nodes):
82 """
83 Detect errors in the nova log files on the controller and compute nodes.
84 """
85 grep = 'egrep "ERROR|TRACE" %s' % logfiles
86 for node in nodes:
87 errors = do_ssh(grep, node)
88 if not errors:
89 return None
90 if len(errors) > 0:
91 logger.error('%s: %s' % (node, errors))
92 return errors
93 return None
94
95
Walter A. Boring IVb725e622013-07-11 17:21:33 -070096def get_action_object(path):
97 (module_part, _, obj_name) = path.rpartition('.')
98 return getattr(importlib.import_module(module_part), obj_name)
David Kranzb9d97502013-05-01 15:55:04 -040099
100
Marc Koderer69d3bea2013-07-18 08:32:11 +0200101def stress_openstack(tests, duration, max_runs=None):
David Kranzb9d97502013-05-01 15:55:04 -0400102 """
103 Workload driver. Executes an action function against a nova-cluster.
104
105 """
106 logfiles = admin_manager.config.stress.target_logfiles
107 log_check_interval = int(admin_manager.config.stress.log_check_interval)
108 if logfiles:
109 controller = admin_manager.config.stress.target_controller
110 computes = _get_compute_nodes(controller)
111 for node in computes:
112 do_ssh("rm -f %s" % logfiles, node)
113 processes = []
114 for test in tests:
115 if test.get('use_admin', False):
116 manager = admin_manager
117 else:
118 manager = clients.Manager()
Marc Koderer69d3bea2013-07-18 08:32:11 +0200119 for p_number in xrange(test.get('threads', 1)):
David Kranzb9d97502013-05-01 15:55:04 -0400120 if test.get('use_isolated_tenants', False):
121 username = rand_name("stress_user")
122 tenant_name = rand_name("stress_tenant")
123 password = "pass"
124 identity_client = admin_manager.identity_client
125 _, tenant = identity_client.create_tenant(name=tenant_name)
126 identity_client.create_user(username,
127 password,
128 tenant['id'],
129 "email")
130 manager = clients.Manager(username=username,
131 password="pass",
132 tenant_name=tenant_name)
Walter A. Boring IVb725e622013-07-11 17:21:33 -0700133
134 test_obj = get_action_object(test['action'])
Marc Koderer69d3bea2013-07-18 08:32:11 +0200135 test_run = test_obj(manager, logger, max_runs)
Walter A. Boring IVb725e622013-07-11 17:21:33 -0700136
137 kwargs = test.get('kwargs', {})
138 test_run.setUp(**dict(kwargs.iteritems()))
139
140 logger.debug("calling Target Object %s" %
141 test_run.__class__.__name__)
Walter A. Boring IVb725e622013-07-11 17:21:33 -0700142
Marc Koderer69d3bea2013-07-18 08:32:11 +0200143 mp_manager = multiprocessing.Manager()
144 shared_statistic = mp_manager.dict()
145 shared_statistic['runs'] = 0
146 shared_statistic['fails'] = 0
147
148 p = multiprocessing.Process(target=test_run.execute,
149 args=(shared_statistic,))
150
151 process = {'process': p,
152 'p_number': p_number,
153 'action': test['action'],
154 'statistic': shared_statistic}
155
156 processes.append(process)
David Kranzb9d97502013-05-01 15:55:04 -0400157 p.start()
158 end_time = time.time() + duration
159 had_errors = False
160 while True:
Marc Koderer69d3bea2013-07-18 08:32:11 +0200161 if max_runs is None:
162 remaining = end_time - time.time()
163 if remaining <= 0:
164 break
165 else:
166 remaining = log_check_interval
167 all_proc_term = True
168 for process in processes:
169 if process['process'].is_alive():
170 all_proc_term = False
171 break
172 if all_proc_term:
173 break
174
David Kranzb9d97502013-05-01 15:55:04 -0400175 time.sleep(min(remaining, log_check_interval))
176 if not logfiles:
177 continue
178 errors = _error_in_logs(logfiles, computes)
179 if errors:
180 had_errors = True
181 break
Walter A. Boring IVb725e622013-07-11 17:21:33 -0700182
Marc Koderer69d3bea2013-07-18 08:32:11 +0200183 for process in processes:
184 if process['process'].is_alive():
185 process['process'].terminate()
186 process['process'].join()
187
188 sum_fails = 0
189 sum_runs = 0
190
191 logger.info("Statistics (per process):")
192 for process in processes:
193 if process['statistic']['fails'] > 0:
194 had_errors = True
195 sum_runs += process['statistic']['runs']
196 sum_fails += process['statistic']['fails']
197 logger.info(" Process %d (%s): Run %d actions (%d failed)" %
198 (process['p_number'],
199 process['action'],
200 process['statistic']['runs'],
201 process['statistic']['fails']))
202 logger.info("Summary:")
203 logger.info("Run %d actions (%d failed)" %
204 (sum_runs, sum_fails))
Walter A. Boring IVb725e622013-07-11 17:21:33 -0700205
David Kranzb9d97502013-05-01 15:55:04 -0400206 if not had_errors:
207 logger.info("cleaning up")
Walter A. Boring IVb725e622013-07-11 17:21:33 -0700208 cleanup.cleanup(logger)
Marc Koderer888ddc42013-07-23 16:13:07 +0200209 if had_errors:
210 return 1
211 else:
212 return 0