From 4545b967760ca795a3c67f043eaca60798a90570 Mon Sep 17 00:00:00 2001 From: Deepak S Date: Tue, 20 Jun 2017 14:18:57 -0700 Subject: IXIA traffic generator Change-Id: I09bcb3f2c4b945283070d442589d3bf00468abbc Signed-off-by: Deepak S Signed-off-by: Edward MacGillivray Signed-off-by: Ross Brattain --- yardstick/network_services/libs/__init__.py | 0 .../network_services/libs/ixia_libs/IxNet/IxNet.py | 335 +++++++++++++++++++++ .../libs/ixia_libs/IxNet/__init__.py | 0 .../network_services/libs/ixia_libs/__init__.py | 0 .../traffic_profile/http_ixload.py | 294 ++++++++++++++++++ .../traffic_profile/ixia_rfc2544.py | 155 ++++++++++ .../network_services/vnf_generic/vnf/tg_ixload.py | 176 +++++++++++ .../vnf_generic/vnf/tg_rfc2544_ixia.py | 165 ++++++++++ 8 files changed, 1125 insertions(+) create mode 100644 yardstick/network_services/libs/__init__.py create mode 100644 yardstick/network_services/libs/ixia_libs/IxNet/IxNet.py create mode 100644 yardstick/network_services/libs/ixia_libs/IxNet/__init__.py create mode 100644 yardstick/network_services/libs/ixia_libs/__init__.py create mode 100644 yardstick/network_services/traffic_profile/http_ixload.py create mode 100644 yardstick/network_services/traffic_profile/ixia_rfc2544.py create mode 100644 yardstick/network_services/vnf_generic/vnf/tg_ixload.py create mode 100644 yardstick/network_services/vnf_generic/vnf/tg_rfc2544_ixia.py (limited to 'yardstick/network_services') diff --git a/yardstick/network_services/libs/__init__.py b/yardstick/network_services/libs/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/yardstick/network_services/libs/ixia_libs/IxNet/IxNet.py b/yardstick/network_services/libs/ixia_libs/IxNet/IxNet.py new file mode 100644 index 000000000..815a2a21c --- /dev/null +++ b/yardstick/network_services/libs/ixia_libs/IxNet/IxNet.py @@ -0,0 +1,335 @@ +# Copyright (c) 2016-2017 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import print_function +import sys +import logging + +import re +from itertools import product + +from yardstick.common.utils import ErrorClass + +try: + import IxNetwork +except ImportError: + IxNetwork = ErrorClass + +log = logging.getLogger(__name__) + +IP_VERSION_4 = 4 +IP_VERSION_6 = 6 + + +class TrafficStreamHelper(object): + + TEMPLATE = '{0.traffic_item}/{0.stream}:{0.param_id}/{1}' + + def __init__(self, traffic_item, stream, param_id): + super(TrafficStreamHelper, self).__init__() + self.traffic_item = traffic_item + self.stream = stream + self.param_id = param_id + + def __getattr__(self, item): + return self.TEMPLATE.format(self, item) + + +class FramesizeHelper(object): + + def __init__(self): + super(FramesizeHelper, self).__init__() + self.weighted_pairs = [] + self.weighted_range_pairs = [] + + @property + def weighted_pairs_arg(self): + return '-weightedPairs', self.weighted_pairs + + @property + def weighted_range_pairs_arg(self): + return '-weightedRangePairs', self.weighted_range_pairs + + def make_args(self, *args): + return self.weighted_pairs_arg + self.weighted_range_pairs_arg + args + + def populate_data(self, framesize_data): + for key, value in framesize_data.items(): + if value == '0': + continue + + replaced = re.sub('[Bb]', '', key) + self.weighted_pairs.extend([ + replaced, + value, + ]) + pairs = [ + replaced, + replaced, + value, + ] + self.weighted_range_pairs.append(pairs) + + +class IxNextgen(object): + + STATS_NAME_MAP = { + "traffic_item": 'Traffic Item', + "Tx_Frames": 'Tx Frames', + "Rx_Frames": 'Rx Frames', + "Tx_Frame_Rate": 'Tx Frame Rate', + "Rx_Frame_Rate": 'Tx Frame Rate', + "Store-Forward_Avg_latency_ns": 'Store-Forward Avg Latency (ns)', + "Store-Forward_Min_latency_ns": 'Store-Forward Min Latency (ns)', + "Store-Forward_Max_latency_ns": 'Store-Forward Max Latency (ns)', + } + + PORT_STATS_NAME_MAP = { + "stat_name": 'Stat Name', + "Frames_Tx": 'Frames Tx.', + "Valid_Frames_Rx": 'Valid Frames Rx.', + "Frames_Tx_Rate": 'Frames Tx. Rate', + "Valid_Frames_Rx_Rate": 'Valid Frames Rx. Rate', + "Tx_Rate_Kbps": 'Tx. Rate (Kbps)', + "Rx_Rate_Kbps": 'Rx. Rate (Kbps)', + "Tx_Rate_Mbps": 'Tx. Rate (Mbps)', + "Rx_Rate_Mbps": 'Rx. Rate (Mbps)', + } + + LATENCY_NAME_MAP = { + "Store-Forward_Avg_latency_ns": 'Store-Forward Avg Latency (ns)', + "Store-Forward_Min_latency_ns": 'Store-Forward Min Latency (ns)', + "Store-Forward_Max_latency_ns": 'Store-Forward Max Latency (ns)', + } + + RANDOM_MASK_MAP = { + IP_VERSION_4: '0.0.0.255', + IP_VERSION_6: '0:0:0:0:0:0:0:ff', + } + + MODE_SEEDS_MAP = { + 0: ('private', ['256', '2048']), + } + + MODE_SEEDS_DEFAULT = 'public', ['2048', '256'] + + @staticmethod + def find_view_obj(view_name, views): + edited_view_name = '::ixNet::OBJ-/statistics/view:"{}"'.format(view_name) + return next((view for view in views if edited_view_name == view), '') + + @staticmethod + def get_config(tg_cfg): + external_interface = tg_cfg["vdu"][0]["external-interface"] + card_port0 = external_interface[0]["virtual-interface"]["vpci"] + card_port1 = external_interface[1]["virtual-interface"]["vpci"] + card0, port0 = card_port0.split(':')[:2] + card1, port1 = card_port1.split(':')[:2] + cfg = { + 'py_lib_path': tg_cfg["mgmt-interface"]["tg-config"]["py_lib_path"], + 'machine': tg_cfg["mgmt-interface"]["ip"], + 'port': tg_cfg["mgmt-interface"]["tg-config"]["tcl_port"], + 'chassis': tg_cfg["mgmt-interface"]["tg-config"]["ixchassis"], + 'card1': card0, + 'port1': port0, + 'card2': card1, + 'port2': port1, + 'output_dir': tg_cfg["mgmt-interface"]["tg-config"]["dut_result_dir"], + 'version': tg_cfg["mgmt-interface"]["tg-config"]["version"], + 'bidir': True, + } + return cfg + + def __init__(self, ixnet=None): + self.ixnet = ixnet + self._objRefs = dict() + self._cfg = None + self._logger = logging.getLogger(__name__) + self._params = None + self._bidir = None + + def iter_over_get_lists(self, x1, x2, y2, offset=0): + for x in self.ixnet.getList(x1, x2): + y_list = self.ixnet.getList(x, y2) + for i, y in enumerate(y_list, offset): + yield x, y, i + + def set_random_ip_multi_attribute(self, ipv4, seed, fixed_bits, random_mask, l3_count): + self.ixnet.setMultiAttribute( + ipv4, + '-seed', str(seed), + '-fixedBits', str(fixed_bits), + '-randomMask', str(random_mask), + '-valueType', 'random', + '-countValue', str(l3_count)) + + def set_random_ip_multi_attributes(self, ip, version, seeds, l3): + try: + random_mask = self.RANDOM_MASK_MAP[version] + except KeyError: + raise ValueError('Unknown version %s' % version) + + l3_count = l3['count'] + if "srcIp" in ip: + fixed_bits = l3['srcip4'] + self.set_random_ip_multi_attribute(ip, seeds[0], fixed_bits, random_mask, l3_count) + if "dstIp" in ip: + fixed_bits = l3['dstip4'] + self.set_random_ip_multi_attribute(ip, seeds[1], fixed_bits, random_mask, l3_count) + + def add_ip_header(self, params, version): + for it, ep, i in self.iter_over_get_lists('/traffic', 'trafficItem', "configElement"): + mode, seeds = self.MODE_SEEDS_MAP.get(i, self.MODE_SEEDS_DEFAULT) + l3 = params[mode]['outer_l3'] + + for ip, ip_bits, _ in self.iter_over_get_lists(ep, 'stack', 'field'): + self.set_random_ip_multi_attributes(ip_bits, version, seeds, l3) + + self.ixnet.commit() + + def _connect(self, tg_cfg): + self._cfg = self.get_config(tg_cfg) + + sys.path.append(self._cfg["py_lib_path"]) + self.ixnet = IxNetwork.IxNet() + + machine = self._cfg['machine'] + port = str(self._cfg['port']) + version = str(self._cfg['version']) + result = self.ixnet.connect(machine, '-port', port, '-version', version) + return result + + def clear_ixia_config(self): + self.ixnet.execute('newConfig') + + def load_ixia_profile(self, profile): + self.ixnet.execute('loadConfig', self.ixnet.readFrom(profile)) + + def ix_load_config(self, profile): + self.clear_ixia_config() + self.load_ixia_profile(profile) + + def ix_assign_ports(self): + vports = self.ixnet.getList(self.ixnet.getRoot(), 'vport') + ports = [ + (self._cfg['chassis'], self._cfg['card1'], self._cfg['port1']), + (self._cfg['chassis'], self._cfg['card2'], self._cfg['port2']), + ] + + vport_list = self.ixnet.getList("/", "vport") + self.ixnet.execute('assignPorts', ports, [], vport_list, True) + self.ixnet.commit() + + for vport in vports: + if self.ixnet.getAttribute(vport, '-state') != 'up': + log.error("Both thr ports are down...") + + def ix_update_frame(self, params): + streams = ["configElement"] + + for param in params.values(): + framesize_data = FramesizeHelper() + traffic_items = self.ixnet.getList('/traffic', 'trafficItem') + param_id = param['id'] + for traffic_item, stream in product(traffic_items, streams): + helper = TrafficStreamHelper(traffic_item, stream, param_id) + + self.ixnet.setMultiAttribute(helper.transmissionControl, + '-type', '{0}'.format(param['traffic_type']), + '-duration', '{0}'.format(param['duration'])) + + stream_frame_rate_path = helper.frameRate + self.ixnet.setMultiAttribute(stream_frame_rate_path, '-rate', param['iload']) + if param['outer_l2']['framesPerSecond']: + self.ixnet.setMultiAttribute(stream_frame_rate_path, + '-type', 'framesPerSecond') + + framesize_data.populate_data(param['outer_l2']['framesize']) + + make_attr_args = framesize_data.make_args('-incrementFrom', '66', + '-randomMin', '66', + '-quadGaussian', [], + '-type', 'weightedPairs', + '-presetDistribution', 'cisco', + '-incrementTo', '1518') + + self.ixnet.setMultiAttribute(helper.frameSize, *make_attr_args) + + self.ixnet.commit() + + def update_ether_multi_attribute(self, ether, mac_addr): + self.ixnet.setMultiAttribute(ether, + '-singleValue', mac_addr, + '-fieldValue', mac_addr, + '-valueType', 'singleValue') + + def update_ether_multi_attributes(self, ether, l2): + if "ethernet.header.destinationAddress" in ether: + self.update_ether_multi_attribute(ether, str(l2['dstmac'])) + + if "ethernet.header.sourceAddress" in ether: + self.update_ether_multi_attribute(ether, str(l2['srcmac'])) + + def ix_update_ether(self, params): + for ti, ep, index in self.iter_over_get_lists('/traffic', 'trafficItem', + "configElement", 1): + iter1 = (v['outer_l2'] for v in params.values() if str(v['id']) == str(index)) + try: + l2 = next(iter1, {}) + except KeyError: + continue + + for ip, ether, _ in self.iter_over_get_lists(ep, 'stack', 'field'): + self.update_ether_multi_attributes(ether, l2) + + self.ixnet.commit() + + def ix_update_udp(self, params): + pass + + def ix_update_tcp(self, params): + pass + + def ix_start_traffic(self): + tis = self.ixnet.getList('/traffic', 'trafficItem') + for ti in tis: + self.ixnet.execute('generate', [ti]) + self.ixnet.execute('apply', '/traffic') + self.ixnet.execute('start', '/traffic') + + def ix_stop_traffic(self): + tis = self.ixnet.getList('/traffic', 'trafficItem') + for _ in tis: + self.ixnet.execute('stop', '/traffic') + + def build_stats_map(self, view_obj, name_map): + return {kl: self.execute_get_column_values(view_obj, kr) for kl, kr in name_map.items()} + + def execute_get_column_values(self, view_obj, name): + return self.ixnet.execute('getColumnValues', view_obj, name) + + def ix_get_statistics(self): + views = self.ixnet.getList('/statistics', 'view') + view_obj = self.find_view_obj("Traffic Item Statistics", views) + stats = self.build_stats_map(view_obj, self.STATS_NAME_MAP) + + self.find_view_obj("Port Statistics", views) + ports_stats = self.build_stats_map(view_obj, self.PORT_STATS_NAME_MAP) + + views = self.ixnet.getList('/statistics', 'view') + view_obj = self.find_view_obj("Flow Statistics", views) + stats["latency"] = self.build_stats_map(view_obj, self.LATENCY_NAME_MAP) + + return stats, ports_stats diff --git a/yardstick/network_services/libs/ixia_libs/IxNet/__init__.py b/yardstick/network_services/libs/ixia_libs/IxNet/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/yardstick/network_services/libs/ixia_libs/__init__.py b/yardstick/network_services/libs/ixia_libs/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/yardstick/network_services/traffic_profile/http_ixload.py b/yardstick/network_services/traffic_profile/http_ixload.py new file mode 100644 index 000000000..8a4f97f04 --- /dev/null +++ b/yardstick/network_services/traffic_profile/http_ixload.py @@ -0,0 +1,294 @@ +# Copyright (c) 2016-2017 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import print_function + +import sys +import os +import logging + +# ixload uses its own py2. So importing jsonutils fails. So adding below +# workaround to support call from yardstick +try: + from oslo_serialization import jsonutils +except ImportError: + import json as jsonutils + +from yardstick.common.utils import join_non_strings +from yardstick.common.utils import ErrorClass + +try: + from IxLoad import IxLoad, StatCollectorUtils +except ImportError: + IxLoad = ErrorClass + StatCollectorUtils = ErrorClass + +LOG = logging.getLogger(__name__) +CSV_FILEPATH_NAME = 'IxL_statResults.csv' + +STATS_TO_GET = ( + 'HTTP_Client.csv', + 'HTTP_Server.csv', + 'L2-3 Stats for Client Ports.csv', + 'L2-3 Stats for Server Ports.csv', + 'IxLoad Detailed Report.html', + 'IxLoad Detailed Report.pdf' +) + +HTTP_CLIENT_STATS = [ + ["HTTP Client", "TCP Connections Established", "kSum"], + ["HTTP Client", "TCP Connection Requests Failed", "kSum"], + ["HTTP Client", "HTTP Simulated Users", "kSum"], + ["HTTP Client", "HTTP Concurrent Connections", "kSum"], + ["HTTP Client", "HTTP Connections", "kSum"], + ["HTTP Client", "HTTP Transactions", "kSum"], + ["HTTP Client", "HTTP Connection Attempts", "kSum"] +] + +HTTP_SERVER_STATS = [ + ["HTTP Server", "TCP Connections Established", "kSum"], + ["HTTP Server", "TCP Connection Requests Failed", "kSum"] +] + + +INCOMING_STAT_RECORD_TEMPLATE = """ +===================================== +INCOMING STAT RECORD >>> %s +Len = %s +%s +%s +===================================== +""" + +INCOMING_STAT_INTERVAL_TEMPLATE = """ +===================================== +Incoming stats: Time interval: %s +Incoming stats: Time interval: %s +===================================== +""" + + +class IXLOADHttpTest(object): + + def __init__(self, test_input): + self.test_input = jsonutils.loads(test_input) + self.parse_run_test() + self.ix_load = None + self.stat_utils = None + self.remote_server = None + self.config_file = None + self.results_on_windows = None + self.result_dir = None + self.chassis = None + self.card = None + self.ports_to_reassign = None + + @staticmethod + def format_ports_for_reassignment(ports): + formatted = [join_non_strings(';', p) for p in ports if len(p) == 3] + LOG.debug('for client ports:%s', os.linesep.join(formatted)) + return formatted + + def reassign_ports(self, test, repository, ports_to_reassign): + LOG.debug('ReassignPorts: %s %s', test, repository) + + chassis_chain = repository.cget('chassisChain') + LOG.debug('chassischain: %s', chassis_chain) + client_ports = ports_to_reassign[0::2] + server_ports = ports_to_reassign[1::2] + + client_ports = self.format_ports_for_reassignment(client_ports) + LOG.debug('Reassigning client ports: %s', client_ports) + server_ports = self.format_ports_for_reassignment(server_ports) + LOG.debug('Reassigning server ports: %s', server_ports) + ports_to_set = client_ports + server_ports + + try: + LOG.debug('Reassigning ports: %s', ports_to_set) + test.setPorts(ports_to_set) + except Exception: + LOG.error('Error: Could not remap port assignment for: %s', + ports_to_set) + self.ix_load.delete(repository) + self.ix_load.disconnect() + raise + + @staticmethod + def stat_collector(*args): + LOG.debug(INCOMING_STAT_RECORD_TEMPLATE, args, len(args), args[0], args[1]) + + @staticmethod + def IxL_StatCollectorCommand(*args): + stats = args[1][3] + timestamp = args[1][1] + LOG.debug(INCOMING_STAT_INTERVAL_TEMPLATE, timestamp, stats) + + @staticmethod + def set_results_dir(test_controller, results_on_windows): + """ + If the folder doesn't exists on the Windows Client PC, + IxLoad will automatically create it. + """ + try: + test_controller.setResultDir(results_on_windows) + except Exception: + LOG.error('Error creating results dir on Win: %s', + results_on_windows) + raise + + def load_config_file(self, config_file): + try: + LOG.debug(config_file) + repository = self.ix_load.new("ixRepository", name=config_file) + return repository + except Exception: + LOG.error('Error: IxLoad config file not found: %s', config_file) + raise + + def start_http_test(self): + self.ix_load = IxLoad() + + LOG.debug('--- ixLoad obj: %s', self.ix_load) + try: + self.ix_load.connect(self.remote_server) + except Exception: + raise + + log_tag = "IxLoad-api" + log_name = "reprun" + logger = self.ix_load.new("ixLogger", log_tag, 1) + log_engine = logger.getEngine() + log_engine.setLevels(self.ix_load.ixLogger.kLevelDebug, + self.ix_load.ixLogger.kLevelInfo) + log_engine.setFile(log_name, 2, 256, 1) + + # Initialize stat collection utilities + self.stat_utils = StatCollectorUtils() + + test_controller = self.ix_load.new("ixTestController", outputDir=1) + + repository = self.load_config_file(self.config_file) + + # Get the first test on the testList + test_name = repository.testList[0].cget("name") + test = repository.testList.getItem(test_name) + + self.set_results_dir(test_controller, self.results_on_windows) + + test.config(statsRequired=1, enableResetPorts=1, csvInterval=2, + enableForceOwnership=True) + + # ---- Remap ports ---- + try: + self.reassign_ports(test, repository, self.ports_to_reassign) + except Exception: + LOG.exception("Exception occurred during reassign_ports") + + # ----------------------------------------------------------------------- + # Set up stat Collection + # ----------------------------------------------------------------------- + test_server_handle = test_controller.getTestServerHandle() + self.stat_utils.Initialize(test_server_handle) + + # Clear any stats that may have been registered previously + self.stat_utils.ClearStats() + + # Define the stats we would like to collect + self.stat_utils.AddStat(caption="Watch_Stat_1", + statSourceType="HTTP Client", + statName="TCP Connections Established", + aggregationType="kSum", + filterList={}) + + self.stat_utils.AddStat(caption="Watch_Stat_2", + statSourceType="HTTP Client", + statName="TCP Connection Requests Failed", + aggregationType="kSum", + filterList={}) + + self.stat_utils.AddStat(caption="Watch_Stat_3", + statSourceType="HTTP Server", + statName="TCP Connections Established", + aggregationType="kSum", + filterList={}) + + self.stat_utils.AddStat(caption="Watch_Stat_4", + statSourceType="HTTP Server", + statName="TCP Connection Requests Failed", + aggregationType="kSum", + filterList={}) + + self.stat_utils.StartCollector(self.IxL_StatCollectorCommand) + + test_controller.run(test) + self.ix_load.waitForTestFinish() + + test_controller.releaseConfigWaitFinish() + + # Stop the collector (running in the tcl event loop) + self.stat_utils.StopCollector() + + # Cleanup + test_controller.generateReport(detailedReport=1, format="PDF;HTML") + test_controller.releaseConfigWaitFinish() + + self.ix_load.delete(test) + self.ix_load.delete(test_controller) + self.ix_load.delete(logger) + self.ix_load.delete(log_engine) + + LOG.debug('Retrieving CSV stats from Windows Client PC ...') + for stat_file in STATS_TO_GET: + enhanced_stat_file = stat_file.replace('-', '') + enhanced_stat_file = enhanced_stat_file.replace(' ', '_') + enhanced_stat_file = enhanced_stat_file.replace('__', '_') + + LOG.debug('Getting csv stat file: %s', stat_file) + src_file = os.path.join(self.results_on_windows, stat_file) + dst_file = os.path.join(self.result_dir, '_'.join(['ixLoad', enhanced_stat_file])) + self.ix_load.retrieveFileCopy(src_file, dst_file) + + self.ix_load.disconnect() + + def parse_run_test(self): + self.remote_server = self.test_input["remote_server"] + LOG.debug("remote tcl server: %s", self.remote_server) + + self.config_file = self.test_input["ixload_cfg"] + LOG.debug("ixload config: %s", self.remote_server) + + self.results_on_windows = 'C:/Results' + self.result_dir = self.test_input["result_dir"] + self.chassis = self.test_input["ixia_chassis"] + LOG.debug("remote ixia chassis: %s", self.chassis) + + self.card = self.test_input["IXIA"]["card"] + self.ports_to_reassign = [ + [self.chassis, self.card, port] for port in + self.test_input["IXIA"]["ports"] + ] + + LOG.debug("Ports to be reassigned: %s", self.ports_to_reassign) + + +def main(args): + # Get the args from cmdline and parse and run the test + test_input = "".join(args[1:]) + if test_input: + ixload_obj = IXLOADHttpTest(test_input) + ixload_obj.start_http_test() + +if __name__ == '__main__': + main(sys.argv) diff --git a/yardstick/network_services/traffic_profile/ixia_rfc2544.py b/yardstick/network_services/traffic_profile/ixia_rfc2544.py new file mode 100644 index 000000000..5ba00180b --- /dev/null +++ b/yardstick/network_services/traffic_profile/ixia_rfc2544.py @@ -0,0 +1,155 @@ +# Copyright (c) 2016-2017 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +import logging +import json + +from yardstick.network_services.traffic_profile.traffic_profile import \ + TrexProfile + +LOG = logging.getLogger(__name__) + + +class IXIARFC2544Profile(TrexProfile): + def _get_ixia_traffic_profile(self, profile_data, mac={}, + xfile=None, static_traffic={}): + result = {} + if xfile: + with open(xfile, 'r') as stream: + try: + static_traffic = json.load(stream) + except Exception as exc: + LOG.debug(exc) + + for traffickey, trafficvalue in static_traffic.items(): + traffic = static_traffic[traffickey] + # outer_l2 + index = 0 + for key, value in profile_data[traffickey].items(): + framesize = value['outer_l2']['framesize'] + traffic['outer_l2']['framesize'] = framesize + traffic['framesPerSecond'] = True + traffic['bidir'] = False + traffic['outer_l2']['srcmac'] = \ + mac["src_mac_{}".format(traffic['id'])] + traffic['outer_l2']['dstmac'] = \ + mac["dst_mac_{}".format(traffic['id'])] + # outer_l3 + if "outer_l3v6" in list(value.keys()): + traffic['outer_l3'] = value['outer_l3v6'] + srcip4 = value['outer_l3v6']['srcip6'] + traffic['outer_l3']['srcip4'] = srcip4.split("-")[0] + dstip4 = value['outer_l3v6']['dstip6'] + traffic['outer_l3']['dstip4'] = dstip4.split("-")[0] + else: + traffic['outer_l3'] = value['outer_l3v4'] + srcip4 = value['outer_l3v4']['srcip4'] + traffic['outer_l3']['srcip4'] = srcip4.split("-")[0] + dstip4 = value['outer_l3v4']['dstip4'] + traffic['outer_l3']['dstip4'] = dstip4.split("-")[0] + + traffic['outer_l3']['type'] = key + # outer_l4 + traffic['outer_l4'] = value['outer_l4'] + index = index + 1 + result.update({traffickey: traffic}) + + return result + + def _ixia_traffic_generate(self, traffic_generator, traffic, ixia_obj): + for key, value in traffic.items(): + if "public" in key or "private" in key: + traffic[key]["iload"] = str(self.rate) + ixia_obj.ix_update_frame(traffic) + ixia_obj.ix_update_ether(traffic) + # ixia_obj.ix_update_ipv4(traffic) + ixia_obj.ix_start_traffic() + self.tmp_drop = 0 + self.tmp_throughput = 0 + + def execute(self, traffic_generator, ixia_obj, mac={}, xfile=None): + if self.first_run: + self.full_profile = {} + self.pg_id = 0 + self.profile = 'private_1' + for key, value in self.params.items(): + if "private" in key or "public" in key: + self.profile_data = self.params[key] + self.get_streams(self.profile_data) + self.full_profile.update({key: self.profile_data}) + traffic = \ + self._get_ixia_traffic_profile(self.full_profile, mac, xfile) + self.max_rate = self.rate + self.min_rate = 0 + self.get_multiplier() + self._ixia_traffic_generate(traffic_generator, traffic, ixia_obj) + + def get_multiplier(self): + self.rate = round((self.max_rate + self.min_rate) / 2.0, 2) + multiplier = round(self.rate / self.pps, 2) + return str(multiplier) + + def start_ixia_latency(self, traffic_generator, ixia_obj, + mac={}, xfile=None): + traffic = self._get_ixia_traffic_profile(self.full_profile, mac) + self._ixia_traffic_generate(traffic_generator, traffic, + ixia_obj, xfile) + + def get_drop_percentage(self, traffic_generator, samples, tol_min, + tolerance, ixia_obj, mac={}, xfile=None): + status = 'Running' + drop_percent = 100 + in_packets = sum([samples[iface]['in_packets'] for iface in samples]) + out_packets = sum([samples[iface]['out_packets'] for iface in samples]) + rx_throughput = \ + sum([samples[iface]['RxThroughput'] for iface in samples]) + tx_throughput = \ + sum([samples[iface]['TxThroughput'] for iface in samples]) + packet_drop = abs(out_packets - in_packets) + try: + drop_percent = round((packet_drop / float(out_packets)) * 100, 2) + except ZeroDivisionError: + LOG.info('No traffic is flowing') + samples['TxThroughput'] = round(tx_throughput / 1.0, 2) + samples['RxThroughput'] = round(rx_throughput / 1.0, 2) + samples['CurrentDropPercentage'] = drop_percent + samples['Throughput'] = self.tmp_throughput + samples['DropPercentage'] = self.tmp_drop + if drop_percent > tolerance and self.tmp_throughput == 0: + samples['Throughput'] = round(rx_throughput / 1.0, 2) + samples['DropPercentage'] = drop_percent + if self.first_run: + max_supported_rate = out_packets / 30.0 + self.rate = max_supported_rate + self.first_run = False + if drop_percent <= tolerance: + status = 'Completed' + if drop_percent > tolerance: + self.max_rate = self.rate + elif drop_percent < tol_min: + self.min_rate = self.rate + if drop_percent >= self.tmp_drop: + self.tmp_drop = drop_percent + self.tmp_throughput = round((rx_throughput / 1.0), 2) + samples['Throughput'] = round(rx_throughput / 1.0, 2) + samples['DropPercentage'] = drop_percent + else: + samples['Throughput'] = round(rx_throughput / 1.0, 2) + samples['DropPercentage'] = drop_percent + return status, samples + self.get_multiplier() + traffic = self._get_ixia_traffic_profile(self.full_profile, mac, xfile) + self._ixia_traffic_generate(traffic_generator, traffic, ixia_obj) + return status, samples diff --git a/yardstick/network_services/vnf_generic/vnf/tg_ixload.py b/yardstick/network_services/vnf_generic/vnf/tg_ixload.py new file mode 100644 index 000000000..c15f7b954 --- /dev/null +++ b/yardstick/network_services/vnf_generic/vnf/tg_ixload.py @@ -0,0 +1,176 @@ +# Copyright (c) 2016-2017 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +import csv +import glob +import logging +import os +import shutil + +from collections import OrderedDict +from subprocess import call + +import six + +from yardstick.common.utils import makedirs +from yardstick.network_services.vnf_generic.vnf.sample_vnf import SampleVNFTrafficGen +from yardstick.network_services.vnf_generic.vnf.sample_vnf import ClientResourceHelper + +LOG = logging.getLogger(__name__) + +VNF_PATH = os.path.dirname(os.path.realpath(__file__)) + +MOUNT_CMD = """\ +mount.cifs //{0[ip]}/Results {1.RESULTS_MOUNT} \ +-o username={0[user]},password={0[password]}\ +""" + +IXLOAD_CONFIG_TEMPLATE = '''\ +{ + "ixia_chassis": "%s", + "IXIA": { + "ports": %s, + "card": %s + }, + "remote_server": "%s", + "result_dir": "%s", + "ixload_cfg": '"C:/Results/%s" +}''' + +IXLOAD_CMD = "{ixloadpy} {http_ixload} {args}" + + +class ResourceDataHelper(list): + + def get_aggregates(self): + return { + "min": min(self), + "max": max(self), + "avg": sum(self) / len(self), + } + + +class IxLoadResourceHelper(ClientResourceHelper): + + RESULTS_MOUNT = "/mnt/Results" + + KPI_LIST = OrderedDict(( + ('http_throughput', 'HTTP Total Throughput (Kbps)'), + ('simulated_users', 'HTTP Simulated Users'), + ('concurrent_connections', 'HTTP Concurrent Connections'), + ('connection_rate', 'HTTP Connection Rate'), + ('transaction_rate', 'HTTP Transaction Rate'), + )) + + def __init__(self, setup_helper): + super(IxLoadResourceHelper, self).__init__(setup_helper) + self.result = OrderedDict((key, ResourceDataHelper()) for key in self.KPI_LIST) + self.resource_file_name = '' + + def parse_csv_read(self, reader): + for row in reader: + try: + new_data = {key_left: int(row[key_right]) + for key_left, key_right in self.KPI_LIST.items()} + except (TypeError, ValueError): + continue + else: + for key, value in new_data.items(): + self.result[key].append(value) + + def setup(self): + # TODO: fixupt scenario_helper to hanlde ixia + self.resource_file_name = str(self.scenario_helper.scenario_cfg['ixia_profile']) + makedirs(self.RESULTS_MOUNT) + cmd = MOUNT_CMD.format(self.vnfd_helper.mgmt_interface, self) + LOG.debug(cmd) + + if not os.path.ismount(self.RESULTS_MOUNT): + call(cmd, shell=True) + + shutil.rmtree(self.RESULTS_MOUNT, ignore_errors=True) + makedirs(self.RESULTS_MOUNT) + shutil.copy(self.resource_file_name, self.RESULTS_MOUNT) + + def make_aggregates(self): + return {key_right: self.result[key_left].get_aggregates() + for key_left, key_right in self.KPI_LIST.items()} + + def log(self): + for key in self.KPI_LIST: + LOG.debug(self.result[key]) + + +class IxLoadTrafficGen(SampleVNFTrafficGen): + + def __init__(self, name, vnfd, setup_env_helper_type=None, resource_helper_type=None): + if resource_helper_type is None: + resource_helper_type = IxLoadResourceHelper + + super(IxLoadTrafficGen, self).__init__(name, vnfd, setup_env_helper_type, + resource_helper_type) + self._result = {} + self.done = False + self.data = None + + def run_traffic(self, traffic_profile): + ports = [] + card = None + for interface in self.vnfd_helper.interfaces: + vpci_list = interface['virtual-interface']["vpci"].split(":") + card = vpci_list[0] + ports.append(vpci_list[1]) + + for csv_file in glob.iglob(self.ssh_helper.join_bin_path('*.csv')): + os.unlink(csv_file) + + ixia_config = self.vnfd_helper.mgmt_interface["tg-config"] + ixload_config = IXLOAD_CONFIG_TEMPLATE % ( + ixia_config["ixchassis"], ports, card, + self.vnfd_helper.mgmt_interface["ip"], self.ssh_helper.bin_path, + os.path.basename(self.resource_helper.resource_file_name)) + + http_ixload_path = os.path.join(VNF_PATH, "../../traffic_profile") + cmd = IXLOAD_CMD.format( + ixloadpy=os.path.join(ixia_config["py_bin_path"], "ixloadpython"), + http_ixload=os.path.join(http_ixload_path, "http_ixload.py"), + args="'%s'" % ixload_config) + + LOG.debug(cmd) + call(cmd, shell=True) + + with open(self.ssh_helper.join_bin_path("ixLoad_HTTP_Client.csv")) as csv_file: + lines = csv_file.readlines()[10:] + + with open(self.ssh_helper.join_bin_path("http_result.csv"), 'wb+') as result_file: + result_file.writelines(six.text_type(lines[:-1])) + result_file.flush() + result_file.seek(0) + reader = csv.DictReader(result_file) + self.resource_helper.parse_csv_read(reader) + + self.resource_helper.log() + self.data = self.resource_helper.make_aggregates() + + def listen_traffic(self, traffic_profile): + pass + + def instantiate(self, scenario_cfg, context_cfg): + super(IxLoadTrafficGen, self).instantiate(scenario_cfg, context_cfg) + self.done = False + + def terminate(self): + call(["pkill", "-9", "http_ixload.py"]) + super(IxLoadTrafficGen, self).terminate() diff --git a/yardstick/network_services/vnf_generic/vnf/tg_rfc2544_ixia.py b/yardstick/network_services/vnf_generic/vnf/tg_rfc2544_ixia.py new file mode 100644 index 000000000..07bbdae95 --- /dev/null +++ b/yardstick/network_services/vnf_generic/vnf/tg_rfc2544_ixia.py @@ -0,0 +1,165 @@ +# Copyright (c) 2016-2017 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +import time +import os +import logging +import sys + +from yardstick.common.utils import ErrorClass +from yardstick.network_services.vnf_generic.vnf.sample_vnf import SampleVNFTrafficGen +from yardstick.network_services.vnf_generic.vnf.sample_vnf import ClientResourceHelper +from yardstick.network_services.vnf_generic.vnf.sample_vnf import Rfc2544ResourceHelper + +try: + from IxNet import IxNextgen +except ImportError: + IxNextgen = ErrorClass + +LOG = logging.getLogger(__name__) + +WAIT_AFTER_CFG_LOAD = 10 +WAIT_FOR_TRAFFIC = 30 +IXIA_LIB = os.path.dirname(os.path.realpath(__file__)) +IXNET_LIB = os.path.join(IXIA_LIB, "../../libs/ixia_libs/IxNet") +sys.path.append(IXNET_LIB) + + +class IxiaRfc2544Helper(Rfc2544ResourceHelper): + + pass + + +class IxiaResourceHelper(ClientResourceHelper): + + def __init__(self, setup_helper, rfc_helper_type=None): + super(IxiaResourceHelper, self).__init__(setup_helper) + self.scenario_helper = setup_helper.scenario_helper + + self.client = IxNextgen() + + if rfc_helper_type is None: + rfc_helper_type = IxiaRfc2544Helper + + self.rfc_helper = rfc_helper_type(self.scenario_helper) + self.tg_port_pairs = [] + self.priv_ports = None + self.pub_ports = None + + def _connect(self, client=None): + self.client.connect(self.vnfd_helper) + + def _build_ports(self): + # self.generate_port_pairs(self.topology) + self.priv_ports = [int(x[0][-1]) for x in self.tg_port_pairs] + self.pub_ports = [int(x[1][-1]) for x in self.tg_port_pairs] + self.my_ports = list(set(self.priv_ports).union(set(self.pub_ports))) + + def get_stats(self, *args, **kwargs): + return self.client.ix_get_statistics()[1] + + def stop_collect(self): + self._terminated.value = 0 + if self.client: + self.client.ix_stop_traffic() + + def generate_samples(self, key=None, default=None): + last_result = self.get_stats() + + samples = {} + for vpci_idx, interface in enumerate(self.vnfd_helper.interfaces): + name = "xe{0}".format(vpci_idx) + samples[name] = { + "rx_throughput_kps": float(last_result["Rx_Rate_Kbps"][vpci_idx]), + "tx_throughput_kps": float(last_result["Tx_Rate_Kbps"][vpci_idx]), + "rx_throughput_mbps": float(last_result["Rx_Rate_Mbps"][vpci_idx]), + "tx_throughput_mbps": float(last_result["Tx_Rate_Mbps"][vpci_idx]), + "in_packets": int(last_result["Valid_Frames_Rx"][vpci_idx]), + "out_packets": int(last_result["Frames_Tx"][vpci_idx]), + "RxThroughput": int(last_result["Valid_Frames_Rx"][vpci_idx]) / 30, + "TxThroughput": int(last_result["Frames_Tx"][vpci_idx]) / 30, + } + + return samples + + def run_traffic(self, traffic_profile): + min_tol = self.rfc_helper.tolerance_low + max_tol = self.rfc_helper.tolerance_high + + self._build_ports() + self._connect() + + # we don't know client_file_name until runtime as instantiate + client_file_name = self.scenario_helper.scenario_cfg['ixia_profile'] + self.client.ix_load_config(client_file_name) + time.sleep(WAIT_AFTER_CFG_LOAD) + + self.client.ix_assign_ports() + + mac = {} + for index, interface in enumerate(self.vnfd_helper.interfaces): + virt_intf = interface["virtual-interface"] + mac.update({ + "src_mac_{}".format(index): virt_intf["local_mac"], + "dst_mac_{}".format(index): virt_intf["dst_mac"], + }) + + samples = {} + ixia_file = os.path.join(os.getcwd(), "ixia_traffic.cfg") + # Generate ixia traffic config... + while not self._terminated.value: + traffic_profile.execute(self, self.client, mac, ixia_file) + self.client_started.value = 1 + time.sleep(WAIT_FOR_TRAFFIC) + self.client.ix_stop_traffic() + samples = self.generate_samples() + self._queue.put(samples) + status, samples = traffic_profile.get_drop_percentage(self, samples, min_tol, + max_tol, self.client, mac, + ixia_file) + + current = samples['CurrentDropPercentage'] + if min_tol <= current <= max_tol or status == 'Completed': + self._terminated.value = 1 + + self.client.ix_stop_traffic() + self._queue.put(samples) + + +class IxiaTrafficGen(SampleVNFTrafficGen): + + def __init__(self, name, vnfd, setup_env_helper_type=None, resource_helper_type=None): + if resource_helper_type is None: + resource_helper_type = IxiaResourceHelper + + super(IxiaTrafficGen, self).__init__(name, vnfd, setup_env_helper_type, + resource_helper_type) + self._ixia_traffic_gen = None + self.ixia_file_name = '' + self.tg_port_pairs = [] + self.vnf_port_pairs = [] + + def _check_status(self): + pass + + def scale(self, flavor=""): + pass + + def listen_traffic(self, traffic_profile): + pass + + def terminate(self): + self.resource_helper.stop_collect() + super(IxiaTrafficGen, self).terminate() -- cgit 1.2.3-korg