1 # Copyright (c) 2016-2017 Intel Corporation
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
7 # http://www.apache.org/licenses/LICENSE-2.0
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.
15 from __future__ import absolute_import
21 from collections import OrderedDict
23 from yardstick import ssh
24 from yardstick.network_services.utils import get_nsb_option
25 from yardstick.network_services.utils import provision_tool
26 from yardstick.benchmark.contexts.base import Context
27 from yardstick.benchmark.contexts.standalone.model import Libvirt
28 from yardstick.benchmark.contexts.standalone.model import StandaloneContextHelper
29 from yardstick.benchmark.contexts.standalone.model import Server
30 from yardstick.benchmark.contexts.standalone.model import OvsDeploy
31 from yardstick.network_services.utils import PciAddress
33 LOG = logging.getLogger(__name__)
36 class OvsDpdkContext(Context):
37 """ This class handles OVS standalone nodes - VM running on Non-Managed NFVi
38 Configuration: ovs_dpdk
41 __context_type__ = "StandaloneOvsDpdk"
43 SUPPORTED_OVS_TO_DPDK_MAP = {
54 PKILL_TEMPLATE = "pkill %s %s"
60 self.dpdk_devbind = ''
68 self.helper = StandaloneContextHelper()
69 self.vnf_node = Server()
70 self.ovs_properties = {}
71 self.wait_for_vswitchd = 10
72 super(OvsDpdkContext, self).__init__()
74 def init(self, attrs):
75 """initializes itself from the supplied arguments"""
76 super(OvsDpdkContext, self).init(attrs)
78 self.file_path = attrs.get("file", "pod.yaml")
80 self.nodes, self.nfvi_host, self.host_mgmt = \
81 self.helper.parse_pod_file(self.file_path, 'OvsDpdk')
84 self.vm_flavor = attrs.get('flavor', {})
85 self.servers = attrs.get('servers', {})
86 self.vm_deploy = attrs.get("vm_deploy", True)
87 self.ovs_properties = attrs.get('ovs_properties', {})
88 # add optional static network definition
89 self.networks = attrs.get("networks", {})
91 LOG.debug("Nodes: %r", self.nodes)
92 LOG.debug("NFVi Node: %r", self.nfvi_host)
93 LOG.debug("Networks: %r", self.networks)
96 vpath = self.ovs_properties.get("vpath", "/usr/local")
97 xargs_kill_cmd = self.PKILL_TEMPLATE % ('-9', 'ovs')
99 create_from = os.path.join(vpath, 'etc/openvswitch/conf.db')
100 create_to = os.path.join(vpath, 'share/openvswitch/vswitch.ovsschema')
103 "chmod 0666 /dev/vfio/*",
104 "chmod a+x /dev/vfio",
108 "mkdir -p {0}/etc/openvswitch".format(vpath),
109 "mkdir -p {0}/var/run/openvswitch".format(vpath),
110 "rm {0}/etc/openvswitch/conf.db".format(vpath),
111 "ovsdb-tool create {0} {1}".format(create_from, create_to),
113 "chmod a+x /dev/vfio",
114 "chmod 0666 /dev/vfio/*",
117 self.connection.execute(cmd)
118 bind_cmd = "{dpdk_devbind} --force -b {driver} {port}"
119 phy_driver = "vfio-pci"
120 for port in self.networks.values():
121 vpci = port.get("phy_port")
122 self.connection.execute(bind_cmd.format(
123 dpdk_devbind=self.dpdk_devbind, driver=phy_driver, port=vpci))
125 def start_ovs_serverswitch(self):
126 vpath = self.ovs_properties.get("vpath")
127 pmd_nums = int(self.ovs_properties.get("pmd_threads", 2))
128 ovs_sock_path = '/var/run/openvswitch/db.sock'
129 log_path = '/var/log/openvswitch/ovs-vswitchd.log'
131 pmd_cpu_mask = self.ovs_properties.get("pmd_cpu_mask", '')
132 pmd_mask = hex(sum(2 ** num for num in range(pmd_nums)) << 1)
134 pmd_mask = pmd_cpu_mask
136 socket0 = self.ovs_properties.get("ram", {}).get("socket_0", "2048")
137 socket1 = self.ovs_properties.get("ram", {}).get("socket_1", "2048")
139 ovs_other_config = "ovs-vsctl {0}set Open_vSwitch . other_config:{1}"
140 detach_cmd = "ovs-vswitchd unix:{0}{1} --pidfile --detach --log-file={2}"
142 lcore_mask = self.ovs_properties.get("lcore_mask", '')
144 lcore_mask = ovs_other_config.format("--no-wait ", "dpdk-lcore-mask='%s'" % lcore_mask)
147 "mkdir -p /usr/local/var/run/openvswitch",
148 "mkdir -p {}".format(os.path.dirname(log_path)),
149 "ovsdb-server --remote=punix:/{0}/{1} --pidfile --detach".format(vpath,
151 ovs_other_config.format("--no-wait ", "dpdk-init=true"),
152 ovs_other_config.format("--no-wait ", "dpdk-socket-mem='%s,%s'" % (socket0, socket1)),
154 detach_cmd.format(vpath, ovs_sock_path, log_path),
155 ovs_other_config.format("", "pmd-cpu-mask=%s" % pmd_mask),
160 self.connection.execute(cmd)
161 time.sleep(self.wait_for_vswitchd)
163 def setup_ovs_bridge_add_flows(self):
166 vpath = self.ovs_properties.get("vpath", "/usr/local")
167 version = self.ovs_properties.get('version', {})
168 ovs_ver = [int(x) for x in version.get('ovs', self.DEFAULT_OVS).split('.')]
170 "ovs-vsctl add-port {br} {port} -- set Interface {port} type={type_}{dpdk_args}"
171 ovs_add_queue = "ovs-vsctl set Interface {port} options:n_rxq={queue}"
172 chmod_vpath = "chmod 0777 {0}/var/run/openvswitch/dpdkvhostuser*"
175 "ovs-vsctl del-br br0",
176 "rm -rf {0}/var/run/openvswitch/dpdkvhostuser*".format(vpath),
177 "ovs-vsctl add-br br0 -- set bridge br0 datapath_type=netdev",
180 ordered_network = OrderedDict(self.networks)
181 for index, vnf in enumerate(ordered_network.values()):
182 if ovs_ver >= [2, 7, 0]:
183 dpdk_args = " options:dpdk-devargs=%s" % vnf.get("phy_port")
184 dpdk_list.append(ovs_add_port.format(br='br0', port='dpdk%s' % vnf.get("port_num", 0),
185 type_='dpdk', dpdk_args=dpdk_args))
186 dpdk_list.append(ovs_add_queue.format(port='dpdk%s' % vnf.get("port_num", 0),
187 queue=self.ovs_properties.get("queues", 1)))
189 # Sorting the array to make sure we execute dpdk0... in the order
191 cmd_dpdk_list.extend(dpdk_list)
193 # Need to do two for loop to maintain the dpdk/vhost ports.
194 for index, _ in enumerate(ordered_network):
195 cmd_dpdk_list.append(ovs_add_port.format(br='br0', port='dpdkvhostuser%s' % index,
196 type_='dpdkvhostuser', dpdk_args=""))
198 for cmd in cmd_dpdk_list:
200 self.connection.execute(cmd)
202 # Fixme: add flows code
203 ovs_flow = "ovs-ofctl add-flow br0 in_port=%s,action=output:%s"
205 network_count = len(ordered_network) + 1
206 for in_port, out_port in zip(range(1, network_count),
207 range(network_count, network_count * 2)):
208 self.connection.execute(ovs_flow % (in_port, out_port))
209 self.connection.execute(ovs_flow % (out_port, in_port))
211 self.connection.execute(chmod_vpath.format(vpath))
213 def cleanup_ovs_dpdk_env(self):
214 self.connection.execute("ovs-vsctl del-br br0")
215 self.connection.execute("pkill -9 ovs")
217 def check_ovs_dpdk_env(self):
218 self.cleanup_ovs_dpdk_env()
220 version = self.ovs_properties.get("version", {})
221 ovs_ver = version.get("ovs", self.DEFAULT_OVS)
222 dpdk_ver = version.get("dpdk", "16.07.2").split('.')
224 supported_version = self.SUPPORTED_OVS_TO_DPDK_MAP.get(ovs_ver, None)
225 if supported_version is None or supported_version.split('.')[:2] != dpdk_ver[:2]:
226 raise Exception("Unsupported ovs '{}'. Please check the config...".format(ovs_ver))
228 status = self.connection.execute("ovs-vsctl -V | grep -i '%s'" % ovs_ver)[0]
230 deploy = OvsDeploy(self.connection,
231 get_nsb_option("bin_path"),
236 """don't need to deploy"""
238 # Todo: NFVi deploy (sriov, vswitch, ovs etc) based on the config.
239 if not self.vm_deploy:
242 self.connection = ssh.SSH.from_node(self.host_mgmt)
243 self.dpdk_devbind = provision_tool(
245 os.path.join(get_nsb_option("bin_path"), "dpdk-devbind.py"))
247 # Check dpdk/ovs version, if not present install
248 self.check_ovs_dpdk_env()
249 # Todo: NFVi deploy (sriov, vswitch, ovs etc) based on the config.
250 StandaloneContextHelper.install_req_libs(self.connection)
251 self.networks = StandaloneContextHelper.get_nic_details(
252 self.connection, self.networks, self.dpdk_devbind)
255 self.start_ovs_serverswitch()
256 self.setup_ovs_bridge_add_flows()
257 self.nodes = self.setup_ovs_dpdk_context()
258 LOG.debug("Waiting for VM to come up...")
259 self.nodes = StandaloneContextHelper.wait_for_vnfs_to_start(self.connection,
265 if not self.vm_deploy:
268 # Cleanup the ovs installation...
269 self.cleanup_ovs_dpdk_env()
271 # Bind nics back to kernel
272 bind_cmd = "{dpdk_devbind} --force -b {driver} {port}"
273 for port in self.networks.values():
274 vpci = port.get("phy_port")
275 phy_driver = port.get("driver")
276 self.connection.execute(bind_cmd.format(
277 dpdk_devbind=self.dpdk_devbind, driver=phy_driver, port=vpci))
279 # Todo: NFVi undeploy (sriov, vswitch, ovs etc) based on the config.
280 for vm in self.vm_names:
281 Libvirt.check_if_vm_exists_and_delete(vm, self.connection)
283 def _get_server(self, attr_name):
284 """lookup server info by name from context
287 attr_name -- A name for a server listed in nodes config file
289 node_name, name = self.split_name(attr_name)
290 if name is None or self.name != name:
293 matching_nodes = (n for n in self.nodes if n["name"] == node_name)
295 # A clone is created in order to avoid affecting the
297 node = dict(next(matching_nodes))
298 except StopIteration:
302 duplicate = next(matching_nodes)
303 except StopIteration:
306 raise ValueError("Duplicate nodes!!! Nodes: %s %s" % (node, duplicate))
308 node["name"] = attr_name
311 def _get_network(self, attr_name):
312 if not isinstance(attr_name, collections.Mapping):
313 network = self.networks.get(attr_name)
316 # Don't generalize too much Just support vld_id
317 vld_id = attr_name.get('vld_id', {})
318 # for standalone context networks are dicts
319 iter1 = (n for n in self.networks.values() if n.get('vld_id') == vld_id)
320 network = next(iter1, None)
327 "name": network["name"],
328 "vld_id": network.get("vld_id"),
329 "segmentation_id": network.get("segmentation_id"),
330 "network_type": network.get("network_type"),
331 "physical_network": network.get("physical_network"),
335 def configure_nics_for_ovs_dpdk(self):
336 portlist = OrderedDict(self.networks)
338 mac = StandaloneContextHelper.get_mac_address()
339 portlist[key].update({'mac': mac})
340 self.networks = portlist
341 LOG.info("Ports %s", self.networks)
343 def _enable_interfaces(self, index, vfs, cfg):
344 vpath = self.ovs_properties.get("vpath", "/usr/local")
345 vf = self.networks[vfs[0]]
346 port_num = vf.get('port_num', 0)
347 vpci = PciAddress(vf['vpci'].strip())
348 # Generate the vpci for the interfaces
349 slot = index + port_num + 10
351 "{}:{}:{:02x}.{}".format(vpci.domain, vpci.bus, slot, vpci.function)
352 Libvirt.add_ovs_interface(vpath, port_num, vf['vpci'], vf['mac'], str(cfg))
354 def setup_ovs_dpdk_context(self):
357 self.configure_nics_for_ovs_dpdk()
359 for index, (key, vnf) in enumerate(OrderedDict(self.servers).items()):
360 cfg = '/tmp/vm_ovs_%d.xml' % index
361 vm_name = "vm_%d" % index
363 # 1. Check and delete VM if already exists
364 Libvirt.check_if_vm_exists_and_delete(vm_name, self.connection)
366 _, mac = Libvirt.build_vm_xml(self.connection, self.vm_flavor,
368 # 2: Cleanup already available VMs
369 for vkey, vfs in OrderedDict(vnf["network_ports"]).items():
372 self._enable_interfaces(index, vfs, cfg)
374 # copy xml to target...
375 self.connection.put(cfg, cfg)
377 # NOTE: launch through libvirt
378 LOG.info("virsh create ...")
379 Libvirt.virsh_create_vm(self.connection, cfg)
381 self.vm_names.append(vm_name)
383 # build vnf node details
384 nodes.append(self.vnf_node.generate_vnf_instance(self.vm_flavor,
386 self.host_mgmt.get('ip'),