blob: 9a453fbed8d82c47badb56d3b68463d6ae3756d7 [file] [log] [blame]
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +03001import os
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +03002import time
3import signal
4import logging
5import argparse
6import functools
koder aka kdanilov39e449e2016-12-17 15:15:26 +02007import contextlib
8from typing import List, Tuple, Any, Callable, IO, cast, Optional, Iterator
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +03009from yaml import load as _yaml_load
10
koder aka kdanilov22d134e2016-11-08 11:33:19 +020011
12YLoader = Callable[[IO], Any]
13yaml_load = None # type: YLoader
14
15
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030016try:
17 from yaml import CLoader
koder aka kdanilov22d134e2016-11-08 11:33:19 +020018 yaml_load = cast(YLoader, functools.partial(_yaml_load, Loader=CLoader))
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030019except ImportError:
koder aka kdanilov22d134e2016-11-08 11:33:19 +020020 yaml_load = cast(YLoader, _yaml_load)
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030021
22
23import texttable
24
25try:
26 import faulthandler
27except ImportError:
28 faulthandler = None
29
koder aka kdanilove7e1a4d2016-12-17 20:29:52 +020030import agent
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030031
koder aka kdanilove7e1a4d2016-12-17 20:29:52 +020032from . import utils, node
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +020033from .storage import make_storage, Storage
koder aka kdanilov22d134e2016-11-08 11:33:19 +020034from .config import Config
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030035from .logger import setup_loggers
koder aka kdanilov39e449e2016-12-17 15:15:26 +020036from .stage import Stage
koder aka kdanilov22d134e2016-11-08 11:33:19 +020037from .test_run_class import TestRun
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030038
39
koder aka kdanilov39e449e2016-12-17 15:15:26 +020040# stages
41from .ceph import DiscoverCephStage
42from .openstack import DiscoverOSStage
43from .fuel import DiscoverFuelStage
44from .run_test import CollectInfoStage, ExplicitNodesStage, SaveNodesStage, RunTestsStage
45from .report import ConsoleReportStage, HtmlReportStage
46from .sensors import StartSensorsStage, CollectSensorsStage
47
48
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030049logger = logging.getLogger("wally")
50
51
koder aka kdanilov39e449e2016-12-17 15:15:26 +020052@contextlib.contextmanager
53def log_stage(stage: Stage) -> Iterator[None]:
54 logger.info("Start " + stage.name())
55 try:
56 yield
57 except utils.StopTestError as exc:
58 logger.error("Exception during %s: %r", stage.name(), exc)
59 except Exception:
60 logger.exception("During %s", stage.name())
61
62
koder aka kdanilov22d134e2016-11-08 11:33:19 +020063def list_results(path: str) -> List[Tuple[str, str, str, str]]:
koder aka kdanilov73084622016-11-16 21:51:08 +020064 results = [] # type: List[Tuple[float, str, str, str, str]]
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030065
koder aka kdanilov22d134e2016-11-08 11:33:19 +020066 for dir_name in os.listdir(path):
67 full_path = os.path.join(path, dir_name)
68
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030069 try:
koder aka kdanilov22d134e2016-11-08 11:33:19 +020070 stor = make_storage(full_path, existing=True)
71 except Exception as exc:
72 logger.warning("Can't load folder {}. Error {}".format(full_path, exc))
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030073
koder aka kdanilov73084622016-11-16 21:51:08 +020074 comment = cast(str, stor['info/comment'])
75 run_uuid = cast(str, stor['info/run_uuid'])
76 run_time = cast(float, stor['info/run_time'])
koder aka kdanilov22d134e2016-11-08 11:33:19 +020077 test_types = ""
koder aka kdanilov73084622016-11-16 21:51:08 +020078 results.append((run_time,
koder aka kdanilov22d134e2016-11-08 11:33:19 +020079 run_uuid,
80 test_types,
koder aka kdanilov73084622016-11-16 21:51:08 +020081 time.ctime(run_time),
koder aka kdanilov22d134e2016-11-08 11:33:19 +020082 '-' if comment is None else comment))
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030083
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030084 results.sort()
koder aka kdanilov22d134e2016-11-08 11:33:19 +020085 return [i[1:] for i in results]
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030086
87
koder aka kdanilov22d134e2016-11-08 11:33:19 +020088def log_nodes_statistic_stage(ctx: TestRun) -> None:
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030089 utils.log_nodes_statistic(ctx.nodes)
90
91
92def parse_args(argv):
93 descr = "Disk io performance test suite"
94 parser = argparse.ArgumentParser(prog='wally', description=descr)
koder aka kdanilov22d134e2016-11-08 11:33:19 +020095 parser.add_argument("-l", '--log-level', help="print some extra log info")
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +020096 parser.add_argument("-s", '--settings-dir', default=None,
97 help="Folder to store key/settings/history files")
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +030098
99 subparsers = parser.add_subparsers(dest='subparser_name')
100
101 # ---------------------------------------------------------------------
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200102 report_parser = subparsers.add_parser('ls', help='list all results')
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300103 report_parser.add_argument("result_storage", help="Folder with test results")
104
105 # ---------------------------------------------------------------------
106 compare_help = 'compare two results'
107 report_parser = subparsers.add_parser('compare', help=compare_help)
108 report_parser.add_argument("data_path1", help="First folder with test results")
109 report_parser.add_argument("data_path2", help="Second folder with test results")
110
111 # ---------------------------------------------------------------------
112 report_help = 'run report on previously obtained results'
113 report_parser = subparsers.add_parser('report', help=report_help)
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300114 report_parser.add_argument("data_dir", help="folder with rest results")
115
116 # ---------------------------------------------------------------------
117 test_parser = subparsers.add_parser('test', help='run tests')
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200118 test_parser.add_argument('--build-description', type=str, default="Build info")
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300119 test_parser.add_argument('--build-id', type=str, default="id")
120 test_parser.add_argument('--build-type', type=str, default="GA")
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200121 test_parser.add_argument('--dont-collect', action='store_true', help="Don't collect cluster info")
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200122 test_parser.add_argument('-n', '--no-tests', action='store_true', help="Don't run tests")
123 test_parser.add_argument('--load-report', action='store_true')
124 test_parser.add_argument("-k", '--keep-vm', action='store_true', help="Don't remove test vm's")
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300125 test_parser.add_argument("-d", '--dont-discover-nodes', action='store_true',
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200126 help="Don't connect/discover fuel nodes")
127 test_parser.add_argument('--no-report', action='store_true', help="Skip report stages")
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200128 test_parser.add_argument('--result-dir', default=None, help="Save results to DIR", metavart="DIR")
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300129 test_parser.add_argument("comment", help="Test information")
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200130 test_parser.add_argument("config_file", help="Yaml config file", nargs='?', default=None)
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300131
132 # ---------------------------------------------------------------------
koder aka kdanilove7e1a4d2016-12-17 20:29:52 +0200133 test_parser = subparsers.add_parser('resume', help='resume tests')
134 test_parser.add_argument("storage_dir", help="Path to test directory")
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300135
136 return parser.parse_args(argv[1:])
137
138
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +0200139def get_config_path(config: Config, opts_value: Optional[str]) -> str:
140 if opts_value is None and 'settings_dir' not in config:
141 val = "~/.wally"
142 elif opts_value is not None:
143 val = opts_value
144 else:
145 val = config.settings_dir
146
147 return os.path.abspath(os.path.expanduser(val))
148
149
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200150def main(argv: List[str]) -> int:
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300151 if faulthandler is not None:
152 faulthandler.register(signal.SIGUSR1, all_threads=True)
153
154 opts = parse_args(argv)
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200155 stages = [] # type: List[Stage]
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200156
157 # stop mypy from telling that config & storage might be undeclared
158 config = None # type: Config
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +0200159 storage = None # type: Storage
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300160
161 if opts.subparser_name == 'test':
koder aka kdanilove7e1a4d2016-12-17 20:29:52 +0200162 file_name = os.path.abspath(opts.config_file)
163 with open(file_name) as fd:
164 config = Config(yaml_load(fd.read())) # type: ignore
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300165
koder aka kdanilove7e1a4d2016-12-17 20:29:52 +0200166 config.storage_url, config.run_uuid = utils.get_uniq_path_uuid(config.results_dir)
167 config.comment = opts.comment
168 config.keep_vm = opts.keep_vm
169 config.no_tests = opts.no_tests
170 config.dont_discover_nodes = opts.dont_discover_nodes
171 config.build_id = opts.build_id
172 config.build_description = opts.build_description
173 config.build_type = opts.build_type
174 config.settings_dir = get_config_path(config, opts.settings_dir)
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300175
koder aka kdanilove7e1a4d2016-12-17 20:29:52 +0200176 storage = make_storage(config.storage_url)
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200177
koder aka kdanilove7e1a4d2016-12-17 20:29:52 +0200178 storage['config'] = config # type: ignore
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300179
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200180 stages.append(DiscoverCephStage) # type: ignore
181 stages.append(DiscoverOSStage) # type: ignore
182 stages.append(DiscoverFuelStage) # type: ignore
183 stages.append(ExplicitNodesStage) # type: ignore
184 stages.append(SaveNodesStage) # type: ignore
185 stages.append(StartSensorsStage) # type: ignore
186 stages.append(RunTestsStage) # type: ignore
187 stages.append(CollectSensorsStage) # type: ignore
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300188
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200189 if not opts.dont_collect:
190 stages.append(CollectInfoStage) # type: ignore
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300191
koder aka kdanilove7e1a4d2016-12-17 20:29:52 +0200192 storage['cli'] = argv
193
194 elif opts.subparser_name == 'resume':
195 storage = make_storage(opts.storage_dir, existing=True)
196 config = storage.load(Config, 'config')
197 # TODO: fix this
198 raise NotImplementedError("Resume in not fully implemented")
199
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300200 elif opts.subparser_name == 'ls':
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200201 tab = texttable.Texttable(max_width=200)
202 tab.set_deco(tab.HEADER | tab.VLINES | tab.BORDER)
203 tab.set_cols_align(["l", "l", "l", "l"])
204 tab.header(["Name", "Tests", "Run at", "Comment"])
205 tab.add_rows(list_results(opts.result_storage))
206 print(tab.draw())
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300207 return 0
208
209 elif opts.subparser_name == 'report':
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200210 storage = make_storage(opts.data_dir, existing=True)
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +0200211 config.settings_dir = get_config_path(config, opts.settings_dir)
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300212
213 elif opts.subparser_name == 'compare':
koder aka kdanilov73084622016-11-16 21:51:08 +0200214 # x = run_test.load_data_from_path(opts.data_path1)
215 # y = run_test.load_data_from_path(opts.data_path2)
216 # print(run_test.IOPerfTest.format_diff_for_console(
217 # [x['io'][0], y['io'][0]]))
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300218 return 0
219
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200220 report_stages = [] # type: List[Stage]
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200221 if not getattr(opts, "no_report", False):
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200222 report_stages.append(ConsoleReportStage) # type: ignore
223 report_stages.append(HtmlReportStage) # type: ignore
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300224
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200225 # log level is not a part of config
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300226 if opts.log_level is not None:
227 str_level = opts.log_level
228 else:
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200229 str_level = config.get('logging/log_level', 'INFO')
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300230
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200231 setup_loggers(getattr(logging, str_level), log_fd=storage.get_stream('log', "w"))
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200232 logger.info("All info would be stored into %r", config.storage_url)
233
234 ctx = TestRun(config, storage)
koder aka kdanilove7e1a4d2016-12-17 20:29:52 +0200235 ctx.rpc_code, ctx.default_rpc_plugins = node.get_rpc_server_code()
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300236
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200237 stages.sort(key=lambda x: x.priority)
238
239 # TODO: run only stages, which have configs
240 failed = False
241 cleanup_stages = []
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300242 for stage in stages:
koder aka kdanilove7e1a4d2016-12-17 20:29:52 +0200243 if stage.config_block is not None:
244 if stage.config_block not in ctx.config:
245 continue
246
247 cleanup_stages.append(stage)
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200248 try:
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200249 with log_stage(stage):
250 stage.run(ctx)
251 except:
252 failed = True
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300253 break
254
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200255 logger.debug("Start cleanup")
256 cleanup_failed = False
257 for stage in cleanup_stages[::-1]:
258 try:
259 with log_stage(stage):
260 stage.cleanup(ctx)
261 except:
262 cleanup_failed = True
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300263
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200264 if not failed:
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300265 for report_stage in report_stages:
266 with log_stage(report_stage):
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200267 report_stage.run(ctx)
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300268
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200269 logger.info("All info is stored into %r", config.storage_url)
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300270
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200271 if failed or cleanup_failed:
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200272 logger.error("Tests are failed. See error details in log above")
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300273 return 1
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200274 else:
275 logger.info("Tests finished successfully")
276 return 0