942b46be96cd2daa0eb2a4ff410320757b96f7e9
[yardstick.git] / yardstick / main.py
1 #!/usr/bin/env python
2
3 ##############################################################################
4 # Copyright (c) 2015 Ericsson AB and others.
5 #
6 # All rights reserved. This program and the accompanying materials
7 # are made available under the terms of the Apache License, Version 2.0
8 # which accompanies this distribution, and is available at
9 # http://www.apache.org/licenses/LICENSE-2.0
10 ##############################################################################
11
12 """ yardstick - command line tool for managing benchmarks
13
14     Example invocation:
15     $ yardstick samples/ping-task.yaml
16
17     Servers are the same as VMs (Nova call them servers in the API)
18
19     Many tests use a client/server architecture. A test client is configured
20     to use a specific test server e.g. using an IP address. This is true for
21     example iperf. In some cases the test server is included in the kernel
22     (ping, pktgen) and no additional software is needed on the server. In other
23     cases (iperf) a server process needs to be installed and started
24
25     One server is required to host the test client program (such as ping or
26     iperf). In the task file this server is called host.
27
28     A server can be the _target_ of a test client (think ping destination
29     argument). A target server is optional but needed in most test scenarios.
30     In the task file this server is called target. This is probably the same
31     as DUT in existing terminology.
32
33     Existing terminology:
34     https://www.ietf.org/rfc/rfc1242.txt (throughput/latency)
35     https://www.ietf.org/rfc/rfc2285.txt (DUT/SUT)
36
37     New terminology:
38     NFV TST
39
40 """
41
42 import sys
43 import yaml
44 import atexit
45 import pkg_resources
46
47 from yardstick.benchmark.context.model import Context
48 from yardstick.benchmark.runners import base as base_runner
49 from yardstick.cmdparser import CmdParser
50
51
52 class TaskParser(object):
53     '''Parser for task config files in yaml format'''
54     def __init__(self, path):
55         self.path = path
56
57     def parse(self):
58         '''parses the task file and return an context and scenario instances'''
59         print "Parsing task config:", self.path
60         try:
61             with open(self.path) as stream:
62                 cfg = yaml.load(stream)
63         except IOError as ioerror:
64             sys.exit(ioerror)
65
66         if cfg["schema"] != "yardstick:task:0.1":
67             sys.exit("error: file %s has unknown schema %s" % (self.path,
68                                                                cfg["schema"]))
69
70         # TODO: support one or many contexts? Many would simpler and precise
71         if "context" in cfg:
72             context_cfgs = [cfg["context"]]
73         else:
74             context_cfgs = cfg["contexts"]
75
76         for cfg_attrs in context_cfgs:
77             context = Context()
78             context.init(cfg_attrs)
79
80         run_in_parallel = cfg.get("run_in_parallel", False)
81
82         # TODO we need something better here, a class that represent the file
83         return cfg["scenarios"], run_in_parallel
84
85
86 def atexit_handler():
87     '''handler for process termination'''
88     base_runner.Runner.terminate_all()
89
90     if len(Context.list) > 0:
91         print "Undeploying all contexts"
92         for context in Context.list:
93             context.undeploy()
94
95
96 def run_one_scenario(scenario_cfg, output_file):
97     '''run one scenario using context'''
98     key_filename = pkg_resources.resource_filename(
99         'yardstick.resources', 'files/yardstick_key')
100
101     host = Context.get_server(scenario_cfg["host"])
102
103     runner_cfg = scenario_cfg["runner"]
104     runner_cfg['host'] = host.public_ip
105     runner_cfg['user'] = host.context.user
106     runner_cfg['key_filename'] = key_filename
107     runner_cfg['output_filename'] = output_file
108
109     if "target" in scenario_cfg:
110         target = Context.get_server(scenario_cfg["target"])
111
112         # get public IP for target server, some scenarios require it
113         if target.public_ip:
114             runner_cfg['target'] = target.public_ip
115
116         # TODO scenario_cfg["ipaddr"] is bad naming
117         if host.context != target.context:
118             # target is in another context, get its public IP
119             scenario_cfg["ipaddr"] = target.public_ip
120         else:
121             # target is in the same context, get its private IP
122             scenario_cfg["ipaddr"] = target.private_ip
123
124     runner = base_runner.Runner.get(runner_cfg)
125
126     print "Starting runner of type '%s'" % runner_cfg["type"]
127     runner.run(scenario_cfg["type"], scenario_cfg)
128
129     return runner
130
131
132 def runner_join(runner):
133     '''join (wait for) a runner, exit process at runner failure'''
134     status = runner.join()
135     base_runner.Runner.release(runner)
136     if status != 0:
137         sys.exit("Runner failed")
138
139
140 def main():
141     '''yardstick main'''
142
143     atexit.register(atexit_handler)
144
145     prog_args = CmdParser().parse_args()
146
147     parser = TaskParser(prog_args.taskfile[0])
148     scenarios, run_in_parallel = parser.parse()
149
150     if prog_args.parse_only:
151         sys.exit(0)
152
153     for context in Context.list:
154         context.deploy()
155
156     runners = []
157     if run_in_parallel:
158         for scenario in scenarios:
159             runner = run_one_scenario(scenario, prog_args.output_file)
160             runners.append(runner)
161
162         # Wait for runners to finish
163         for runner in runners:
164             runner_join(runner)
165             print "Runner ended, output in", prog_args.output_file
166     else:
167         # run serially
168         for scenario in scenarios:
169             runner = run_one_scenario(scenario, prog_args.output_file)
170             runner_join(runner)
171             print "Runner ended, output in", prog_args.output_file
172
173     if prog_args.keep_deploy:
174         # keep deployment, forget about stack (hide it for exit handler)
175         Context.list = []
176     else:
177         for context in Context.list:
178             context.undeploy()
179
180     print "Done, exiting"
181
182 if __name__ == '__main__':
183     main()