Merge "connections: Introduction of generic API"
[vswitchperf.git] / core / vswitch_controller_pxp.py
1 # Copyright 2016-2018 Intel Corporation., Tieto
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 import netaddr
18
19 from core.vswitch_controller import IVswitchController
20 from conf import settings
21
22 class VswitchControllerPXP(IVswitchController):
23     """VSwitch controller for PXP deployment scenario.
24     """
25     def __init__(self, deployment, vswitch_class, traffic):
26         """See IVswitchController for general description
27         """
28         super().__init__(deployment, vswitch_class, traffic)
29         self._pxp_topology = 'parallel' if deployment.startswith('pvpv') else 'serial'
30         if deployment == 'pvp':
31             self._pxp_vm_count = 1
32         elif deployment.startswith('pvvp') or deployment.startswith('pvpv'):
33             if len(deployment) > 4:
34                 self._pxp_vm_count = int(deployment[4:])
35             else:
36                 self._pxp_vm_count = 2
37         else:
38             raise RuntimeError('Unknown number of VMs involved in {} deployment.'.format(deployment))
39
40         self._deployment_scenario = deployment
41
42         self._bidir = True if self._traffic['bidir'] == 'True' else False
43         self._bridge = settings.getValue('VSWITCH_BRIDGE_NAME')
44
45     def setup(self):
46         """ Sets up the switch for PXP
47         """
48         self._logger.debug('Setup using %s', str(self._vswitch_class))
49
50         try:
51             self._vswitch.start()
52
53             self._vswitch.add_switch(self._bridge)
54
55             # create physical ports
56             (phy1, _) = self._vswitch.add_phy_port(self._bridge)
57             (phy2, _) = self._vswitch.add_phy_port(self._bridge)
58
59             # create VM ports
60             # initialize vport array to requested number of VMs
61             guest_nics = settings.getValue('GUEST_NICS_NR')
62             vm_ports = [[] for _ in range(self._pxp_vm_count)]
63             # create as many VM ports as requested by configuration, but configure
64             # only even number of NICs or just one
65             for vmindex in range(self._pxp_vm_count):
66                 # just for case, enforce even number of NICs or 1
67                 nics_nr = int(guest_nics[vmindex] / 2) * 2 if guest_nics[vmindex] > 1 else 1
68                 self._logger.debug('Create %s vports for %s. VM with index %s',
69                                    nics_nr, vmindex + 1, vmindex)
70                 for _ in range(nics_nr):
71                     (vport, _) = self._vswitch.add_vport(self._bridge)
72                     vm_ports[vmindex].append(vport)
73
74             # configure connections according to the TC definition
75             if self._pxp_topology == 'serial':
76                 # NOTE: all traffic from VMs is sent to other ports directly
77                 # without applying traffic options to avoid issues with MAC swapping
78                 # and upper layer mods performed inside guests
79
80                 # insert connections for phy ports first
81                 # from 1st PHY to 1st vport of 1st VM
82                 self._vswitch.add_connection(self._bridge, phy1, vm_ports[0][0], self._traffic)
83                 self._vswitch.add_connection(self._bridge, vm_ports[0][0], phy1)
84                 # from last vport of last VM to 2nd phy
85                 self._vswitch.add_connection(self._bridge, vm_ports[self._pxp_vm_count-1][-1], phy2)
86                 self._vswitch.add_connection(self._bridge, phy2, vm_ports[self._pxp_vm_count-1][-1], self._traffic)
87
88                 # add serial connections among VMs and VM NICs pairs if needed
89                 # in case of multiple NICs pairs per VM, the pairs are chained
90                 # first, before connection to the next VM is created
91                 for vmindex in range(self._pxp_vm_count):
92                     # connect VMs NICs pairs in case of 4 and more NICs per VM
93                     connections = [(vm_ports[vmindex][2*(x+1)-1],
94                                     vm_ports[vmindex][2*(x+1)])
95                                    for x in range(int(len(vm_ports[vmindex])/2)-1)]
96                     for connection in connections:
97                         self._vswitch.add_connection(self._bridge, connection[0], connection[1])
98                         self._vswitch.add_connection(self._bridge, connection[1], connection[0])
99                     # connect last NICs to the next VM if there is any
100                     if self._pxp_vm_count > vmindex + 1:
101                         self._vswitch.add_connection(self._bridge, vm_ports[vmindex][-1], vm_ports[vmindex+1][0])
102                         self._vswitch.add_connection(self._bridge, vm_ports[vmindex+1][0], vm_ports[vmindex][-1])
103             else:
104                 mac_value = netaddr.EUI(self._traffic['l2']['dstmac']).value
105                 ip_value = netaddr.IPAddress(self._traffic['l3']['dstip']).value
106                 port_value = self._traffic['l4']['dstport']
107                 # initialize stream index; every NIC pair of every VM uses unique stream
108                 stream = 0
109                 for vmindex in range(self._pxp_vm_count):
110                     # iterate through all VMs NIC pairs...
111                     if len(vm_ports[vmindex]) > 1:
112                         port_pairs = [(vm_ports[vmindex][2*x],
113                                        vm_ports[vmindex][2*x+1]) for x in range(int(len(vm_ports[vmindex])/2))]
114                     else:
115                         # ...or connect VM with just one NIC to both phy ports
116                         port_pairs = [(vm_ports[vmindex][0], vm_ports[vmindex][0])]
117
118                     for port_pair in port_pairs:
119                         # override traffic options to ensure, that traffic is
120                         # dispatched among VMs connected in parallel
121                         options = {'multistream':1,
122                                    'stream_type':self._traffic['stream_type'],
123                                    'pre_installed_flows':'Yes'}
124                         # update connection based on trafficgen settings
125                         if self._traffic['stream_type'] == 'L2':
126                             tmp_mac = netaddr.EUI(mac_value + stream)
127                             tmp_mac.dialect = netaddr.mac_unix_expanded
128                             options.update({'l2':{'dstmac':tmp_mac}})
129                         elif self._traffic['stream_type'] == 'L3':
130                             tmp_ip = netaddr.IPAddress(ip_value + stream)
131                             options.update({'l3':{'dstip':tmp_ip}})
132                         elif self._traffic['stream_type'] == 'L4':
133                             options.update({'l3':{'proto':self._traffic['l3']['proto']}})
134                             options.update({'l4':{'dstport':(port_value + stream) % 65536}})
135                         else:
136                             raise RuntimeError('Unknown stream_type {}'.format(self._traffic['stream_type']))
137
138                         # insert connection to dispatch traffic from physical ports
139                         # to VMs based on stream type; all traffic from VMs is
140                         # sent to physical ports to avoid issues with MAC swapping
141                         # and upper layer mods performed inside guests
142                         self._vswitch.add_connection(self._bridge, phy1, port_pair[0], options)
143                         self._vswitch.add_connection(self._bridge, port_pair[1], phy2)
144                         self._vswitch.add_connection(self._bridge, phy2, port_pair[1], options)
145                         self._vswitch.add_connection(self._bridge, port_pair[0], phy1)
146
147                         # every NIC pair needs its own unique traffic stream
148                         stream += 1
149
150         except:
151             self._vswitch.stop()
152             raise
153
154     def stop(self):
155         """Tears down the switch created in setup().
156         """
157         self._logger.debug('Stop using %s', str(self._vswitch_class))
158         self._vswitch.stop()
159
160     def get_ports_info(self):
161         """See IVswitchController for description
162         """
163         self._logger.debug('get_ports_info  using %s', str(self._vswitch_class))
164         return self._vswitch.get_ports(self._bridge)
165
166     def dump_vswitch_connections(self):
167         """See IVswitchController for description
168         """
169         self._vswitch.dump_connections(self._bridge)