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