bb35426bfa1193b7f24e1ac6d75febc681e2b63d
[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 or self.config.loop_vm_arp)\
109                     and not self.config.no_arp:
110                 self.traffic_client.ensure_arp_successful()
111             self.traffic_client.ensure_end_to_end()
112
113     def __get_result_per_frame_size(self, frame_size, bidirectional):
114         traffic_result = {
115             frame_size: {}
116         }
117         result = {}
118         if not self.config.no_traffic:
119             self.traffic_client.set_traffic(frame_size, bidirectional)
120
121             if self.config.single_run:
122                 result = self.stats_manager.run_fixed_rate()
123             else:
124                 results = self.traffic_client.get_ndr_and_pdr()
125
126                 for dr in ['pdr', 'ndr']:
127                     if dr in results:
128                         traffic_result[frame_size][dr] = results[dr]
129                         if 'warning' in results[dr]['stats'] and results[dr]['stats']['warning']:
130                             traffic_result['warning'] = results[dr]['stats']['warning']
131                 traffic_result[frame_size]['iteration_stats'] = results['iteration_stats']
132
133             if self.config.single_run:
134                 result['run_config'] = self.traffic_client.get_run_config(result)
135                 required = result['run_config']['direction-total']['orig']['rate_pps']
136                 actual = result['stats']['total_tx_rate']
137                 warning = self.traffic_client.compare_tx_rates(required, actual)
138                 if warning is not None:
139                     result['run_config']['warning'] = warning
140
141         traffic_result[frame_size].update(result)
142         return traffic_result
143
144     def __get_chain_result(self):
145         result = OrderedDict()
146         for fs in self.config.frame_sizes:
147             result.update(self.__get_result_per_frame_size(fs,
148                                                            self.config.traffic.bidirectional))
149         chain_result = {
150             'flow_count': self.config.flow_count,
151             'service_chain_count': self.config.service_chain_count,
152             'bidirectional': self.config.traffic.bidirectional,
153             'profile': self.config.traffic.profile,
154             'compute_nodes': self.stats_manager.get_compute_nodes_bios(),
155             'result': result
156         }
157         return chain_result
158
159     def run(self):
160         """Run the requested benchmark.
161
162         return: the results of the benchmark as a dict
163         """
164         results = {}
165         if self.config.no_traffic:
166             return results
167
168         LOG.info('Starting %dx%s benchmark...', self.config.service_chain_count, self.chain_name)
169         self.stats_manager.create_worker()
170         if self.config.vxlan:
171             # Configure vxlan tunnels
172             self.stats_manager.worker.config_interfaces()
173
174         self.__setup_traffic()
175
176         results[self.chain_name] = {'result': self.__get_chain_result()}
177
178         LOG.info("Service chain '%s' run completed.", self.chain_name)
179         return results
180
181     def close(self):
182         """Close this instance of chain runner and delete resources if applicable."""
183         try:
184             if not self.config.no_cleanup:
185                 LOG.info('Cleaning up...')
186                 if self.chain_manager:
187                     self.chain_manager.delete()
188             else:
189                 LOG.info('Clean up skipped.')
190             try:
191                 self.traffic_client.close()
192             except Exception:
193                 LOG.exception()
194             if self.stats_manager:
195                 self.stats_manager.close()
196         except Exception:
197             LOG.exception('Cleanup not finished')
198
199     def get_version(self):
200         """Retrieve the version of dependent components."""
201         versions = {}
202         if self.traffic_client:
203             versions['Traffic_Generator'] = self.traffic_client.get_version()
204         versions.update(self.stats_manager.get_version())
205         return versions