NFVBENCH-153 Add support for python3
[nfvbench.git] / nfvbench / chain_runner.py
1 #!/usr/bin/env python
2 # Copyright 2016 Cisco Systems, Inc.  All rights reserved.
3 #
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
7 #
8 #         http://www.apache.org/licenses/LICENSE-2.0
9 #
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
14 #    under the License.
15 #
16 """This module takes care of coordinating a benchmark run between various modules.
17
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
22 """
23
24 from collections import OrderedDict
25
26 from .chaining import ChainManager
27 from .log import LOG
28 from .specs import ChainType
29 from .stats_manager import StatsManager
30 from .traffic_client import TrafficClient
31
32
33 class ChainRunner(object):
34     """Run selected chain, collect results and analyse them."""
35
36     def __init__(self, config, cred, specs, factory, notifier=None):
37         """Create a new instance of chain runner.
38
39         Create dependent components
40         A new instance is created everytime the nfvbench config may have changed.
41
42         config: the new nfvbench config to use for this run
43         cred: openstack credentials (or None if no openstack)
44         specs: TBD
45         factory:
46         notifier:
47         """
48         self.config = config
49         self.cred = cred
50         self.specs = specs
51         self.factory = factory
52         self.notifier = notifier
53         self.chain_name = self.config.service_chain
54
55         # get an instance of traffic client
56         self.traffic_client = TrafficClient(config, notifier)
57
58         if self.config.no_traffic:
59             LOG.info('Dry run: traffic generation is disabled')
60         else:
61             # Start the traffic generator server
62             self.traffic_client.start_traffic_generator()
63
64         # get an instance of a chain manager
65         self.chain_manager = ChainManager(self)
66
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))
74
75         # the only case we do not need to set the dest MAC is in the case of
76         # l2-loopback (because the traffic gen will default to use the peer MAC)
77         # or EXT+ARP+VLAN (because dest MAC will be discovered by TRex ARP)
78         # Note that in the case of EXT+ARP+VxLAN, the dest MACs need to be loaded
79         # because ARP only operates on the dest VTEP IP not on the VM dest MAC
80         if not config.l2_loopback and \
81                 (config.service_chain != ChainType.EXT or config.no_arp or config.vxlan):
82             gen_config.set_dest_macs(0, self.chain_manager.get_dest_macs(0))
83             gen_config.set_dest_macs(1, self.chain_manager.get_dest_macs(1))
84
85         if config.vxlan:
86             # VXLAN is discovered from the networks
87             vtep_vlan = gen_config.gen_config.vtep_vlan
88             src_vteps = gen_config.gen_config.src_vteps
89             dst_vtep = gen_config.gen_config.dst_vtep
90             gen_config.set_vxlans(0, self.chain_manager.get_chain_vxlans(0))
91             gen_config.set_vxlans(1, self.chain_manager.get_chain_vxlans(1))
92             gen_config.set_vtep_vlan(0, vtep_vlan)
93             gen_config.set_vtep_vlan(1, vtep_vlan)
94             # Configuring source an remote VTEPs on TREx interfaces
95             gen_config.set_vxlan_endpoints(0, src_vteps[0], dst_vtep)
96             gen_config.set_vxlan_endpoints(1, src_vteps[1], dst_vtep)
97             self.config['vxlan_gen_config'] = gen_config
98
99         # get an instance of the stats manager
100         self.stats_manager = StatsManager(self)
101         LOG.info('ChainRunner initialized')
102
103     def __setup_traffic(self):
104         self.traffic_client.setup()
105         if not self.config.no_traffic:
106             # ARP is needed for EXT chain or VxLAN overlay unless disabled explicitly
107             if (self.config.service_chain == ChainType.EXT or
108                     self.config.vxlan or self.config.l3_router) and not self.config.no_arp:
109                 self.traffic_client.ensure_arp_successful()
110             self.traffic_client.ensure_end_to_end()
111
112     def __get_result_per_frame_size(self, frame_size, bidirectional):
113         traffic_result = {
114             frame_size: {}
115         }
116         result = {}
117         if not self.config.no_traffic:
118             self.traffic_client.set_traffic(frame_size, bidirectional)
119
120             if self.config.single_run:
121                 result = self.stats_manager.run_fixed_rate()
122             else:
123                 results = self.traffic_client.get_ndr_and_pdr()
124
125                 for dr in ['pdr', 'ndr']:
126                     if dr in results:
127                         traffic_result[frame_size][dr] = results[dr]
128                         if 'warning' in results[dr]['stats'] and results[dr]['stats']['warning']:
129                             traffic_result['warning'] = results[dr]['stats']['warning']
130                 traffic_result[frame_size]['iteration_stats'] = results['iteration_stats']
131
132             if self.config.single_run:
133                 result['run_config'] = self.traffic_client.get_run_config(result)
134                 required = result['run_config']['direction-total']['orig']['rate_pps']
135                 actual = result['stats']['total_tx_rate']
136                 warning = self.traffic_client.compare_tx_rates(required, actual)
137                 if warning is not None:
138                     result['run_config']['warning'] = warning
139
140         traffic_result[frame_size].update(result)
141         return traffic_result
142
143     def __get_chain_result(self):
144         result = OrderedDict()
145         for fs in self.config.frame_sizes:
146             result.update(self.__get_result_per_frame_size(fs,
147                                                            self.config.traffic.bidirectional))
148         chain_result = {
149             'flow_count': self.config.flow_count,
150             'service_chain_count': self.config.service_chain_count,
151             'bidirectional': self.config.traffic.bidirectional,
152             'profile': self.config.traffic.profile,
153             'compute_nodes': self.stats_manager.get_compute_nodes_bios(),
154             'result': result
155         }
156         return chain_result
157
158     def run(self):
159         """Run the requested benchmark.
160
161         return: the results of the benchmark as a dict
162         """
163         results = {}
164         if self.config.no_traffic:
165             return results
166
167         LOG.info('Starting %dx%s benchmark...', self.config.service_chain_count, self.chain_name)
168         self.stats_manager.create_worker()
169         if self.config.vxlan:
170             # Configure vxlan tunnels
171             self.stats_manager.worker.config_interfaces()
172
173         self.__setup_traffic()
174
175         results[self.chain_name] = {'result': self.__get_chain_result()}
176
177         LOG.info("Service chain '%s' run completed.", self.chain_name)
178         return results
179
180     def close(self):
181         """Close this instance of chain runner and delete resources if applicable."""
182         try:
183             if not self.config.no_cleanup:
184                 LOG.info('Cleaning up...')
185                 if self.chain_manager:
186                     self.chain_manager.delete()
187             else:
188                 LOG.info('Clean up skipped.')
189             try:
190                 self.traffic_client.close()
191             except Exception:
192                 LOG.exception()
193             if self.stats_manager:
194                 self.stats_manager.close()
195         except Exception:
196             LOG.exception('Cleanup not finished')
197
198     def get_version(self):
199         """Retrieve the version of dependent components."""
200         versions = {}
201         if self.traffic_client:
202             versions['Traffic_Generator'] = self.traffic_client.get_version()
203         versions.update(self.stats_manager.get_version())
204         return versions