2 # Copyright 2016 Cisco Systems, Inc. All rights reserved.
4 # Licensed under the Apache License, Version 2.0 (the "License"); you may
5 # not use this file except in compliance with the License. You may obtain
6 # a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 # License for the specific language governing permissions and limitations
16 """This module takes care of coordinating a benchmark run between various modules.
18 The ChainRunner class is in charge of coordinating:
19 - the chain manager which takes care of staging resources
20 - traffic generator client which drives the traffic generator
21 - the stats manager which collects and aggregates stats
24 from collections import OrderedDict
26 from .chaining import ChainManager
28 from .specs import ChainType
29 from .stats_manager import StatsManager
30 from .traffic_client import TrafficClient
33 class ChainRunner(object):
34 """Run selected chain, collect results and analyse them."""
36 def __init__(self, config, cred, specs, factory, notifier=None):
37 """Create a new instance of chain runner.
39 Create dependent components
40 A new instance is created everytime the nfvbench config may have changed.
42 config: the new nfvbench config to use for this run
43 cred: openstack credentials (or None if no openstack)
51 self.factory = factory
52 self.notifier = notifier
53 self.chain_name = self.config.service_chain
55 # get an instance of traffic client
56 self.traffic_client = TrafficClient(config, notifier)
58 if self.config.no_traffic:
59 LOG.info('Dry run: traffic generation is disabled')
61 # Start the traffic generator server
62 self.traffic_client.start_traffic_generator()
64 # get an instance of a chain manager
65 self.chain_manager = ChainManager(self)
67 # at this point all resources are setup/discovered
68 # we need to program the traffic dest MAC and VLANs
69 gen_config = self.traffic_client.generator_config
70 if config.vlan_tagging:
71 # VLAN is discovered from the networks
72 gen_config.set_vlans(0, self.chain_manager.get_chain_vlans(0))
73 gen_config.set_vlans(1, self.chain_manager.get_chain_vlans(1))
75 LOG.info("Ports: untagged")
77 # the only case we do not need to set the dest MAC is in the case of
78 # l2-loopback (because the traffic gen will default to use the peer MAC)
79 # or EXT+ARP+VLAN (because dest MAC will be discovered by TRex ARP)
80 # Note that in the case of EXT+ARP+VxLAN, the dest MACs need to be loaded
81 # because ARP only operates on the dest VTEP IP not on the VM dest MAC
82 if not config.l2_loopback and \
83 (config.service_chain != ChainType.EXT or config.no_arp or config.vxlan):
84 gen_config.set_dest_macs(0, self.chain_manager.get_dest_macs(0))
85 gen_config.set_dest_macs(1, self.chain_manager.get_dest_macs(1))
88 # VXLAN is discovered from the networks
89 vtep_vlan = gen_config.gen_config.vtep_vlan
90 src_vteps = gen_config.gen_config.src_vteps
91 dst_vtep = gen_config.gen_config.dst_vtep
92 gen_config.set_vxlans(0, self.chain_manager.get_chain_vxlans(0))
93 gen_config.set_vxlans(1, self.chain_manager.get_chain_vxlans(1))
94 gen_config.set_vtep_vlan(0, vtep_vlan)
95 gen_config.set_vtep_vlan(1, vtep_vlan)
96 # Configuring source an remote VTEPs on TREx interfaces
97 gen_config.set_vxlan_endpoints(0, src_vteps[0], dst_vtep)
98 gen_config.set_vxlan_endpoints(1, src_vteps[1], dst_vtep)
99 self.config['vxlan_gen_config'] = gen_config
102 # MPLS VPN is discovered from the networks
103 src_vteps = gen_config.gen_config.src_vteps
104 vtep_gateway_ips = gen_config.gen_config.vtep_gateway_ips
105 gen_config.set_mpls_inner_labels(0, self.chain_manager.get_chain_mpls_inner_labels(0))
106 gen_config.set_mpls_inner_labels(1, self.chain_manager.get_chain_mpls_inner_labels(1))
107 outer_mpls_labels_left = self.config.internal_networks.left.mpls_transport_labels
108 outer_mpls_labels_right = self.config.internal_networks.right.mpls_transport_labels
109 if outer_mpls_labels_left or outer_mpls_labels_right:
110 gen_config.set_mpls_outer_labels(0, outer_mpls_labels_left)
111 gen_config.set_mpls_outer_labels(1, outer_mpls_labels_right)
112 # Configuring source an remote VTEPs on TREx interfaces
113 gen_config.set_mpls_peers(0, src_vteps[0], vtep_gateway_ips[0])
114 gen_config.set_mpls_peers(1, src_vteps[1], vtep_gateway_ips[1])
115 self.config['mpls_gen_config'] = gen_config
117 # get an instance of the stats manager
118 self.stats_manager = StatsManager(self)
119 LOG.info('ChainRunner initialized')
121 def __setup_traffic(self):
122 # possibly skip connectivity check
123 if self.config.no_e2e_check:
124 LOG.info('Skipping end to end connectivity check')
126 self.traffic_client.setup()
127 if not self.config.no_traffic:
128 # ARP is needed for EXT chain or VxLAN overlay or MPLS unless disabled explicitly
129 if (self.config.service_chain == ChainType.EXT or self.config.mpls or
130 self.config.vxlan or self.config.l3_router or self.config.loop_vm_arp)\
131 and not self.config.no_arp:
132 self.traffic_client.ensure_arp_successful()
133 self.traffic_client.ensure_end_to_end()
135 def __get_result_per_frame_size(self, frame_size, bidirectional):
140 if not self.config.no_traffic:
141 self.traffic_client.set_traffic(frame_size, bidirectional)
143 if self.config.single_run:
144 result = self.stats_manager.run_fixed_rate()
146 results = self.traffic_client.get_ndr_and_pdr()
148 for dr in ['pdr', 'ndr']:
150 traffic_result[frame_size][dr] = results[dr]
151 if 'warning' in results[dr]['stats'] and results[dr]['stats']['warning']:
152 traffic_result['warning'] = results[dr]['stats']['warning']
153 traffic_result[frame_size]['iteration_stats'] = results['iteration_stats']
155 if self.config.single_run:
156 result['run_config'] = self.traffic_client.get_run_config(result)
157 required = result['run_config']['direction-total']['orig']['rate_pps']
158 actual = result['stats']['total_tx_rate']
159 warning = self.traffic_client.compare_tx_rates(required, actual)
160 if warning is not None:
161 result['run_config']['warning'] = warning
163 traffic_result[frame_size].update(result)
164 return traffic_result
166 def __get_chain_result(self):
167 result = OrderedDict()
168 for fs in self.config.frame_sizes:
169 result.update(self.__get_result_per_frame_size(fs,
170 self.config.traffic.bidirectional))
172 'flow_count': self.config.flow_count,
173 'service_chain_count': self.config.service_chain_count,
174 'bidirectional': self.config.traffic.bidirectional,
175 'profile': self.config.traffic.profile,
176 'compute_nodes': self.stats_manager.get_compute_nodes_bios(),
182 """Run the requested benchmark.
184 return: the results of the benchmark as a dict
187 if self.config.no_traffic:
190 LOG.info('Starting %dx%s benchmark...', self.config.service_chain_count, self.chain_name)
191 self.stats_manager.create_worker()
192 if self.config.vxlan or self.config.mpls:
193 # Configure vxlan or mpls tunnels
194 self.stats_manager.worker.config_interfaces()
195 self.__setup_traffic()
197 results[self.chain_name] = {'result': self.__get_chain_result()}
199 LOG.info("Service chain '%s' run completed.", self.chain_name)
203 """Close this instance of chain runner and delete resources if applicable."""
205 if not self.config.no_cleanup:
206 LOG.info('Cleaning up...')
207 if self.chain_manager:
208 self.chain_manager.delete()
210 LOG.info('Clean up skipped.')
212 self.traffic_client.close()
215 if self.stats_manager:
216 self.stats_manager.close()
218 LOG.exception('Cleanup not finished')
220 def get_version(self):
221 """Retrieve the version of dependent components."""
223 if self.traffic_client:
224 versions['Traffic_Generator'] = self.traffic_client.get_version()
225 versions.update(self.stats_manager.get_version())