NFVBENCH-102 NFVBench won't work with external chain
[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 if self.client else {}
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         if self.worker:
85             self.worker.set_vlan_tag(device, vlan)
86         else:
87             device.set_vlan_tag(vlan)
88
89     def _setup(self):
90         WORKER_CLASS = self.factory.get_chain_worker(self.specs.openstack.encaps,
91                                                      self.config.service_chain)
92         self.worker = WORKER_CLASS(self.config, self.clients, self.specs)
93         try:
94             self.worker.set_vlans(self.vlans)
95             self._config_interfaces()
96         except Exception:
97             # since the wrorker is up and running, we need to close it
98             # in case of exception
99             self.close()
100             raise
101
102     def _get_data(self):
103         return self.worker.get_data() if self.worker else {}
104
105     def _get_network(self, traffic_port, stats, reverse=False):
106         """Get the Network object corresponding to a given TG port.
107
108         :param traffic_port: must be either 0 or 1
109         :param stats: TG stats for given traffic port
110         :param reverse: specifies if the interface list for this network
111         should go from TG to loopback point (reverse=false) or
112         from loopback point to TG (reverse=true)
113         """
114         # build the interface list in fwd direction (TG To loopback point)
115         interfaces = [self.clients['traffic'].get_interface(traffic_port, stats)]
116         if self.worker:
117             # if available,
118             # interfaces for workers must be aligned on the TG port number
119             interfaces.extend(self.worker.get_network_interfaces(traffic_port))
120         # let Network reverse the interface order if needed
121         return Network(interfaces, reverse)
122
123     def _config_interfaces(self):
124         if self.config.service_chain != ChainType.EXT:
125             self.clients['vm'].disable_port_security()
126
127         self.worker.config_interfaces()
128
129     def _generate_traffic(self):
130         if self.config.no_traffic:
131             return {}
132
133         self.interval_collector = IntervalCollector(time.time())
134         self.interval_collector.attach_notifier(self.notifier)
135         LOG.info('Starting to generate traffic...')
136         stats = {}
137         for stats in self.clients['traffic'].run_traffic():
138             self.interval_collector.add(stats)
139
140         LOG.info('...traffic generating ended.')
141         return stats
142
143     def get_stats(self):
144         return self.interval_collector.get() if self.interval_collector else []
145
146     def get_version(self):
147         return self.worker.get_version() if self.worker else {}
148
149     def run(self):
150         """Run analysis in both direction and return the analysis."""
151         if self.worker:
152             self.worker.run()
153
154         stats = self._generate_traffic()
155         result = {
156             'raw_data': self._get_data(),
157             'packet_analysis': {},
158             'stats': stats
159         }
160
161         # fetch latest stats from traffic gen
162         stats = self.clients['traffic'].get_stats()
163         LOG.info('Requesting packet analysis on the forward direction...')
164         result['packet_analysis']['direction-forward'] = \
165             self.get_analysis([self._get_network(0, stats),
166                                self._get_network(1, stats, reverse=True)])
167         LOG.info('Packet analysis on the forward direction completed')
168
169         LOG.info('Requesting packet analysis on the reverse direction...')
170         result['packet_analysis']['direction-reverse'] = \
171             self.get_analysis([self._get_network(1, stats),
172                                self._get_network(0, stats, reverse=True)])
173
174         LOG.info('Packet analysis on the reverse direction completed')
175         return result
176
177     def get_compute_nodes_bios(self):
178         return self.worker.get_compute_nodes_bios() if self.worker else {}
179
180     @staticmethod
181     def get_analysis(nets):
182         LOG.info('Starting traffic analysis...')
183
184         packet_analyzer = PacketAnalyzer()
185         # Traffic types are assumed to always alternate in every chain. Add a no stats interface in
186         # between if that is not the case.
187         tx = True
188         for network in nets:
189             for interface in network.get_interfaces():
190                 packet_analyzer.record(interface, 'tx' if tx else 'rx')
191                 tx = not tx
192
193         LOG.info('...traffic analysis completed')
194         return packet_analyzer.get_analysis()
195
196     def close(self):
197         if self.worker:
198             self.worker.close()
199
200
201 class PVVPStatsManager(PVPStatsManager):
202     """A Class to generate traffic and extract results for PVVP chains."""
203
204     def __init__(self, config, clients, specs, factory, vlans, notifier=None):
205         PVPStatsManager.__init__(self, config, clients, specs, factory, vlans, notifier)
206
207     def run(self):
208         """Run analysis in both direction and return the analysis."""
209         fwd_v2v_net, rev_v2v_net = self.worker.run()
210
211         stats = self._generate_traffic()
212         result = {
213             'raw_data': self._get_data(),
214             'packet_analysis': {},
215             'stats': stats
216         }
217         # fetch latest stats from traffic gen
218         stats = self.clients['traffic'].get_stats()
219         fwd_nets = [self._get_network(0, stats)]
220         if fwd_v2v_net:
221             fwd_nets.append(fwd_v2v_net)
222         fwd_nets.append(self._get_network(1, stats, reverse=True))
223
224         rev_nets = [self._get_network(1, stats)]
225         if rev_v2v_net:
226             rev_nets.append(rev_v2v_net)
227         rev_nets.append(self._get_network(0, stats, reverse=True))
228
229         LOG.info('Requesting packet analysis on the forward direction...')
230         result['packet_analysis']['direction-forward'] = self.get_analysis(fwd_nets)
231         LOG.info('Packet analysis on the forward direction completed')
232
233         LOG.info('Requesting packet analysis on the reverse direction...')
234         result['packet_analysis']['direction-reverse'] = self.get_analysis(rev_nets)
235
236         LOG.info('Packet analysis on the reverse direction completed')
237         return result
238
239
240 class EXTStatsManager(PVPStatsManager):
241     """A Class to generate traffic and extract results for EXT chains."""
242
243     def __init__(self, config, clients, specs, factory, vlans, notifier=None):
244         PVPStatsManager.__init__(self, config, clients, specs, factory, vlans, notifier)
245
246     def _setup(self):
247         if self.specs.openstack:
248             WORKER_CLASS = self.factory.get_chain_worker(self.specs.openstack.encaps,
249                                                          self.config.service_chain)
250             self.worker = WORKER_CLASS(self.config, self.clients, self.specs)
251             self.worker.set_vlans(self.vlans)
252
253             if not self.config.no_int_config:
254                 self._config_interfaces()
255         else:
256             self.worker = None