[NFVBENCH-89] Fix exception losing original tracebacks
[nfvbench.git] / nfvbench / chain_managers.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 import time
17
18 from log import LOG
19 from network import Network
20 from packet_analyzer import PacketAnalyzer
21 from specs import ChainType
22 from stats_collector import IntervalCollector
23
24
25 class StageManager(object):
26     """A class to stage resources in the systenm under test."""
27
28     def __init__(self, config, cred, factory):
29         self.config = config
30         self.client = None
31         # conditions due to EXT chain special cases
32         if (config.vlan_tagging and not config.vlans) or not config.no_int_config:
33             VM_CLASS = factory.get_stage_class(config.service_chain)
34             self.client = VM_CLASS(config, cred)
35             self.client.setup()
36
37     def get_vlans(self):
38         return self.client.get_vlans() if self.client else []
39
40     def get_host_ips(self):
41         return self.client.get_host_ips()
42
43     def get_networks_uuids(self):
44         return self.client.get_networks_uuids()
45
46     def disable_port_security(self):
47         self.client.disable_port_security()
48
49     def get_vms(self):
50         return self.client.vms
51
52     def get_nets(self):
53         return self.client.nets
54
55     def get_ports(self):
56         return self.client.ports
57
58     def get_compute_nodes(self):
59         return self.client.compute_nodes
60
61     def set_vm_macs(self):
62         if self.client and self.config.service_chain != ChainType.EXT:
63             self.config.generator_config.set_vm_mac_list(self.client.get_end_port_macs())
64
65     def close(self):
66         if not self.config.no_cleanup and self.client:
67             self.client.dispose()
68
69
70 class PVPStatsManager(object):
71     """A class to generate traffic and extract results for PVP chains."""
72
73     def __init__(self, config, clients, specs, factory, vlans, notifier=None):
74         self.config = config
75         self.clients = clients
76         self.specs = specs
77         self.notifier = notifier
78         self.interval_collector = None
79         self.vlans = vlans
80         self.factory = factory
81         self._setup()
82
83     def set_vlan_tag(self, device, vlan):
84         self.worker.set_vlan_tag(device, vlan)
85
86     def _setup(self):
87         WORKER_CLASS = self.factory.get_chain_worker(self.specs.openstack.encaps,
88                                                      self.config.service_chain)
89         self.worker = WORKER_CLASS(self.config, self.clients, self.specs)
90         try:
91             self.worker.set_vlans(self.vlans)
92             self._config_interfaces()
93         except Exception:
94             # since the wrorker is up and running, we need to close it
95             # in case of exception
96             self.close()
97             raise
98
99     def _get_data(self):
100         return self.worker.get_data() if self.worker else {}
101
102     def _get_network(self, traffic_port, stats, reverse=False):
103         """Get the Network object corresponding to a given TG port.
104
105         :param traffic_port: must be either 0 or 1
106         :param stats: TG stats for given traffic port
107         :param reverse: specifies if the interface list for this network
108         should go from TG to loopback point (reverse=false) or
109         from loopback point to TG (reverse=true)
110         """
111         # build the interface list in fwd direction (TG To loopback point)
112         interfaces = [self.clients['traffic'].get_interface(traffic_port, stats)]
113         if self.worker:
114             # if available,
115             # interfaces for workers must be aligned on the TG port number
116             interfaces.extend(self.worker.get_network_interfaces(traffic_port))
117         # let Network reverse the interface order if needed
118         return Network(interfaces, reverse)
119
120     def _config_interfaces(self):
121         if self.config.service_chain != ChainType.EXT:
122             self.clients['vm'].disable_port_security()
123
124         self.worker.config_interfaces()
125
126     def _generate_traffic(self):
127         if self.config.no_traffic:
128             return {}
129
130         self.interval_collector = IntervalCollector(time.time())
131         self.interval_collector.attach_notifier(self.notifier)
132         LOG.info('Starting to generate traffic...')
133         stats = {}
134         for stats in self.clients['traffic'].run_traffic():
135             self.interval_collector.add(stats)
136
137         LOG.info('...traffic generating ended.')
138         return stats
139
140     def get_stats(self):
141         return self.interval_collector.get() if self.interval_collector else []
142
143     def get_version(self):
144         return self.worker.get_version() if self.worker else {}
145
146     def run(self):
147         """Run analysis in both direction and return the analysis."""
148         if self.worker:
149             self.worker.run()
150
151         stats = self._generate_traffic()
152         result = {
153             'raw_data': self._get_data(),
154             'packet_analysis': {},
155             'stats': stats
156         }
157
158         # fetch latest stats from traffic gen
159         stats = self.clients['traffic'].get_stats()
160         LOG.info('Requesting packet analysis on the forward direction...')
161         result['packet_analysis']['direction-forward'] = \
162             self.get_analysis([self._get_network(0, stats),
163                                self._get_network(1, stats, reverse=True)])
164         LOG.info('Packet analysis on the forward direction completed')
165
166         LOG.info('Requesting packet analysis on the reverse direction...')
167         result['packet_analysis']['direction-reverse'] = \
168             self.get_analysis([self._get_network(1, stats),
169                                self._get_network(0, stats, reverse=True)])
170
171         LOG.info('Packet analysis on the reverse direction completed')
172         return result
173
174     def get_compute_nodes_bios(self):
175         return self.worker.get_compute_nodes_bios() if self.worker else {}
176
177     @staticmethod
178     def get_analysis(nets):
179         LOG.info('Starting traffic analysis...')
180
181         packet_analyzer = PacketAnalyzer()
182         # Traffic types are assumed to always alternate in every chain. Add a no stats interface in
183         # between if that is not the case.
184         tx = True
185         for network in nets:
186             for interface in network.get_interfaces():
187                 packet_analyzer.record(interface, 'tx' if tx else 'rx')
188                 tx = not tx
189
190         LOG.info('...traffic analysis completed')
191         return packet_analyzer.get_analysis()
192
193     def close(self):
194         if self.worker:
195             self.worker.close()
196
197
198 class PVVPStatsManager(PVPStatsManager):
199     """A Class to generate traffic and extract results for PVVP chains."""
200
201     def __init__(self, config, clients, specs, factory, vlans, notifier=None):
202         PVPStatsManager.__init__(self, config, clients, specs, factory, vlans, notifier)
203
204     def run(self):
205         """Run analysis in both direction and return the analysis."""
206         fwd_v2v_net, rev_v2v_net = self.worker.run()
207
208         stats = self._generate_traffic()
209         result = {
210             'raw_data': self._get_data(),
211             'packet_analysis': {},
212             'stats': stats
213         }
214         # fetch latest stats from traffic gen
215         stats = self.clients['traffic'].get_stats()
216         fwd_nets = [self._get_network(0, stats)]
217         if fwd_v2v_net:
218             fwd_nets.append(fwd_v2v_net)
219         fwd_nets.append(self._get_network(1, stats, reverse=True))
220
221         rev_nets = [self._get_network(1, stats)]
222         if rev_v2v_net:
223             rev_nets.append(rev_v2v_net)
224         rev_nets.append(self._get_network(0, stats, reverse=True))
225
226         LOG.info('Requesting packet analysis on the forward direction...')
227         result['packet_analysis']['direction-forward'] = self.get_analysis(fwd_nets)
228         LOG.info('Packet analysis on the forward direction completed')
229
230         LOG.info('Requesting packet analysis on the reverse direction...')
231         result['packet_analysis']['direction-reverse'] = self.get_analysis(rev_nets)
232
233         LOG.info('Packet analysis on the reverse direction completed')
234         return result
235
236
237 class EXTStatsManager(PVPStatsManager):
238     """A Class to generate traffic and extract results for EXT chains."""
239
240     def __init__(self, config, clients, specs, factory, vlans, notifier=None):
241         PVPStatsManager.__init__(self, config, clients, specs, factory, vlans, notifier)
242
243     def _setup(self):
244         if self.specs.openstack:
245             WORKER_CLASS = self.factory.get_chain_worker(self.specs.openstack.encaps,
246                                                          self.config.service_chain)
247             self.worker = WORKER_CLASS(self.config, self.clients, self.specs)
248             self.worker.set_vlans(self.vlans)
249
250             if not self.config.no_int_config:
251                 self._config_interfaces()
252         else:
253             self.worker = None