Merge "Adding new test case for prox Standalone L3FWD."
[yardstick.git] / yardstick / benchmark / contexts / standalone / sriov.py
1 # Copyright (c) 2016-2017 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 from __future__ import absolute_import
16 import os
17 import logging
18 import collections
19
20 from yardstick import ssh
21 from yardstick.benchmark import contexts
22 from yardstick.benchmark.contexts import base
23 from yardstick.benchmark.contexts.standalone import model
24 from yardstick.common import utils
25 from yardstick.network_services.utils import get_nsb_option
26 from yardstick.network_services.utils import PciAddress
27
28 LOG = logging.getLogger(__name__)
29
30
31 class SriovContext(base.Context):
32     """ This class handles SRIOV standalone nodes - VM running on Non-Managed NFVi
33     Configuration: sr-iov
34     """
35
36     __context_type__ = contexts.CONTEXT_STANDALONESRIOV
37
38     def __init__(self):
39         self.file_path = None
40         self.sriov = []
41         self.first_run = True
42         self.dpdk_devbind = os.path.join(get_nsb_option('bin_path'),
43                                          'dpdk-devbind.py')
44         self.vm_names = []
45         self.nfvi_host = []
46         self.nodes = []
47         self.networks = {}
48         self.attrs = {}
49         self.vm_flavor = None
50         self.servers = None
51         self.helper = model.StandaloneContextHelper()
52         self.vnf_node = model.Server()
53         self.drivers = []
54         super(SriovContext, self).__init__()
55
56     def init(self, attrs):
57         """initializes itself from the supplied arguments"""
58         super(SriovContext, self).init(attrs)
59
60         self.file_path = attrs.get("file", "pod.yaml")
61
62         self.nodes, self.nfvi_host, self.host_mgmt = \
63             self.helper.parse_pod_file(self.file_path, 'Sriov')
64
65         self.attrs = attrs
66         self.vm_flavor = attrs.get('flavor', {})
67         self.servers = attrs.get('servers', {})
68         self.vm_deploy = attrs.get("vm_deploy", True)
69         # add optional static network definition
70         self.networks = attrs.get("networks", {})
71
72         LOG.debug("Nodes: %r", self.nodes)
73         LOG.debug("NFVi Node: %r", self.nfvi_host)
74         LOG.debug("Networks: %r", self.networks)
75
76     def deploy(self):
77         """don't need to deploy"""
78
79         # Todo: NFVi deploy (sriov, vswitch, ovs etc) based on the config.
80         if not self.vm_deploy:
81             return
82
83         self.connection = ssh.SSH.from_node(self.host_mgmt)
84
85         #    Todo: NFVi deploy (sriov, vswitch, ovs etc) based on the config.
86         model.StandaloneContextHelper.install_req_libs(self.connection)
87         self.networks = model.StandaloneContextHelper.get_nic_details(
88             self.connection, self.networks, self.dpdk_devbind)
89         self.nodes = self.setup_sriov_context()
90
91         LOG.debug("Waiting for VM to come up...")
92         self.nodes = model.StandaloneContextHelper.wait_for_vnfs_to_start(
93             self.connection, self.servers, self.nodes)
94
95     def undeploy(self):
96         """don't need to undeploy"""
97
98         if not self.vm_deploy:
99             return
100
101         # Todo: NFVi undeploy (sriov, vswitch, ovs etc) based on the config.
102         for vm in self.vm_names:
103             model.Libvirt.check_if_vm_exists_and_delete(vm, self.connection)
104
105         # Bind nics back to kernel
106         for ports in self.networks.values():
107             # enable VFs for given...
108             build_vfs = "echo 0 > /sys/bus/pci/devices/{0}/sriov_numvfs"
109             self.connection.execute(build_vfs.format(ports.get('phy_port')))
110
111     def _get_physical_nodes(self):
112         return self.nfvi_host
113
114     def _get_physical_node_for_server(self, server_name):
115
116         # self.nfvi_host always contain only one host.
117         node_name, ctx_name = self.split_host_name(server_name)
118         if ctx_name is None or self.name != ctx_name:
119             return None
120
121         matching_nodes = [s for s in self.servers if s == node_name]
122         if len(matching_nodes) == 0:
123             return None
124
125         return "{}.{}".format(self.nfvi_host[0]["name"], self._name)
126
127     def _get_server(self, attr_name):
128         """lookup server info by name from context
129
130         Keyword arguments:
131         attr_name -- A name for a server listed in nodes config file
132         """
133         node_name, name = self.split_host_name(attr_name)
134         if name is None or self.name != name:
135             return None
136
137         matching_nodes = (n for n in self.nodes if n["name"] == node_name)
138         try:
139             # A clone is created in order to avoid affecting the
140             # original one.
141             node = dict(next(matching_nodes))
142         except StopIteration:
143             return None
144
145         try:
146             duplicate = next(matching_nodes)
147         except StopIteration:
148             pass
149         else:
150             raise ValueError("Duplicate nodes!!! Nodes: %s %s"
151                              % (node, duplicate))
152
153         node["name"] = attr_name
154         return node
155
156     def _get_network(self, attr_name):
157         if not isinstance(attr_name, collections.Mapping):
158             network = self.networks.get(attr_name)
159
160         else:
161             # Don't generalize too much  Just support vld_id
162             vld_id = attr_name.get('vld_id', {})
163             # for standalone context networks are dicts
164             iter1 = (n for n in self.networks.values() if n.get('vld_id') == vld_id)
165             network = next(iter1, None)
166
167         if network is None:
168             return None
169
170         result = {
171             # name is required
172             "name": network["name"],
173             "vld_id": network.get("vld_id"),
174             "segmentation_id": network.get("segmentation_id"),
175             "network_type": network.get("network_type"),
176             "physical_network": network.get("physical_network"),
177         }
178         return result
179
180     def configure_nics_for_sriov(self):
181         vf_cmd = "ip link set {0} vf 0 mac {1}"
182         for ports in self.networks.values():
183             host_driver = ports.get('driver')
184             if host_driver not in self.drivers:
185                 self.connection.execute("rmmod %svf" % host_driver)
186                 self.drivers.append(host_driver)
187
188             # enable VFs for given...
189             build_vfs = "echo 1 > /sys/bus/pci/devices/{0}/sriov_numvfs"
190             self.connection.execute(build_vfs.format(ports.get('phy_port')))
191
192             # configure VFs...
193             mac = model.StandaloneContextHelper.get_mac_address()
194             interface = ports.get('interface')
195             if interface is not None:
196                 self.connection.execute(vf_cmd.format(interface, mac))
197
198             vf_pci = self._get_vf_data(ports.get('phy_port'), mac, interface)
199             ports.update({
200                 'vf_pci': vf_pci,
201                 'mac': mac
202             })
203
204         LOG.info('Ports %s', self.networks)
205
206     def _enable_interfaces(self, index, idx, vfs, cfg):
207         vf_spoofchk = "ip link set {0} vf 0 spoofchk off"
208
209         vf = self.networks[vfs[0]]
210         vpci = PciAddress(vf['vpci'].strip())
211         # Generate the vpci for the interfaces
212         slot = index + idx + 10
213         vf['vpci'] = \
214             "{}:{}:{:02x}.{}".format(vpci.domain, vpci.bus, slot, vpci.function)
215         self.connection.execute("ifconfig %s up" % vf['interface'])
216         self.connection.execute(vf_spoofchk.format(vf['interface']))
217         return model.Libvirt.add_sriov_interfaces(
218             vf['vpci'], vf['vf_pci']['vf_pci'], vf['mac'], str(cfg))
219
220     def setup_sriov_context(self):
221         nodes = []
222
223         #   1 : modprobe host_driver with num_vfs
224         self.configure_nics_for_sriov()
225
226         hp_total_mb = int(self.vm_flavor.get('ram', '4096')) * len(self.servers)
227         utils.setup_hugepages(self.connection, hp_total_mb * 1024)
228
229         for index, (key, vnf) in enumerate(collections.OrderedDict(
230                 self.servers).items()):
231             cfg = '/tmp/vm_sriov_%s.xml' % str(index)
232             vm_name = "vm-%s" % str(index)
233             cdrom_img = "/var/lib/libvirt/images/cdrom-%d.img" % index
234
235             # 1. Check and delete VM if already exists
236             model.Libvirt.check_if_vm_exists_and_delete(vm_name,
237                                                         self.connection)
238             xml_str, mac = model.Libvirt.build_vm_xml(
239                 self.connection, self.vm_flavor, vm_name, index, cdrom_img)
240
241             # 2: Cleanup already available VMs
242             network_ports = collections.OrderedDict(
243                 {k: v for k, v in vnf["network_ports"].items() if k != 'mgmt'})
244             for idx, vfs in enumerate(network_ports.values()):
245                 xml_str = self._enable_interfaces(index, idx, vfs, xml_str)
246
247             # copy xml to target...
248             model.Libvirt.write_file(cfg, xml_str)
249             self.connection.put(cfg, cfg)
250
251             node = self.vnf_node.generate_vnf_instance(self.vm_flavor,
252                                                        self.networks,
253                                                        self.host_mgmt.get('ip'),
254                                                        key, vnf, mac)
255             # Generate public/private keys if password or private key file is not provided
256             node = model.StandaloneContextHelper.check_update_key(self.connection,
257                                                                   node,
258                                                                   vm_name,
259                                                                   self.name,
260                                                                   cdrom_img,
261                                                                   mac)
262
263             # store vnf node details
264             nodes.append(node)
265
266             # NOTE: launch through libvirt
267             LOG.info("virsh create ...")
268             model.Libvirt.virsh_create_vm(self.connection, cfg)
269
270             self.vm_names.append(vm_name)
271
272         return nodes
273
274     def _get_vf_data(self, value, vfmac, pfif):
275         vf_data = {
276             "mac": vfmac,
277             "pf_if": pfif
278         }
279         vfs = model.StandaloneContextHelper.get_virtual_devices(
280             self.connection, value)
281         for k, v in vfs.items():
282             m = PciAddress(k.strip())
283             m1 = PciAddress(value.strip())
284             if m.bus == m1.bus:
285                 vf_data.update({"vf_pci": str(v)})
286                 break
287
288         return vf_data