[NFVBENCH-81]With some Intel X710 NIC cards, NFVbench reports erroneous RX counters
[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
19 from log import LOG
20 from network import Network
21 from packet_analyzer import PacketAnalyzer
22 from specs import ChainType
23 from stats_collector import IntervalCollector
24
25
26 class StageManager(object):
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 StatsManager(object):
71
72     def __init__(self, config, clients, specs, factory, vlans, notifier=None):
73         self.config = config
74         self.clients = clients
75         self.specs = specs
76         self.notifier = notifier
77         self.interval_collector = None
78         self.vlans = vlans
79         self.factory = factory
80         self._setup()
81
82     def set_vlan_tag(self, device, vlan):
83         self.worker.set_vlan_tag(device, vlan)
84
85     def _setup(self):
86         WORKER_CLASS = self.factory.get_chain_worker(self.specs.openstack.encaps,
87                                                      self.config.service_chain)
88         self.worker = WORKER_CLASS(self.config, self.clients, self.specs)
89         try:
90             self.worker.set_vlans(self.vlans)
91             self._config_interfaces()
92         except Exception as exc:
93             # since the wrorker is up and running, we need to close it
94             # in case of exception
95             self.close()
96             raise exc
97
98     def _get_data(self):
99         return self.worker.get_data() if self.worker else {}
100
101     def _get_network(self, traffic_port, index, stats, reverse=False):
102         interfaces = [self.clients['traffic'].get_interface(traffic_port, stats)]
103         if self.worker:
104             interfaces.extend(self.worker.get_network_interfaces(index))
105         return Network(interfaces, reverse)
106
107     def _config_interfaces(self):
108         if self.config.service_chain != ChainType.EXT:
109             self.clients['vm'].disable_port_security()
110
111         self.worker.config_interfaces()
112
113     def _generate_traffic(self):
114         if self.config.no_traffic:
115             return {}
116
117         self.interval_collector = IntervalCollector(time.time())
118         self.interval_collector.attach_notifier(self.notifier)
119         LOG.info('Starting to generate traffic...')
120         stats = {}
121         for stats in self.clients['traffic'].run_traffic():
122             self.interval_collector.add(stats)
123
124         LOG.info('...traffic generating ended.')
125         return stats
126
127     def get_stats(self):
128         return self.interval_collector.get() if self.interval_collector else []
129
130     def get_version(self):
131         return self.worker.get_version() if self.worker else {}
132
133     def run(self):
134         """
135         Run analysis in both direction and return the analysis
136         """
137         if self.worker:
138             self.worker.run()
139
140         stats = self._generate_traffic()
141         result = {
142             'raw_data': self._get_data(),
143             'packet_analysis': {},
144             'stats': stats
145         }
146
147         # fetch latest stats from traffic gen
148         if self.config.no_traffic:
149             stats = None
150         else:
151             stats = self.clients['traffic'].get_stats()
152         LOG.info('Requesting packet analysis on the forward direction...')
153         result['packet_analysis']['direction-forward'] = \
154             self.get_analysis([self._get_network(0, 0, stats),
155                                self._get_network(0, 1, stats, reverse=True)])
156         LOG.info('Packet analysis on the forward direction completed')
157
158         LOG.info('Requesting packet analysis on the reverse direction...')
159         result['packet_analysis']['direction-reverse'] = \
160             self.get_analysis([self._get_network(1, 1, stats),
161                                self._get_network(1, 0, stats, reverse=True)])
162
163         LOG.info('Packet analysis on the reverse direction completed')
164         return result
165
166     def get_compute_nodes_bios(self):
167         return self.worker.get_compute_nodes_bios() if self.worker else {}
168
169     @staticmethod
170     def get_analysis(nets):
171         LOG.info('Starting traffic analysis...')
172
173         packet_analyzer = PacketAnalyzer()
174         # Traffic types are assumed to always alternate in every chain. Add a no stats interface in
175         # between if that is not the case.
176         tx = True
177         for network in nets:
178             for interface in network.get_interfaces():
179                 packet_analyzer.record(interface, 'tx' if tx else 'rx')
180                 tx = not tx
181
182         LOG.info('...traffic analysis completed')
183         return packet_analyzer.get_analysis()
184
185     def close(self):
186         if self.worker:
187             self.worker.close()
188
189
190 class PVPStatsManager(StatsManager):
191
192     def __init__(self, config, clients, specs, factory, vlans, notifier=None):
193         StatsManager.__init__(self, config, clients, specs, factory, vlans, notifier)
194
195
196 class PVVPStatsManager(StatsManager):
197
198     def __init__(self, config, clients, specs, factory, vlans, notifier=None):
199         StatsManager.__init__(self, config, clients, specs, factory, vlans, notifier)
200
201     def run(self):
202         """
203         Run analysis in both direction and return the analysis
204         """
205         fwd_v2v_net, rev_v2v_net = self.worker.run()
206
207         stats = self._generate_traffic()
208         result = {
209             'raw_data': self._get_data(),
210             'packet_analysis': {},
211             'stats': stats
212         }
213         # fetch latest stats from traffic gen
214         if self.config.no_traffic:
215             stats = None
216         else:
217             stats = self.clients['traffic'].get_stats()
218         fwd_nets = [self._get_network(0, 0, stats)]
219         if fwd_v2v_net:
220             fwd_nets.append(fwd_v2v_net)
221         fwd_nets.append(self._get_network(0, 1, stats, reverse=True))
222
223         rev_nets = [self._get_network(1, 1, stats)]
224         if rev_v2v_net:
225             rev_nets.append(rev_v2v_net)
226         rev_nets.append(self._get_network(1, 0, stats, reverse=True))
227
228         LOG.info('Requesting packet analysis on the forward direction...')
229         result['packet_analysis']['direction-forward'] = self.get_analysis(fwd_nets)
230         LOG.info('Packet analysis on the forward direction completed')
231
232         LOG.info('Requesting packet analysis on the reverse direction...')
233         result['packet_analysis']['direction-reverse'] = self.get_analysis(rev_nets)
234
235         LOG.info('Packet analysis on the reverse direction completed')
236         return result
237
238
239 class EXTStatsManager(StatsManager):
240     def __init__(self, config, clients, specs, factory, vlans, notifier=None):
241         StatsManager.__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