Merge "Make TRex IMIX traffic mode configurable"
[yardstick.git] / yardstick / network_services / traffic_profile / http_ixload.py
1 # Copyright (c) 2016-2017 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
15 import sys
16 import os
17 import logging
18 import collections
19
20 # ixload uses its own py2. So importing jsonutils fails. So adding below
21 # workaround to support call from yardstick
22 try:
23     from oslo_serialization import jsonutils
24 except ImportError:
25     import json as jsonutils
26
27 from yardstick.common import exceptions
28
29 try:
30     from IxLoad import IxLoad, StatCollectorUtils
31 except ImportError:
32     IxLoad = exceptions.ErrorClass
33     StatCollectorUtils = exceptions.ErrorClass
34
35
36 LOG = logging.getLogger(__name__)
37 CSV_FILEPATH_NAME = 'IxL_statResults.csv'
38
39 STATS_TO_GET = (
40     'HTTP_Client.csv',
41     'HTTP_Server.csv',
42     'L2-3 Stats for Client Ports.csv',
43     'L2-3 Stats for Server Ports.csv',
44     'IxLoad Detailed Report.html',
45     'IxLoad Detailed Report.pdf'
46 )
47
48 HTTP_CLIENT_STATS = [
49     ["HTTP Client", "TCP Connections Established", "kSum"],
50     ["HTTP Client", "TCP Connection Requests Failed", "kSum"],
51     ["HTTP Client", "HTTP Simulated Users", "kSum"],
52     ["HTTP Client", "HTTP Concurrent Connections", "kSum"],
53     ["HTTP Client", "HTTP Connections", "kSum"],
54     ["HTTP Client", "HTTP Transactions", "kSum"],
55     ["HTTP Client", "HTTP Connection Attempts", "kSum"]
56 ]
57
58 HTTP_SERVER_STATS = [
59     ["HTTP Server", "TCP Connections Established", "kSum"],
60     ["HTTP Server", "TCP Connection Requests Failed", "kSum"]
61 ]
62
63
64 INCOMING_STAT_RECORD_TEMPLATE = """
65 =====================================
66 INCOMING STAT RECORD >>> %s
67 Len = %s
68 %s
69 %s
70 =====================================
71 """
72
73 INCOMING_STAT_INTERVAL_TEMPLATE = """
74 =====================================
75 Incoming stats: Time interval: %s
76 Incoming stats: Time interval: %s
77 =====================================
78 """
79
80
81 def validate_non_string_sequence(value, default=None, raise_exc=None):
82     if isinstance(value, collections.Sequence) and not isinstance(value, str):
83         return value
84     if raise_exc:
85         raise raise_exc  # pylint: disable=raising-bad-type
86     return default
87
88
89 def join_non_strings(separator, *non_strings):
90     try:
91         non_strings = validate_non_string_sequence(non_strings[0], raise_exc=RuntimeError)
92     except (IndexError, RuntimeError):
93         pass
94     return str(separator).join(str(non_string) for non_string in non_strings)
95
96
97 class IXLOADHttpTest(object):
98
99     def __init__(self, test_input):
100         self.ix_load = None
101         self.stat_utils = None
102         self.remote_server = None
103         self.config_file = None
104         self.results_on_windows = None
105         self.result_dir = None
106         self.chassis = None
107         self.card = None
108         self.ports_to_reassign = None
109         self.test_input = jsonutils.loads(test_input)
110         self.parse_run_test()
111
112     @staticmethod
113     def format_ports_for_reassignment(ports):
114         formatted = [join_non_strings(';', p) for p in ports if len(p) == 3]
115         LOG.debug('for client ports:%s', os.linesep.join(formatted))
116         return formatted
117
118     def reassign_ports(self, test, repository, ports_to_reassign):
119         LOG.debug('ReassignPorts: %s %s', test, repository)
120
121         chassis_chain = repository.cget('chassisChain')
122         LOG.debug('chassischain: %s', chassis_chain)
123         client_ports = ports_to_reassign[0::2]
124         server_ports = ports_to_reassign[1::2]
125
126         client_ports = self.format_ports_for_reassignment(client_ports)
127         LOG.debug('Reassigning client ports: %s', client_ports)
128         server_ports = self.format_ports_for_reassignment(server_ports)
129         LOG.debug('Reassigning server ports: %s', server_ports)
130         ports_to_set = client_ports + server_ports
131
132         try:
133             LOG.debug('Reassigning ports: %s', ports_to_set)
134             test.setPorts(ports_to_set)
135         except Exception:
136             LOG.error('Error: Could not remap port assignment for: %s',
137                       ports_to_set)
138             self.ix_load.delete(repository)
139             self.ix_load.disconnect()
140             raise
141
142     @staticmethod
143     def stat_collector(*args):
144         LOG.debug(INCOMING_STAT_RECORD_TEMPLATE, args, len(args), args[0], args[1])
145
146     @staticmethod
147     def IxL_StatCollectorCommand(*args):
148         stats = args[1][3]
149         timestamp = args[1][1]
150         LOG.debug(INCOMING_STAT_INTERVAL_TEMPLATE, timestamp, stats)
151
152     @staticmethod
153     def set_results_dir(test_controller, results_on_windows):
154         """
155         If the folder doesn't exists on the Windows Client PC,
156         IxLoad will automatically create it.
157         """
158         try:
159             test_controller.setResultDir(results_on_windows)
160         except Exception:
161             LOG.error('Error creating results dir on Win: %s',
162                       results_on_windows)
163             raise
164
165     def load_config_file(self, config_file):
166         try:
167             LOG.debug(config_file)
168             repository = self.ix_load.new("ixRepository", name=config_file)
169             return repository
170         except Exception:
171             LOG.error('Error: IxLoad config file not found: %s', config_file)
172             raise
173
174     def start_http_test(self):
175         self.ix_load = IxLoad()
176
177         LOG.debug('--- ixLoad obj: %s', self.ix_load)
178         try:
179             self.ix_load.connect(self.remote_server)
180         except Exception:
181             raise
182
183         log_tag = "IxLoad-api"
184         log_name = "reprun"
185         logger = self.ix_load.new("ixLogger", log_tag, 1)
186         log_engine = logger.getEngine()
187         log_engine.setLevels(self.ix_load.ixLogger.kLevelDebug,
188                              self.ix_load.ixLogger.kLevelInfo)
189         log_engine.setFile(log_name, 2, 256, 1)
190
191         # Initialize stat collection utilities
192         self.stat_utils = StatCollectorUtils()
193
194         test_controller = self.ix_load.new("ixTestController", outputDir=1)
195
196         repository = self.load_config_file(self.config_file)
197
198         # Get the first test on the testList
199         test_name = repository.testList[0].cget("name")
200         test = repository.testList.getItem(test_name)
201
202         self.set_results_dir(test_controller, self.results_on_windows)
203
204         test.config(statsRequired=1, enableResetPorts=1, csvInterval=2,
205                     enableForceOwnership=True)
206
207         #  ---- Remap ports ----
208         try:
209             self.reassign_ports(test, repository, self.ports_to_reassign)
210         except Exception:  # pylint: disable=broad-except
211             LOG.exception("Exception occurred during reassign_ports")
212
213         # -----------------------------------------------------------------------
214         # Set up stat Collection
215         # -----------------------------------------------------------------------
216         test_server_handle = test_controller.getTestServerHandle()
217         self.stat_utils.Initialize(test_server_handle)
218
219         # Clear any stats that may have been registered previously
220         self.stat_utils.ClearStats()
221
222         # Define the stats we would like to collect
223         self.stat_utils.AddStat(caption="Watch_Stat_1",
224                                 statSourceType="HTTP Client",
225                                 statName="TCP Connections Established",
226                                 aggregationType="kSum",
227                                 filterList={})
228
229         self.stat_utils.AddStat(caption="Watch_Stat_2",
230                                 statSourceType="HTTP Client",
231                                 statName="TCP Connection Requests Failed",
232                                 aggregationType="kSum",
233                                 filterList={})
234
235         self.stat_utils.AddStat(caption="Watch_Stat_3",
236                                 statSourceType="HTTP Server",
237                                 statName="TCP Connections Established",
238                                 aggregationType="kSum",
239                                 filterList={})
240
241         self.stat_utils.AddStat(caption="Watch_Stat_4",
242                                 statSourceType="HTTP Server",
243                                 statName="TCP Connection Requests Failed",
244                                 aggregationType="kSum",
245                                 filterList={})
246
247         self.stat_utils.StartCollector(self.IxL_StatCollectorCommand)
248
249         test_controller.run(test)
250         self.ix_load.waitForTestFinish()
251
252         test_controller.releaseConfigWaitFinish()
253
254         # Stop the collector (running in the tcl event loop)
255         self.stat_utils.StopCollector()
256
257         # Cleanup
258         test_controller.generateReport(detailedReport=1, format="PDF;HTML")
259         test_controller.releaseConfigWaitFinish()
260
261         self.ix_load.delete(test)
262         self.ix_load.delete(test_controller)
263         self.ix_load.delete(logger)
264         self.ix_load.delete(log_engine)
265
266         LOG.debug('Retrieving CSV stats from Windows Client PC ...')
267         for stat_file in STATS_TO_GET:
268             enhanced_stat_file = stat_file.replace('-', '')
269             enhanced_stat_file = enhanced_stat_file.replace(' ', '_')
270             enhanced_stat_file = enhanced_stat_file.replace('__', '_')
271
272             LOG.debug('Getting csv stat file: %s', stat_file)
273             src_file = os.path.join(self.results_on_windows, stat_file)
274             dst_file = os.path.join(self.result_dir, '_'.join(['ixLoad', enhanced_stat_file]))
275             self.ix_load.retrieveFileCopy(src_file, dst_file)
276
277         self.ix_load.disconnect()
278
279     def parse_run_test(self):
280         self.remote_server = self.test_input["remote_server"]
281         LOG.debug("remote tcl server: %s", self.remote_server)
282
283         self.config_file = self.test_input["ixload_cfg"]
284         LOG.debug("ixload config: %s", self.remote_server)
285
286         self.results_on_windows = 'C:/Results'
287         self.result_dir = self.test_input["result_dir"]
288         self.chassis = self.test_input["ixia_chassis"]
289         LOG.debug("remote ixia chassis: %s", self.chassis)
290
291         self.card = self.test_input["IXIA"]["card"]
292         self.ports_to_reassign = [
293             [self.chassis, self.card, port] for port in
294             self.test_input["IXIA"]["ports"]
295         ]
296
297         LOG.debug("Ports to be reassigned: %s", self.ports_to_reassign)
298
299
300 def main(args):
301     # Get the args from cmdline and parse and run the test
302     test_input = "".join(args[1:])
303     if test_input:
304         ixload_obj = IXLOADHttpTest(test_input)
305         ixload_obj.start_http_test()
306
307 if __name__ == '__main__':
308     LOG.info("Start http_ixload test")
309     main(sys.argv)