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