Add support for single server ping test
[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 import ipaddress
47
48 from yardstick.benchmark.context.model import Context
49 from yardstick.benchmark.runners import base as base_runner
50 from yardstick.cmdparser import CmdParser
51
52
53 class TaskParser(object):
54     '''Parser for task config files in yaml format'''
55     def __init__(self, path):
56         self.path = path
57
58     def parse(self):
59         '''parses the task file and return an context and scenario instances'''
60         print "Parsing task config:", self.path
61         try:
62             with open(self.path) as stream:
63                 cfg = yaml.load(stream)
64         except IOError as ioerror:
65             sys.exit(ioerror)
66
67         if cfg["schema"] != "yardstick:task:0.1":
68             sys.exit("error: file %s has unknown schema %s" % (self.path,
69                                                                cfg["schema"]))
70
71         # TODO: support one or many contexts? Many would simpler and precise
72         if "context" in cfg:
73             context_cfgs = [cfg["context"]]
74         else:
75             context_cfgs = cfg["contexts"]
76
77         for cfg_attrs in context_cfgs:
78             context = Context()
79             context.init(cfg_attrs)
80
81         run_in_parallel = cfg.get("run_in_parallel", False)
82
83         # TODO we need something better here, a class that represent the file
84         return cfg["scenarios"], run_in_parallel
85
86
87 def atexit_handler():
88     '''handler for process termination'''
89     base_runner.Runner.terminate_all()
90
91     if len(Context.list) > 0:
92         print "Undeploying all contexts"
93         for context in Context.list:
94             context.undeploy()
95
96
97 def is_ip_addr(addr):
98     '''check if string addr is an IP address'''
99     try:
100         ipaddress.ip_address(addr)
101         return True
102     except ValueError:
103         return False
104
105
106 def run_one_scenario(scenario_cfg, output_file):
107     '''run one scenario using context'''
108     key_filename = pkg_resources.resource_filename(
109         'yardstick.resources', 'files/yardstick_key')
110
111     host = Context.get_server(scenario_cfg["host"])
112
113     runner_cfg = scenario_cfg["runner"]
114     runner_cfg['host'] = host.public_ip
115     runner_cfg['user'] = host.context.user
116     runner_cfg['key_filename'] = key_filename
117     runner_cfg['output_filename'] = output_file
118
119     if "target" in scenario_cfg:
120         if is_ip_addr(scenario_cfg["target"]):
121             scenario_cfg["ipaddr"] = scenario_cfg["target"]
122         else:
123             target = Context.get_server(scenario_cfg["target"])
124
125             # get public IP for target server, some scenarios require it
126             if target.public_ip:
127                 runner_cfg['target'] = target.public_ip
128
129             # TODO scenario_cfg["ipaddr"] is bad naming
130             if host.context != target.context:
131                 # target is in another context, get its public IP
132                 scenario_cfg["ipaddr"] = target.public_ip
133             else:
134                 # target is in the same context, get its private IP
135                 scenario_cfg["ipaddr"] = target.private_ip
136
137     runner = base_runner.Runner.get(runner_cfg)
138
139     print "Starting runner of type '%s'" % runner_cfg["type"]
140     runner.run(scenario_cfg["type"], scenario_cfg)
141
142     return runner
143
144
145 def runner_join(runner):
146     '''join (wait for) a runner, exit process at runner failure'''
147     status = runner.join()
148     base_runner.Runner.release(runner)
149     if status != 0:
150         sys.exit("Runner failed")
151
152
153 def main():
154     '''yardstick main'''
155
156     atexit.register(atexit_handler)
157
158     prog_args = CmdParser().parse_args()
159
160     parser = TaskParser(prog_args.taskfile[0])
161     scenarios, run_in_parallel = parser.parse()
162
163     if prog_args.parse_only:
164         sys.exit(0)
165
166     for context in Context.list:
167         context.deploy()
168
169     runners = []
170     if run_in_parallel:
171         for scenario in scenarios:
172             runner = run_one_scenario(scenario, prog_args.output_file)
173             runners.append(runner)
174
175         # Wait for runners to finish
176         for runner in runners:
177             runner_join(runner)
178             print "Runner ended, output in", prog_args.output_file
179     else:
180         # run serially
181         for scenario in scenarios:
182             runner = run_one_scenario(scenario, prog_args.output_file)
183             runner_join(runner)
184             print "Runner ended, output in", prog_args.output_file
185
186     if prog_args.keep_deploy:
187         # keep deployment, forget about stack (hide it for exit handler)
188         Context.list = []
189     else:
190         for context in Context.list:
191             context.undeploy()
192
193     print "Done, exiting"
194
195 if __name__ == '__main__':
196     main()