Merge "Do not request NFVi metrics from empty nodes"
[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
230             # 1. Check and delete VM if already exists
231             model.Libvirt.check_if_vm_exists_and_delete(vm_name,
232                                                         self.connection)
233             xml_str, mac = model.Libvirt.build_vm_xml(
234                 self.connection, self.vm_flavor, vm_name, index)
235
236             # 2: Cleanup already available VMs
237             network_ports = collections.OrderedDict(
238                 {k: v for k, v in vnf["network_ports"].items() if k != 'mgmt'})
239             for idx, vfs in enumerate(network_ports.values()):
240                 xml_str = self._enable_interfaces(index, idx, vfs, xml_str)
241
242             # copy xml to target...
243             model.Libvirt.write_file(cfg, xml_str)
244             self.connection.put(cfg, cfg)
245
246             # NOTE: launch through libvirt
247             LOG.info("virsh create ...")
248             model.Libvirt.virsh_create_vm(self.connection, cfg)
249
250             self.vm_names.append(vm_name)
251
252             # build vnf node details
253             nodes.append(self.vnf_node.generate_vnf_instance(
254                 self.vm_flavor, self.networks, self.host_mgmt.get('ip'),
255                 key, vnf, mac))
256
257         return nodes
258
259     def _get_vf_data(self, value, vfmac, pfif):
260         vf_data = {
261             "mac": vfmac,
262             "pf_if": pfif
263         }
264         vfs = model.StandaloneContextHelper.get_virtual_devices(
265             self.connection, value)
266         for k, v in vfs.items():
267             m = PciAddress(k.strip())
268             m1 = PciAddress(value.strip())
269             if m.bus == m1.bus:
270                 vf_data.update({"vf_pci": str(v)})
271                 break
272
273         return vf_data