Relax checking for vxlan network type.
[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 (because dest MAC will be discovered by TRex ARP)
78         if not config.l2_loopback and (config.service_chain != ChainType.EXT or config.no_arp):
79             gen_config.set_dest_macs(0, self.chain_manager.get_dest_macs(0))
80             gen_config.set_dest_macs(1, self.chain_manager.get_dest_macs(1))
81
82         if config.vxlan:
83             # VXLAN is discovered from the networks
84             vtep_vlan = gen_config.gen_config.vtep_vlan
85             src_vteps = gen_config.gen_config.src_vteps
86             dst_vtep = gen_config.gen_config.dst_vtep
87             gen_config.set_vxlans(0, self.chain_manager.get_chain_vxlans(0))
88             gen_config.set_vxlans(1, self.chain_manager.get_chain_vxlans(1))
89             gen_config.set_vtep_vlan(0, vtep_vlan)
90             gen_config.set_vtep_vlan(1, vtep_vlan)
91             # Configuring source an remote VTEPs on TREx interfaces
92             gen_config.set_vxlan_endpoints(0, src_vteps[0], dst_vtep)
93             gen_config.set_vxlan_endpoints(1, src_vteps[1], dst_vtep)
94             self.config['vxlan_gen_config'] = gen_config
95
96         # get an instance of the stats manager
97         self.stats_manager = StatsManager(self)
98         LOG.info('ChainRunner initialized')
99
100     def __setup_traffic(self):
101         self.traffic_client.setup()
102         if not self.config.no_traffic:
103             # ARP is needed for EXT chain or VxLAN overlay unless disabled explicitly
104             if (self.config.service_chain == ChainType.EXT or self.config.vxlan) and \
105                not self.config.no_arp:
106                 self.traffic_client.ensure_arp_successful()
107             self.traffic_client.ensure_end_to_end()
108
109     def __get_result_per_frame_size(self, frame_size, bidirectional):
110         traffic_result = {
111             frame_size: {}
112         }
113         result = {}
114         if not self.config.no_traffic:
115             self.traffic_client.set_traffic(frame_size, bidirectional)
116
117             if self.config.single_run:
118                 result = self.stats_manager.run_fixed_rate()
119             else:
120                 results = self.traffic_client.get_ndr_and_pdr()
121
122                 for dr in ['pdr', 'ndr']:
123                     if dr in results:
124                         traffic_result[frame_size][dr] = results[dr]
125                         if 'warning' in results[dr]['stats'] and results[dr]['stats']['warning']:
126                             traffic_result['warning'] = results[dr]['stats']['warning']
127                 traffic_result[frame_size]['iteration_stats'] = results['iteration_stats']
128
129             if self.config.single_run:
130                 result['run_config'] = self.traffic_client.get_run_config(result)
131                 required = result['run_config']['direction-total']['orig']['rate_pps']
132                 actual = result['stats']['total_tx_rate']
133                 warning = self.traffic_client.compare_tx_rates(required, actual)
134                 if warning is not None:
135                     result['run_config']['warning'] = warning
136
137         traffic_result[frame_size].update(result)
138         return traffic_result
139
140     def __get_chain_result(self):
141         result = OrderedDict()
142         for fs in self.config.frame_sizes:
143             result.update(self.__get_result_per_frame_size(fs,
144                                                            self.config.traffic.bidirectional))
145         chain_result = {
146             'flow_count': self.config.flow_count,
147             'service_chain_count': self.config.service_chain_count,
148             'bidirectional': self.config.traffic.bidirectional,
149             'profile': self.config.traffic.profile,
150             'compute_nodes': self.stats_manager.get_compute_nodes_bios(),
151             'result': result
152         }
153         return chain_result
154
155     def run(self):
156         """Run the requested benchmark.
157
158         return: the results of the benchmark as a dict
159         """
160         results = {}
161         if self.config.no_traffic:
162             return results
163
164         LOG.info('Starting %dx%s benchmark...', self.config.service_chain_count, self.chain_name)
165         self.stats_manager.create_worker()
166         if self.config.vxlan:
167             # Configure vxlan tunnels
168             self.stats_manager.worker.config_interfaces()
169
170         self.__setup_traffic()
171
172         results[self.chain_name] = {'result': self.__get_chain_result()}
173
174         LOG.info("Service chain '%s' run completed.", self.chain_name)
175         return results
176
177     def close(self):
178         """Close this instance of chain runner and delete resources if applicable."""
179         try:
180             if not self.config.no_cleanup:
181                 LOG.info('Cleaning up...')
182                 if self.chain_manager:
183                     self.chain_manager.delete()
184             else:
185                 LOG.info('Clean up skipped.')
186             try:
187                 self.traffic_client.close()
188             except Exception:
189                 LOG.exception()
190             if self.stats_manager:
191                 self.stats_manager.close()
192         except Exception:
193             LOG.exception('Cleanup not finished')
194
195     def get_version(self):
196         """Retrieve the version of dependent components."""
197         versions = {}
198         if self.traffic_client:
199             versions['Traffic_Generator'] = self.traffic_client.get_version()
200         versions.update(self.stats_manager.get_version())
201         return versions