Merge "ixia: VLAN support without l3/l4 headers"
[vswitchperf.git] / core / vswitch_controller_pxp.py
1 # Copyright 2016 Intel Corporation.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #   http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 """VSwitch controller for multi VM scenarios with serial or parallel connection
16 """
17
18 import logging
19 import netaddr
20
21 from core.vswitch_controller import IVswitchController
22 from vswitches.utils import add_ports_to_flow
23 from conf import settings
24
25 _FLOW_TEMPLATE = {
26     'idle_timeout': '0'
27 }
28
29 _PROTO_TCP = 6
30 _PROTO_UDP = 17
31
32 class VswitchControllerPXP(IVswitchController):
33     """VSwitch controller for PXP deployment scenario.
34     """
35     def __init__(self, deployment, vswitch_class, traffic):
36         """Initializes up the prerequisites for the PXP deployment scenario.
37
38         :vswitch_class: the vSwitch class to be used.
39         :deployment: the deployment scenario to configure
40         :traffic: dictionary with detailed traffic definition
41         """
42         self._logger = logging.getLogger(__name__)
43         self._vswitch_class = vswitch_class
44         self._vswitch = vswitch_class()
45         self._pxp_topology = 'parallel' if deployment.startswith('pvpv') else 'serial'
46         if deployment == 'pvp':
47             self._pxp_vm_count = 1
48         elif deployment.startswith('pvvp') or deployment.startswith('pvpv'):
49             if len(deployment) > 4:
50                 self._pxp_vm_count = int(deployment[4:])
51             else:
52                 self._pxp_vm_count = 2
53         else:
54             raise RuntimeError('Unknown number of VMs involved in {} deployment.'.format(deployment))
55
56         self._deployment_scenario = deployment
57
58         self._traffic = traffic.copy()
59         self._bidir = True if self._traffic['bidir'] == 'True' else False
60         self._logger.debug('Creation using ' + str(self._vswitch_class))
61         self._bridge = settings.getValue('VSWITCH_BRIDGE_NAME')
62
63     def setup(self):
64         """ Sets up the switch for PXP
65         """
66         self._logger.debug('Setup using ' + str(self._vswitch_class))
67
68         try:
69             self._vswitch.start()
70
71             self._vswitch.add_switch(self._bridge)
72
73             # create physical ports
74             (_, phy1_number) = self._vswitch.add_phy_port(self._bridge)
75             (_, phy2_number) = self._vswitch.add_phy_port(self._bridge)
76
77             # create VM ports
78             # initialize vport array to requested number of VMs
79             guest_nics = settings.getValue('GUEST_NICS_NR')
80             vm_ports = [[] for _ in range(self._pxp_vm_count)]
81             # create as many VM ports as requested by configuration, but configure
82             # only even number of NICs or just one
83             for vmindex in range(self._pxp_vm_count):
84                 # just for case, enforce even number of NICs or 1
85                 nics_nr = int(guest_nics[vmindex] / 2) * 2 if guest_nics[vmindex] > 1 else 1
86                 self._logger.debug('Create %s vports for %s. VM with index %s',
87                                    nics_nr, vmindex + 1, vmindex)
88                 for _ in range(nics_nr):
89                     (_, vport) = self._vswitch.add_vport(self._bridge)
90                     vm_ports[vmindex].append(vport)
91
92             self._vswitch.del_flow(self._bridge)
93
94             # configure flows according to the TC definition
95             if self._pxp_topology == 'serial':
96                 flow = _FLOW_TEMPLATE.copy()
97                 if self._traffic['flow_type'] == 'IP':
98                     flow.update({'dl_type':'0x0800',
99                                  'nw_src':self._traffic['l3']['srcip'],
100                                  'nw_dst':self._traffic['l3']['dstip']})
101
102                 # insert flows for phy ports first
103                 # from 1st PHY to 1st vport of 1st VM
104                 self._add_flow(flow,
105                                phy1_number,
106                                vm_ports[0][0],
107                                self._bidir)
108                 # from last vport of last VM to 2nd phy
109                 self._add_flow(flow,
110                                vm_ports[self._pxp_vm_count-1][-1],
111                                phy2_number,
112                                self._bidir)
113
114                 # add serial connections among VMs and VM NICs pairs if needed
115                 # in case of multiple NICs pairs per VM, the pairs are chained
116                 # first, before flow to the next VM is created
117                 for vmindex in range(self._pxp_vm_count):
118                     # connect VMs NICs pairs in case of 4 and more NICs per VM
119                     connections = [(vm_ports[vmindex][2*(x+1)-1],
120                                     vm_ports[vmindex][2*(x+1)])
121                                    for x in range(int(len(vm_ports[vmindex])/2)-1)]
122                     for connection in connections:
123                         self._add_flow(flow,
124                                        connection[0],
125                                        connection[1],
126                                        self._bidir)
127                     # connect last NICs to the next VM if there is any
128                     if self._pxp_vm_count > vmindex + 1:
129                         self._add_flow(flow,
130                                        vm_ports[vmindex][-1],
131                                        vm_ports[vmindex+1][0],
132                                        self._bidir)
133             else:
134                 proto = _PROTO_TCP if self._traffic['l3']['proto'].lower() == 'tcp' else _PROTO_UDP
135                 dst_mac_value = netaddr.EUI(self._traffic['l2']['dstmac']).value
136                 dst_ip_value = netaddr.IPAddress(self._traffic['l3']['dstip']).value
137                 # initialize stream index; every NIC pair of every VM uses unique stream
138                 stream = 0
139                 for vmindex in range(self._pxp_vm_count):
140                     # iterate through all VMs NIC pairs...
141                     if len(vm_ports[vmindex]) > 1:
142                         port_pairs = [(vm_ports[vmindex][2*x],
143                                        vm_ports[vmindex][2*x+1]) for x in range(int(len(vm_ports[vmindex])/2))]
144                     else:
145                         # ...or connect VM with just one NIC to both phy ports
146                         port_pairs = [(vm_ports[vmindex][0], vm_ports[vmindex][0])]
147
148                     for port_pair in port_pairs:
149                         flow_p = _FLOW_TEMPLATE.copy()
150                         flow_v = _FLOW_TEMPLATE.copy()
151
152                         # update flow based on trafficgen settings
153                         if self._traffic['stream_type'] == 'L2':
154                             tmp_mac = netaddr.EUI(dst_mac_value + stream)
155                             tmp_mac.dialect = netaddr.mac_unix_expanded
156                             flow_p.update({'dl_dst':tmp_mac})
157                         elif self._traffic['stream_type'] == 'L3':
158                             tmp_ip = netaddr.IPAddress(dst_ip_value + stream)
159                             flow_p.update({'dl_type':'0x0800', 'nw_dst':tmp_ip})
160                         elif self._traffic['stream_type'] == 'L4':
161                             flow_p.update({'dl_type':'0x0800', 'nw_proto':proto, 'tp_dst':stream})
162                         else:
163                             raise RuntimeError('Unknown stream_type {}'.format(self._traffic['stream_type']))
164
165                         # insert flow to dispatch traffic from physical ports
166                         # to VMs based on stream type; all traffic from VMs is
167                         # sent to physical ports to avoid issues with MAC swapping
168                         # and upper layer mods performed inside guests
169                         self._add_flow(flow_p, phy1_number, port_pair[0])
170                         self._add_flow(flow_v, port_pair[1], phy2_number)
171                         if self._bidir:
172                             self._add_flow(flow_p, phy2_number, port_pair[1])
173                             self._add_flow(flow_v, port_pair[0], phy1_number)
174
175                         # every NIC pair needs its own unique traffic stream
176                         stream += 1
177
178         except:
179             self._vswitch.stop()
180             raise
181
182     def stop(self):
183         """Tears down the switch created in setup().
184         """
185         self._logger.debug('Stop using ' + str(self._vswitch_class))
186         self._vswitch.stop()
187
188     def _add_flow(self, flow, port1, port2, reverse_flow=False):
189         """ Helper method to insert flow into the vSwitch
190         """
191         self._vswitch.add_flow(self._bridge,
192                                add_ports_to_flow(flow,
193                                                  port1,
194                                                  port2))
195         if reverse_flow:
196             self._vswitch.add_flow(self._bridge,
197                                    add_ports_to_flow(flow,
198                                                      port2,
199                                                      port1))
200
201     def __enter__(self):
202         self.setup()
203
204     def __exit__(self, type_, value, traceback):
205         self.stop()
206
207     def get_vswitch(self):
208         """See IVswitchController for description
209         """
210         return self._vswitch
211
212     def get_ports_info(self):
213         """See IVswitchController for description
214         """
215         self._logger.debug('get_ports_info  using ' + str(self._vswitch_class))
216         return self._vswitch.get_ports(self._bridge)
217
218     def dump_vswitch_flows(self):
219         """See IVswitchController for description
220         """
221         self._vswitch.dump_flows(self._bridge)