blob: c4c2041f098290e68bd7f474faa8dcb2a7a50c17 [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
David Kranzb9d97502013-05-01 15:55:04 -040015import logging
16import multiprocessing
17import time
18
19from tempest import clients
20from tempest.common import ssh
21from tempest.common.utils.data_utils import rand_name
22from tempest import exceptions
Attila Fazekas1e30d5d2013-07-30 14:38:20 +020023from tempest.openstack.common import importutils
David Kranzb9d97502013-05-01 15:55:04 -040024from 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
Marc Koderer69d3bea2013-07-18 08:32:11 +020096def stress_openstack(tests, duration, max_runs=None):
David Kranzb9d97502013-05-01 15:55:04 -040097 """
98 Workload driver. Executes an action function against a nova-cluster.
99
100 """
101 logfiles = admin_manager.config.stress.target_logfiles
102 log_check_interval = int(admin_manager.config.stress.log_check_interval)
103 if logfiles:
104 controller = admin_manager.config.stress.target_controller
105 computes = _get_compute_nodes(controller)
106 for node in computes:
107 do_ssh("rm -f %s" % logfiles, node)
108 processes = []
109 for test in tests:
110 if test.get('use_admin', False):
111 manager = admin_manager
112 else:
113 manager = clients.Manager()
Marc Koderer69d3bea2013-07-18 08:32:11 +0200114 for p_number in xrange(test.get('threads', 1)):
David Kranzb9d97502013-05-01 15:55:04 -0400115 if test.get('use_isolated_tenants', False):
116 username = rand_name("stress_user")
117 tenant_name = rand_name("stress_tenant")
118 password = "pass"
119 identity_client = admin_manager.identity_client
120 _, tenant = identity_client.create_tenant(name=tenant_name)
121 identity_client.create_user(username,
122 password,
123 tenant['id'],
124 "email")
125 manager = clients.Manager(username=username,
126 password="pass",
127 tenant_name=tenant_name)
Walter A. Boring IVb725e622013-07-11 17:21:33 -0700128
Attila Fazekas1e30d5d2013-07-30 14:38:20 +0200129 test_obj = importutils.import_class(test['action'])
Marc Koderer69d3bea2013-07-18 08:32:11 +0200130 test_run = test_obj(manager, logger, max_runs)
Walter A. Boring IVb725e622013-07-11 17:21:33 -0700131
132 kwargs = test.get('kwargs', {})
133 test_run.setUp(**dict(kwargs.iteritems()))
134
135 logger.debug("calling Target Object %s" %
136 test_run.__class__.__name__)
Walter A. Boring IVb725e622013-07-11 17:21:33 -0700137
Marc Koderer69d3bea2013-07-18 08:32:11 +0200138 mp_manager = multiprocessing.Manager()
139 shared_statistic = mp_manager.dict()
140 shared_statistic['runs'] = 0
141 shared_statistic['fails'] = 0
142
143 p = multiprocessing.Process(target=test_run.execute,
144 args=(shared_statistic,))
145
146 process = {'process': p,
147 'p_number': p_number,
148 'action': test['action'],
149 'statistic': shared_statistic}
150
151 processes.append(process)
David Kranzb9d97502013-05-01 15:55:04 -0400152 p.start()
153 end_time = time.time() + duration
154 had_errors = False
155 while True:
Marc Koderer69d3bea2013-07-18 08:32:11 +0200156 if max_runs is None:
157 remaining = end_time - time.time()
158 if remaining <= 0:
159 break
160 else:
161 remaining = log_check_interval
162 all_proc_term = True
163 for process in processes:
164 if process['process'].is_alive():
165 all_proc_term = False
166 break
167 if all_proc_term:
168 break
169
David Kranzb9d97502013-05-01 15:55:04 -0400170 time.sleep(min(remaining, log_check_interval))
171 if not logfiles:
172 continue
173 errors = _error_in_logs(logfiles, computes)
174 if errors:
175 had_errors = True
176 break
Walter A. Boring IVb725e622013-07-11 17:21:33 -0700177
Marc Koderer69d3bea2013-07-18 08:32:11 +0200178 for process in processes:
179 if process['process'].is_alive():
180 process['process'].terminate()
181 process['process'].join()
182
183 sum_fails = 0
184 sum_runs = 0
185
186 logger.info("Statistics (per process):")
187 for process in processes:
188 if process['statistic']['fails'] > 0:
189 had_errors = True
190 sum_runs += process['statistic']['runs']
191 sum_fails += process['statistic']['fails']
192 logger.info(" Process %d (%s): Run %d actions (%d failed)" %
193 (process['p_number'],
194 process['action'],
195 process['statistic']['runs'],
196 process['statistic']['fails']))
197 logger.info("Summary:")
198 logger.info("Run %d actions (%d failed)" %
199 (sum_runs, sum_fails))
Walter A. Boring IVb725e622013-07-11 17:21:33 -0700200
David Kranzb9d97502013-05-01 15:55:04 -0400201 if not had_errors:
202 logger.info("cleaning up")
Walter A. Boring IVb725e622013-07-11 17:21:33 -0700203 cleanup.cleanup(logger)
Marc Koderer888ddc42013-07-23 16:13:07 +0200204 if had_errors:
205 return 1
206 else:
207 return 0