Merge "testpmd: Simplify GUEST's testpmd configuration"
[vswitchperf.git] / tools / pkt_gen / ixia / ixia.py
1 # Copyright 2015-2016 Intel Corporation.
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 """IXIA traffic generator model.
15
16 Provides a model for the IXIA traffic generator. In addition, provides
17 a number of generic "helper" functions that are used to do the "heavy
18 lifting".
19
20 This requires the following settings in your config file:
21
22 * TRAFFICGEN_IXIA_LIB_PATH
23     IXIA libraries path
24 * TRAFFICGEN_IXIA_HOST
25     IXIA chassis IP address
26 * TRAFFICGEN_IXIA_CARD
27     IXIA card
28 * TRAFFICGEN_IXIA_PORT1
29     IXIA Tx port
30 * TRAFFICGEN_IXIA_PORT2
31     IXIA Rx port
32
33 If any of these don't exist, the application will raise an exception
34 (EAFP).
35 """
36
37 import tkinter
38 import logging
39 import os
40
41 from collections import OrderedDict
42 from tools.pkt_gen import trafficgen
43 from conf import settings
44 from core.results.results_constants import ResultsConstants
45
46 _ROOT_DIR = os.path.dirname(os.path.realpath(__file__))
47 _IXIA_ROOT_DIR = settings.getValue('TRAFFICGEN_IXIA_ROOT_DIR')
48
49
50 def configure_env():
51     """Configure envionment for TCL.
52
53     """
54     os.environ['IXIA_HOME'] = _IXIA_ROOT_DIR
55
56     # USER MAY NEED TO CHANGE THESE IF USING OWN TCL LIBRARY
57     os.environ['TCL_HOME'] = _IXIA_ROOT_DIR
58     os.environ['TCLver'] = '8.5'
59
60     # USER NORMALLY DOES NOT CHANGE ANY LINES BELOW
61     os.environ['IxiaLibPath'] = os.path.expandvars('$IXIA_HOME/lib')
62     os.environ['IxiaBinPath'] = os.path.expandvars('$IXIA_HOME/bin')
63
64     os.environ['TCLLibPath'] = os.path.expandvars('$TCL_HOME/lib')
65     os.environ['TCLBinPath'] = os.path.expandvars('$TCL_HOME/bin')
66
67     os.environ['TCL_LIBRARY'] = os.path.expandvars('$TCLLibPath/tcl$TCLver')
68     os.environ['TK_LIBRARY'] = os.path.expandvars('$TCLLibPath/tk$TCLver')
69
70     os.environ['PATH'] = os.path.expandvars('$IxiaBinPath:.:$TCLBinPath:$PATH')
71     os.environ['TCLLIBPATH'] = os.path.expandvars('$IxiaLibPath')
72     os.environ['LD_LIBRARY_PATH'] = os.path.expandvars(
73         '$IxiaLibPath:$TCLLibPath:$LD_LIBRARY_PATH')
74
75     os.environ['IXIA_RESULTS_DIR'] = '/tmp/Ixia/Results'
76     os.environ['IXIA_LOGS_DIR'] = '/tmp/Ixia/Logs'
77     os.environ['IXIA_TCL_DIR'] = os.path.expandvars('$IxiaLibPath')
78     os.environ['IXIA_SAMPLES'] = os.path.expandvars('$IxiaLibPath/ixTcl1.0')
79     os.environ['IXIA_VERSION'] = '6.60.1000.11'
80
81
82 def _build_set_cmds(values, prefix='dict set'):
83     """Generate a list of 'dict set' args for Tcl.
84
85     Parse a dictionary and recursively build the arguments for the
86     'dict set' Tcl command, given that this is of the format:
87
88         dict set [name...] [key] [value]
89
90     For example, for a non-nested dict (i.e. a non-dict element):
91
92         dict set mydict mykey myvalue
93
94     For a nested dict (i.e. a dict element):
95
96         dict set mydict mysubdict mykey myvalue
97
98     :param values: Dictionary to yield values for
99     :param prefix: Prefix to append to output string. Generally the
100         already generated part of the command.
101
102     :yields: Output strings to be passed to a `Tcl` instance.
103     """
104     for key in values:
105         value = values[key]
106
107         # Not allowing derived dictionary types for now
108         # pylint: disable=unidiomatic-typecheck
109         if type(value) == dict:
110             _prefix = ' '.join([prefix, key]).strip()
111             for subkey in _build_set_cmds(value, _prefix):
112                 yield subkey
113             continue
114
115         # tcl doesn't recognise the strings "True" or "False", only "1"
116         # or "0". Special case to convert them
117         if type(value) == bool:
118             value = str(int(value))
119         else:
120             value = str(value)
121
122         if prefix:
123             yield ' '.join([prefix, key, value]).strip()
124         else:
125             yield ' '.join([key, value]).strip()
126
127
128 class Ixia(trafficgen.ITrafficGenerator):
129     """A wrapper around the IXIA traffic generator.
130
131     Runs different traffic generator tests through an Ixia traffic
132     generator chassis by generating TCL scripts from templates.
133     """
134     _script = os.path.join(settings.getValue('TRAFFICGEN_IXIA_3RD_PARTY'),
135                            'pass_fail.tcl')
136     _tclsh = tkinter.Tcl()
137     _logger = logging.getLogger(__name__)
138
139     def run_tcl(self, cmd):
140         """Run a TCL script using the TCL interpreter found in ``tkinter``.
141
142         :param cmd: Command to execute
143
144         :returns: Output of command, where applicable.
145         """
146         self._logger.debug('%s%s', trafficgen.CMD_PREFIX, cmd)
147
148         output = self._tclsh.eval(cmd)
149
150         return output.split()
151
152     def connect(self):
153         """Connect to Ixia chassis.
154         """
155         ixia_cfg = {
156             'lib_path': os.path.join(_IXIA_ROOT_DIR, 'lib', 'ixTcl1.0'),
157             'host': settings.getValue('TRAFFICGEN_IXIA_HOST'),
158             'card': settings.getValue('TRAFFICGEN_IXIA_CARD'),
159             'port1': settings.getValue('TRAFFICGEN_IXIA_PORT1'),
160             'port2': settings.getValue('TRAFFICGEN_IXIA_PORT2'),
161         }
162
163         self._logger.info('Connecting to IXIA...')
164
165         self._logger.debug('IXIA configuration configuration : %s', ixia_cfg)
166
167         configure_env()
168
169         for cmd in _build_set_cmds(ixia_cfg, prefix='set'):
170             self.run_tcl(cmd)
171
172         output = self.run_tcl('source {%s}' % self._script)
173         if output:
174             self._logger.critical(
175                 'An error occured when connecting to IXIA...')
176             raise RuntimeError('Ixia failed to initialise.')
177
178         self._logger.info('Connected to IXIA...')
179
180         return self
181
182     def disconnect(self):
183         """Disconnect from Ixia chassis.
184         """
185         self._logger.info('Disconnecting from IXIA...')
186
187         self.run_tcl('cleanUp')
188
189         self._logger.info('Disconnected from IXIA...')
190
191     def _send_traffic(self, flow, traffic):
192         """Send regular traffic.
193
194         :param flow: Flow specification
195         :param traffic: Traffic specification
196
197         :returns: Results from IXIA
198         """
199         params = {}
200
201         params['flow'] = flow
202         params['traffic'] = self.traffic_defaults.copy()
203
204         if traffic:
205             params['traffic'] = trafficgen.merge_spec(
206                 params['traffic'], traffic)
207
208         for cmd in _build_set_cmds(params):
209             self.run_tcl(cmd)
210
211         result = self.run_tcl('sendTraffic $flow $traffic')
212
213         return result
214
215     def send_burst_traffic(self, traffic=None, numpkts=100, duration=20):
216         """See ITrafficGenerator for description
217         """
218         flow = {
219             'numpkts': numpkts,
220             'duration': duration,
221             'type': 'stopStream',
222             'framerate': traffic['frame_rate'],
223         }
224
225         result = self._send_traffic(flow, traffic)
226
227         assert len(result) == 6  # fail-fast if underlying Tcl code changes
228
229         #TODO - implement Burst results setting via TrafficgenResults.
230
231     def send_cont_traffic(self, traffic=None, duration=30):
232         """See ITrafficGenerator for description
233         """
234         flow = {
235             'numpkts': 100,
236             'duration': duration,
237             'type': 'contPacket',
238             'framerate': traffic['frame_rate'],
239             'multipleStreams': traffic['multistream'],
240         }
241
242         result = self._send_traffic(flow, traffic)
243
244         return Ixia._create_result(result)
245
246     def start_cont_traffic(self, traffic=None, duration=30):
247         """See ITrafficGenerator for description
248         """
249         return self.send_cont_traffic(traffic, 0)
250
251     def stop_cont_traffic(self):
252         """See ITrafficGenerator for description
253         """
254         return self.run_tcl('stopTraffic')
255
256     def send_rfc2544_throughput(self, traffic=None, tests=1, duration=20, lossrate=0.0):
257         """See ITrafficGenerator for description
258         """
259         params = {}
260
261         params['config'] = {
262             'tests': tests,
263             'duration': duration,
264             'lossrate': lossrate,
265             'multipleStreams': traffic['multistream'],
266         }
267         params['traffic'] = self.traffic_defaults.copy()
268
269         if traffic:
270             params['traffic'] = trafficgen.merge_spec(
271                 params['traffic'], traffic)
272
273         for cmd in _build_set_cmds(params):
274             self.run_tcl(cmd)
275
276         # this will return a list with one result
277         result = self.run_tcl('rfcThroughputTest $config $traffic')
278
279         return Ixia._create_result(result)
280
281     @staticmethod
282     def _create_result(result):
283         """Create result based on list returned from tcl script.
284
285         :param result: list representing output from tcl script.
286
287         :returns: dictionary strings representing results from
288                     traffic generator.
289         """
290         assert len(result) == 8  # fail-fast if underlying Tcl code changes
291
292         if float(result[0]) == 0:
293             loss_rate = 100
294         else:
295             loss_rate = (float(result[0]) - float(result[1])) / float(result[0]) * 100
296         result_dict = OrderedDict()
297         # drop the first 4 elements as we don't use/need them. In
298         # addition, IxExplorer does not support latency or % line rate
299         # metrics so we have to return dummy values for these metrics
300         result_dict[ResultsConstants.THROUGHPUT_RX_FPS] = result[4]
301         result_dict[ResultsConstants.TX_RATE_FPS] = result[5]
302         result_dict[ResultsConstants.THROUGHPUT_RX_MBPS] = result[6]
303         result_dict[ResultsConstants.TX_RATE_MBPS] = result[7]
304         result_dict[ResultsConstants.FRAME_LOSS_PERCENT] = loss_rate
305         result_dict[ResultsConstants.TX_RATE_PERCENT] = \
306                                             ResultsConstants.UNKNOWN_VALUE
307         result_dict[ResultsConstants.THROUGHPUT_RX_PERCENT] = \
308                                             ResultsConstants.UNKNOWN_VALUE
309         result_dict[ResultsConstants.MIN_LATENCY_NS] = \
310                                             ResultsConstants.UNKNOWN_VALUE
311         result_dict[ResultsConstants.MAX_LATENCY_NS] = \
312                                             ResultsConstants.UNKNOWN_VALUE
313         result_dict[ResultsConstants.AVG_LATENCY_NS] = \
314                                             ResultsConstants.UNKNOWN_VALUE
315
316         return result_dict
317
318 if __name__ == '__main__':
319     TRAFFIC = {
320         'l3': {
321             'proto': 'udp',
322             'srcip': '10.1.1.1',
323             'dstip': '10.1.1.254',
324         },
325     }
326
327     with Ixia() as dev:
328         print(dev.send_burst_traffic(traffic=TRAFFIC))
329         print(dev.send_cont_traffic(traffic=TRAFFIC))
330         print(dev.send_rfc2544_throughput(traffic=TRAFFIC))