Fix exception handling ixload
[yardstick.git] / yardstick / network_services / traffic_profile / http_ixload.py
1 # Copyright (c) 2016-2019 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
28 class ErrorClass(object):
29
30     def __init__(self, *args, **kwargs):
31         if 'test' not in kwargs:
32             raise RuntimeError
33
34     def __getattr__(self, item):
35         raise AttributeError
36
37
38 class InvalidRxfFile(Exception):
39     message = 'Loaded rxf file has unexpected format'
40
41
42 try:
43     from IxLoad import IxLoad, StatCollectorUtils
44 except ImportError:
45     IxLoad = ErrorClass
46     StatCollectorUtils = ErrorClass
47
48 LOG = logging.getLogger(__name__)
49 CSV_FILEPATH_NAME = 'IxL_statResults.csv'
50
51 STATS_TO_GET = (
52     'HTTP_Client.csv',
53     'HTTP_Server.csv',
54     'L2-3 Stats for Client Ports.csv',
55     'L2-3 Stats for Server Ports.csv',
56     'IxLoad Detailed Report.html',
57     'IxLoad Detailed Report.pdf'
58 )
59
60 HTTP_CLIENT_STATS = [
61     ["HTTP Client", "TCP Connections Established", "kSum"],
62     ["HTTP Client", "TCP Connection Requests Failed", "kSum"],
63     ["HTTP Client", "HTTP Simulated Users", "kSum"],
64     ["HTTP Client", "HTTP Concurrent Connections", "kSum"],
65     ["HTTP Client", "HTTP Connections", "kSum"],
66     ["HTTP Client", "HTTP Transactions", "kSum"],
67     ["HTTP Client", "HTTP Connection Attempts", "kSum"]
68 ]
69
70 HTTP_SERVER_STATS = [
71     ["HTTP Server", "TCP Connections Established", "kSum"],
72     ["HTTP Server", "TCP Connection Requests Failed", "kSum"]
73 ]
74
75
76 INCOMING_STAT_RECORD_TEMPLATE = """
77 =====================================
78 INCOMING STAT RECORD >>> %s
79 Len = %s
80 %s
81 %s
82 =====================================
83 """
84
85 INCOMING_STAT_INTERVAL_TEMPLATE = """
86 =====================================
87 Incoming stats: Time interval: %s
88 Incoming stats: Time interval: %s
89 =====================================
90 """
91
92
93 def validate_non_string_sequence(value, default=None, raise_exc=None):
94     if isinstance(value, collections.Sequence) and not isinstance(value, str):
95         return value
96     if raise_exc:
97         raise raise_exc  # pylint: disable=raising-bad-type
98     return default
99
100
101 def join_non_strings(separator, *non_strings):
102     try:
103         non_strings = validate_non_string_sequence(non_strings[0], raise_exc=RuntimeError)
104     except (IndexError, RuntimeError):
105         pass
106     return str(separator).join(str(non_string) for non_string in non_strings)
107
108
109 class IXLOADHttpTest(object):
110
111     def __init__(self, test_input):
112         self.ix_load = None
113         self.stat_utils = None
114         self.remote_server = None
115         self.config_file = None
116         self.results_on_windows = None
117         self.result_dir = None
118         self.chassis = None
119         self.card = None
120         self.ports_to_reassign = None
121         self.links_param = None
122         self.test_input = jsonutils.loads(test_input)
123         self.parse_run_test()
124         self.test = None
125
126     @staticmethod
127     def format_ports_for_reassignment(ports):
128         formatted = [join_non_strings(';', p) for p in ports if len(p) == 3]
129         LOG.debug('for client ports:%s', os.linesep.join(formatted))
130         return formatted
131
132     def reassign_ports(self, test, repository, ports_to_reassign):
133         LOG.debug('ReassignPorts: %s %s', test, repository)
134
135         chassis_chain = repository.cget('chassisChain')
136         LOG.debug('chassischain: %s', chassis_chain)
137         client_ports = ports_to_reassign[0::2]
138         server_ports = ports_to_reassign[1::2]
139
140         client_ports = self.format_ports_for_reassignment(client_ports)
141         LOG.debug('Reassigning client ports: %s', client_ports)
142         server_ports = self.format_ports_for_reassignment(server_ports)
143         LOG.debug('Reassigning server ports: %s', server_ports)
144         ports_to_set = client_ports + server_ports
145
146         try:
147             LOG.debug('Reassigning ports: %s', ports_to_set)
148             test.setPorts(ports_to_set)
149         except Exception:
150             LOG.error('Error: Could not remap port assignment for: %s',
151                       ports_to_set)
152             self.ix_load.delete(repository)
153             self.ix_load.disconnect()
154             raise
155
156     @staticmethod
157     def stat_collector(*args):
158         LOG.debug(INCOMING_STAT_RECORD_TEMPLATE, args, len(args), args[0], args[1])
159
160     @staticmethod
161     def IxL_StatCollectorCommand(*args):
162         stats = args[1][3]
163         timestamp = args[1][1]
164         LOG.debug(INCOMING_STAT_INTERVAL_TEMPLATE, timestamp, stats)
165
166     @staticmethod
167     def set_results_dir(test_controller, results_on_windows):
168         """
169         If the folder doesn't exists on the Windows Client PC,
170         IxLoad will automatically create it.
171         """
172         try:
173             test_controller.setResultDir(results_on_windows)
174         except Exception:
175             LOG.error('Error creating results dir on Win: %s',
176                       results_on_windows)
177             raise
178
179     def load_config_file(self, config_file):
180         try:
181             LOG.debug(config_file)
182             repository = self.ix_load.new("ixRepository", name=config_file)
183             return repository
184         except Exception:
185             LOG.error('Error: IxLoad config file not found: %s', config_file)
186             raise
187
188     def update_network_address(self, net_traffic, address, gateway, prefix):
189         """Update ip address and gateway for net_traffic object
190
191         This function update field which configure source addresses for
192         traffic which is described by net_traffic object.
193         Do not return anything
194
195         :param net_traffic: (IxLoadObjectProxy) proxy obj to tcl net_traffic object
196         :param address: (str) Ipv4 range start address
197         :param gateway: (str) Ipv4 address of gateway
198         :param prefix: (int) subnet prefix
199         :return:
200         """
201         try:
202             ethernet = net_traffic.network.getL1Plugin()
203             ix_net_l2_ethernet_plugin = ethernet.childrenList[0]
204             ix_net_ip_v4_v6_plugin = ix_net_l2_ethernet_plugin.childrenList[0]
205             ix_net_ip_v4_v6_range = ix_net_ip_v4_v6_plugin.rangeList[0]
206
207             ix_net_ip_v4_v6_range.config(
208                 prefix=prefix,
209                 ipAddress=address,
210                 gatewayAddress=gateway)
211         except Exception:
212             raise InvalidRxfFile
213
214     def update_network_mac_address(self, net_traffic, mac):
215         """Update MACaddress for net_traffic object
216
217         This function update field which configure MACaddresses for
218         traffic which is described by net_traffic object.
219         If mac == "auto" then will be configured auto generated mac
220         Do not return anything.
221
222         :param net_traffic: (IxLoadObjectProxy) proxy obj to tcl net_traffic object
223         :param mac: (str) MAC
224         :return:
225         """
226         try:
227             ethernet = net_traffic.network.getL1Plugin()
228             ix_net_l2_ethernet_plugin = ethernet.childrenList[0]
229             ix_net_ip_v4_v6_plugin = ix_net_l2_ethernet_plugin.childrenList[0]
230             ix_net_ip_v4_v6_range = ix_net_ip_v4_v6_plugin.rangeList[0]
231
232             if str(mac).lower() == "auto":
233                 ix_net_ip_v4_v6_range.config(autoMacGeneration=True)
234             else:
235                 ix_net_ip_v4_v6_range.config(autoMacGeneration=False)
236                 mac_range = ix_net_ip_v4_v6_range.getLowerRelatedRange(
237                     "MacRange")
238                 mac_range.config(mac=mac)
239         except Exception:
240             raise InvalidRxfFile
241
242     def update_network_param(self, net_traffic, param):
243         """Update net_traffic by parameters specified in param"""
244
245         self.update_network_address(net_traffic, param["address"],
246                                     param["gateway"], param["subnet_prefix"])
247
248         self.update_network_mac_address(net_traffic, param["mac"])
249
250     def update_config(self):
251         """Update some fields by parameters from traffic profile"""
252
253         net_traffics = {}
254         # self.test.communityList is a IxLoadObjectProxy to some tcl object
255         # which contain all net_traffic objects in scenario.
256         # net_traffic item has a name in format "activity_name@item_name"
257         try:
258             for item in self.test.communityList:
259                 net_traffics[item.name.split('@')[1]] = item
260         except Exception:  # pylint: disable=broad-except
261             pass
262
263         for name, net_traffic in net_traffics.items():
264             try:
265                 param = self.links_param[name]
266             except KeyError:
267                 LOG.debug('There is no param for net_traffic %s', name)
268                 continue
269
270             self.update_network_param(net_traffic, param["ip"])
271             if "uplink" in name:
272                 self.update_http_client_param(net_traffic, param["http_client"])
273
274     def update_http_client_param(self, net_traffic, param):
275         """Update http client object in net_traffic
276
277         Update http client object in net_traffic by parameters
278         specified in param.
279         Do not return anything.
280
281         :param net_traffic: (IxLoadObjectProxy) proxy obj to tcl net_traffic object
282         :param param: (dict) http_client section from traffic profile
283         :return:
284         """
285         page = param.get("page_object")
286         if page:
287             self.update_page_size(net_traffic, page)
288         users = param.get("simulated_users")
289         if users:
290             self.update_user_count(net_traffic, users)
291
292     def update_page_size(self, net_traffic, page_object):
293         """Update page_object field in http client object in net_traffic
294
295         This function update field which configure page_object
296         which will be loaded from server
297         Do not return anything.
298
299         :param net_traffic: (IxLoadObjectProxy) proxy obj to tcl net_traffic object
300         :param page_object: (str) path to object on server e.g. "/4k.html"
301         :return:
302         """
303         try:
304             activity = net_traffic.activityList[0]
305             ix_http_command = activity.agent.actionList[0]
306             ix_http_command.config(pageObject=page_object)
307         except Exception:
308             raise InvalidRxfFile
309
310     def update_user_count(self, net_traffic, user_count):
311         """Update userObjectiveValue field in activity object in net_traffic
312
313         This function update field which configure users count
314         which will be simulated by client.
315         Do not return anything.
316
317         :param net_traffic: (IxLoadObjectProxy) proxy obj to tcl net_traffic object
318         :param user_count: (int) number of simulated users
319         :return:
320         """
321         try:
322             activity = net_traffic.activityList[0]
323             activity.config(userObjectiveValue=user_count)
324         except Exception:
325             raise InvalidRxfFile
326
327     def start_http_test(self):
328         self.ix_load = IxLoad()
329
330         LOG.debug('--- ixLoad obj: %s', self.ix_load)
331         try:
332             self.ix_load.connect(self.remote_server)
333         except Exception:
334             raise
335
336         log_tag = "IxLoad-api"
337         log_name = "reprun"
338         logger = self.ix_load.new("ixLogger", log_tag, 1)
339         log_engine = logger.getEngine()
340         log_engine.setLevels(self.ix_load.ixLogger.kLevelDebug,
341                              self.ix_load.ixLogger.kLevelInfo)
342         log_engine.setFile(log_name, 2, 256, 1)
343
344         # Initialize stat collection utilities
345         self.stat_utils = StatCollectorUtils()
346
347         test_controller = self.ix_load.new("ixTestController", outputDir=1)
348
349         repository = self.load_config_file(self.config_file)
350
351         # Get the first test on the testList
352         test_name = repository.testList[0].cget("name")
353         self.test = repository.testList.getItem(test_name)
354
355         self.set_results_dir(test_controller, self.results_on_windows)
356
357         self.test.config(statsRequired=1, enableResetPorts=1, csvInterval=2,
358                          enableForceOwnership=True)
359
360         self.update_config()
361
362         #  ---- Remap ports ----
363         try:
364             self.reassign_ports(self.test, repository, self.ports_to_reassign)
365         except Exception:  # pylint: disable=broad-except
366             LOG.exception("Exception occurred during reassign_ports")
367
368         # -----------------------------------------------------------------------
369         # Set up stat Collection
370         # -----------------------------------------------------------------------
371         test_server_handle = test_controller.getTestServerHandle()
372         self.stat_utils.Initialize(test_server_handle)
373
374         # Clear any stats that may have been registered previously
375         self.stat_utils.ClearStats()
376
377         # Define the stats we would like to collect
378         self.stat_utils.AddStat(caption="Watch_Stat_1",
379                                 statSourceType="HTTP Client",
380                                 statName="TCP Connections Established",
381                                 aggregationType="kSum",
382                                 filterList={})
383
384         self.stat_utils.AddStat(caption="Watch_Stat_2",
385                                 statSourceType="HTTP Client",
386                                 statName="TCP Connection Requests Failed",
387                                 aggregationType="kSum",
388                                 filterList={})
389
390         self.stat_utils.AddStat(caption="Watch_Stat_3",
391                                 statSourceType="HTTP Server",
392                                 statName="TCP Connections Established",
393                                 aggregationType="kSum",
394                                 filterList={})
395
396         self.stat_utils.AddStat(caption="Watch_Stat_4",
397                                 statSourceType="HTTP Server",
398                                 statName="TCP Connection Requests Failed",
399                                 aggregationType="kSum",
400                                 filterList={})
401
402         self.stat_utils.StartCollector(self.IxL_StatCollectorCommand)
403
404         test_controller.run(self.test)
405         self.ix_load.waitForTestFinish()
406
407         test_controller.releaseConfigWaitFinish()
408
409         # Stop the collector (running in the tcl event loop)
410         self.stat_utils.StopCollector()
411
412         # Cleanup
413         test_controller.generateReport(detailedReport=1, format="PDF;HTML")
414         test_controller.releaseConfigWaitFinish()
415
416         self.ix_load.delete(self.test)
417         self.ix_load.delete(test_controller)
418         self.ix_load.delete(logger)
419         self.ix_load.delete(log_engine)
420
421         LOG.debug('Retrieving CSV stats from Windows Client PC ...')
422         for stat_file in STATS_TO_GET:
423             enhanced_stat_file = stat_file.replace('-', '')
424             enhanced_stat_file = enhanced_stat_file.replace(' ', '_')
425             enhanced_stat_file = enhanced_stat_file.replace('__', '_')
426
427             LOG.debug('Getting csv stat file: %s', stat_file)
428             src_file = os.path.join(self.results_on_windows, stat_file)
429             dst_file = os.path.join(self.result_dir, '_'.join(['ixLoad', enhanced_stat_file]))
430             self.ix_load.retrieveFileCopy(src_file, dst_file)
431
432         self.ix_load.disconnect()
433
434     def parse_run_test(self):
435         self.remote_server = self.test_input["remote_server"]
436         LOG.debug("remote tcl server: %s", self.remote_server)
437
438         self.config_file = self.test_input["ixload_cfg"]
439         LOG.debug("ixload config: %s", self.remote_server)
440
441         self.results_on_windows = 'C:/Results'
442         self.result_dir = self.test_input["result_dir"]
443         self.chassis = self.test_input["ixia_chassis"]
444         LOG.debug("remote ixia chassis: %s", self.chassis)
445
446         self.card = self.test_input["IXIA"]["card"]
447         self.ports_to_reassign = [
448             [self.chassis, self.card, port] for port in
449             self.test_input["IXIA"]["ports"]
450         ]
451
452         LOG.debug("Ports to be reassigned: %s", self.ports_to_reassign)
453
454         self.links_param = self.test_input["links_param"]
455         LOG.debug("Links param to be applied: %s", self.links_param)
456
457
458 def main(args):
459     # Get the args from cmdline and parse and run the test
460     test_input = "".join(args[1:])
461     if test_input:
462         ixload_obj = IXLOADHttpTest(test_input)
463         ixload_obj.start_http_test()
464
465 if __name__ == '__main__':
466     LOG.info("Start http_ixload test")
467     main(sys.argv)