Added traffic update capability to Ixload TG
[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.links_param = None
110         self.test_input = jsonutils.loads(test_input)
111         self.parse_run_test()
112         self.test = None
113
114     @staticmethod
115     def format_ports_for_reassignment(ports):
116         formatted = [join_non_strings(';', p) for p in ports if len(p) == 3]
117         LOG.debug('for client ports:%s', os.linesep.join(formatted))
118         return formatted
119
120     def reassign_ports(self, test, repository, ports_to_reassign):
121         LOG.debug('ReassignPorts: %s %s', test, repository)
122
123         chassis_chain = repository.cget('chassisChain')
124         LOG.debug('chassischain: %s', chassis_chain)
125         client_ports = ports_to_reassign[0::2]
126         server_ports = ports_to_reassign[1::2]
127
128         client_ports = self.format_ports_for_reassignment(client_ports)
129         LOG.debug('Reassigning client ports: %s', client_ports)
130         server_ports = self.format_ports_for_reassignment(server_ports)
131         LOG.debug('Reassigning server ports: %s', server_ports)
132         ports_to_set = client_ports + server_ports
133
134         try:
135             LOG.debug('Reassigning ports: %s', ports_to_set)
136             test.setPorts(ports_to_set)
137         except Exception:
138             LOG.error('Error: Could not remap port assignment for: %s',
139                       ports_to_set)
140             self.ix_load.delete(repository)
141             self.ix_load.disconnect()
142             raise
143
144     @staticmethod
145     def stat_collector(*args):
146         LOG.debug(INCOMING_STAT_RECORD_TEMPLATE, args, len(args), args[0], args[1])
147
148     @staticmethod
149     def IxL_StatCollectorCommand(*args):
150         stats = args[1][3]
151         timestamp = args[1][1]
152         LOG.debug(INCOMING_STAT_INTERVAL_TEMPLATE, timestamp, stats)
153
154     @staticmethod
155     def set_results_dir(test_controller, results_on_windows):
156         """
157         If the folder doesn't exists on the Windows Client PC,
158         IxLoad will automatically create it.
159         """
160         try:
161             test_controller.setResultDir(results_on_windows)
162         except Exception:
163             LOG.error('Error creating results dir on Win: %s',
164                       results_on_windows)
165             raise
166
167     def load_config_file(self, config_file):
168         try:
169             LOG.debug(config_file)
170             repository = self.ix_load.new("ixRepository", name=config_file)
171             return repository
172         except Exception:
173             LOG.error('Error: IxLoad config file not found: %s', config_file)
174             raise
175
176     def update_network_address(self, net_traffic, address, gateway, prefix):
177         """Update ip address and gateway for net_traffic object
178
179         This function update field which configure source addresses for
180         traffic which is described by net_traffic object.
181         Do not return anything
182
183         :param net_traffic: (IxLoadObjectProxy) proxy obj to tcl net_traffic object
184         :param address: (str) Ipv4 range start address
185         :param gateway: (str) Ipv4 address of gateway
186         :param prefix: (int) subnet prefix
187         :return:
188         """
189         try:
190             ethernet = net_traffic.network.getL1Plugin()
191             ix_net_l2_ethernet_plugin = ethernet.childrenList[0]
192             ix_net_ip_v4_v6_plugin = ix_net_l2_ethernet_plugin.childrenList[0]
193             ix_net_ip_v4_v6_range = ix_net_ip_v4_v6_plugin.rangeList[0]
194
195             ix_net_ip_v4_v6_range.config(
196                 prefix=prefix,
197                 ipAddress=address,
198                 gatewayAddress=gateway)
199         except Exception:
200             raise exceptions.InvalidRxfFile
201
202     def update_network_mac_address(self, net_traffic, mac):
203         """Update MACaddress for net_traffic object
204
205         This function update field which configure MACaddresses for
206         traffic which is described by net_traffic object.
207         If mac == "auto" then will be configured auto generated mac
208         Do not return anything.
209
210         :param net_traffic: (IxLoadObjectProxy) proxy obj to tcl net_traffic object
211         :param mac: (str) MAC
212         :return:
213         """
214         try:
215             ethernet = net_traffic.network.getL1Plugin()
216             ix_net_l2_ethernet_plugin = ethernet.childrenList[0]
217             ix_net_ip_v4_v6_plugin = ix_net_l2_ethernet_plugin.childrenList[0]
218             ix_net_ip_v4_v6_range = ix_net_ip_v4_v6_plugin.rangeList[0]
219
220             if str(mac).lower() == "auto":
221                 ix_net_ip_v4_v6_range.config(autoMacGeneration=True)
222             else:
223                 ix_net_ip_v4_v6_range.config(autoMacGeneration=False)
224                 mac_range = ix_net_ip_v4_v6_range.getLowerRelatedRange(
225                     "MacRange")
226                 mac_range.config(mac=mac)
227         except Exception:
228             raise exceptions.InvalidRxfFile
229
230     def update_network_param(self, net_traffic, param):
231         """Update net_traffic by parameters specified in param"""
232
233         self.update_network_address(net_traffic, param["address"],
234                                     param["gateway"], param["subnet_prefix"])
235
236         self.update_network_mac_address(net_traffic, param["mac"])
237
238     def update_config(self):
239         """Update some fields by parameters from traffic profile"""
240
241         net_traffics = {}
242         # self.test.communityList is a IxLoadObjectProxy to some tcl object
243         # which contain all net_traffic objects in scenario.
244         # net_traffic item has a name in format "activity_name@item_name"
245         try:
246             for item in self.test.communityList:
247                 net_traffics[item.name.split('@')[1]] = item
248         except Exception:  # pylint: disable=broad-except
249             pass
250
251         for name, net_traffic in net_traffics.items():
252             try:
253                 param = self.links_param[name]
254             except KeyError:
255                 LOG.debug('There is no param for net_traffic %s', name)
256                 continue
257
258             self.update_network_param(net_traffic, param["ip"])
259
260     def start_http_test(self):
261         self.ix_load = IxLoad()
262
263         LOG.debug('--- ixLoad obj: %s', self.ix_load)
264         try:
265             self.ix_load.connect(self.remote_server)
266         except Exception:
267             raise
268
269         log_tag = "IxLoad-api"
270         log_name = "reprun"
271         logger = self.ix_load.new("ixLogger", log_tag, 1)
272         log_engine = logger.getEngine()
273         log_engine.setLevels(self.ix_load.ixLogger.kLevelDebug,
274                              self.ix_load.ixLogger.kLevelInfo)
275         log_engine.setFile(log_name, 2, 256, 1)
276
277         # Initialize stat collection utilities
278         self.stat_utils = StatCollectorUtils()
279
280         test_controller = self.ix_load.new("ixTestController", outputDir=1)
281
282         repository = self.load_config_file(self.config_file)
283
284         # Get the first test on the testList
285         test_name = repository.testList[0].cget("name")
286         self.test = repository.testList.getItem(test_name)
287
288         self.set_results_dir(test_controller, self.results_on_windows)
289
290         self.test.config(statsRequired=1, enableResetPorts=1, csvInterval=2,
291                          enableForceOwnership=True)
292
293         self.update_config()
294
295         #  ---- Remap ports ----
296         try:
297             self.reassign_ports(self.test, repository, self.ports_to_reassign)
298         except Exception:  # pylint: disable=broad-except
299             LOG.exception("Exception occurred during reassign_ports")
300
301         # -----------------------------------------------------------------------
302         # Set up stat Collection
303         # -----------------------------------------------------------------------
304         test_server_handle = test_controller.getTestServerHandle()
305         self.stat_utils.Initialize(test_server_handle)
306
307         # Clear any stats that may have been registered previously
308         self.stat_utils.ClearStats()
309
310         # Define the stats we would like to collect
311         self.stat_utils.AddStat(caption="Watch_Stat_1",
312                                 statSourceType="HTTP Client",
313                                 statName="TCP Connections Established",
314                                 aggregationType="kSum",
315                                 filterList={})
316
317         self.stat_utils.AddStat(caption="Watch_Stat_2",
318                                 statSourceType="HTTP Client",
319                                 statName="TCP Connection Requests Failed",
320                                 aggregationType="kSum",
321                                 filterList={})
322
323         self.stat_utils.AddStat(caption="Watch_Stat_3",
324                                 statSourceType="HTTP Server",
325                                 statName="TCP Connections Established",
326                                 aggregationType="kSum",
327                                 filterList={})
328
329         self.stat_utils.AddStat(caption="Watch_Stat_4",
330                                 statSourceType="HTTP Server",
331                                 statName="TCP Connection Requests Failed",
332                                 aggregationType="kSum",
333                                 filterList={})
334
335         self.stat_utils.StartCollector(self.IxL_StatCollectorCommand)
336
337         test_controller.run(self.test)
338         self.ix_load.waitForTestFinish()
339
340         test_controller.releaseConfigWaitFinish()
341
342         # Stop the collector (running in the tcl event loop)
343         self.stat_utils.StopCollector()
344
345         # Cleanup
346         test_controller.generateReport(detailedReport=1, format="PDF;HTML")
347         test_controller.releaseConfigWaitFinish()
348
349         self.ix_load.delete(self.test)
350         self.ix_load.delete(test_controller)
351         self.ix_load.delete(logger)
352         self.ix_load.delete(log_engine)
353
354         LOG.debug('Retrieving CSV stats from Windows Client PC ...')
355         for stat_file in STATS_TO_GET:
356             enhanced_stat_file = stat_file.replace('-', '')
357             enhanced_stat_file = enhanced_stat_file.replace(' ', '_')
358             enhanced_stat_file = enhanced_stat_file.replace('__', '_')
359
360             LOG.debug('Getting csv stat file: %s', stat_file)
361             src_file = os.path.join(self.results_on_windows, stat_file)
362             dst_file = os.path.join(self.result_dir, '_'.join(['ixLoad', enhanced_stat_file]))
363             self.ix_load.retrieveFileCopy(src_file, dst_file)
364
365         self.ix_load.disconnect()
366
367     def parse_run_test(self):
368         self.remote_server = self.test_input["remote_server"]
369         LOG.debug("remote tcl server: %s", self.remote_server)
370
371         self.config_file = self.test_input["ixload_cfg"]
372         LOG.debug("ixload config: %s", self.remote_server)
373
374         self.results_on_windows = 'C:/Results'
375         self.result_dir = self.test_input["result_dir"]
376         self.chassis = self.test_input["ixia_chassis"]
377         LOG.debug("remote ixia chassis: %s", self.chassis)
378
379         self.card = self.test_input["IXIA"]["card"]
380         self.ports_to_reassign = [
381             [self.chassis, self.card, port] for port in
382             self.test_input["IXIA"]["ports"]
383         ]
384
385         LOG.debug("Ports to be reassigned: %s", self.ports_to_reassign)
386
387         self.links_param = self.test_input["links_param"]
388         LOG.debug("Links param to be applied: %s", self.links_param)
389
390
391 def main(args):
392     # Get the args from cmdline and parse and run the test
393     test_input = "".join(args[1:])
394     if test_input:
395         ixload_obj = IXLOADHttpTest(test_input)
396         ixload_obj.start_http_test()
397
398 if __name__ == '__main__':
399     LOG.info("Start http_ixload test")
400     main(sys.argv)