8c44b26c2b506bc84f0b26308e4e4d2474267360
[yardstick.git] / yardstick / network_services / helpers / dpdkbindnic_helper.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 import logging
15
16 import re
17 import itertools
18
19 import six
20
21 NETWORK_KERNEL = 'network_kernel'
22 NETWORK_DPDK = 'network_dpdk'
23 NETWORK_OTHER = 'network_other'
24 CRYPTO_KERNEL = 'crypto_kernel'
25 CRYPTO_DPDK = 'crypto_dpdk'
26 CRYPTO_OTHER = 'crypto_other'
27
28
29 LOG = logging.getLogger(__name__)
30
31
32 class DpdkBindHelperException(Exception):
33     pass
34
35
36 class DpdkBindHelper(object):
37     DPDK_STATUS_CMD = "{dpdk_devbind} --status"
38     DPDK_BIND_CMD = "sudo {dpdk_devbind} {force} -b {driver} {vpci}"
39
40     NIC_ROW_RE = re.compile(r"([^ ]+) '([^']+)' (?:if=([^ ]+) )?drv=([^ ]+) "
41                             r"unused=([^ ]*)(?: (\*Active\*))?")
42     SKIP_RE = re.compile('(====|<none>|^$)')
43     NIC_ROW_FIELDS = ['vpci', 'dev_type', 'iface', 'driver', 'unused', 'active']
44
45     HEADER_DICT_PAIRS = [
46         (re.compile('^Network.*DPDK.*$'), NETWORK_DPDK),
47         (re.compile('^Network.*kernel.*$'), NETWORK_KERNEL),
48         (re.compile('^Other network.*$'), NETWORK_OTHER),
49         (re.compile('^Crypto.*DPDK.*$'), CRYPTO_DPDK),
50         (re.compile('^Crypto.*kernel$'), CRYPTO_KERNEL),
51         (re.compile('^Other crypto.*$'), CRYPTO_OTHER),
52     ]
53
54     def clean_status(self):
55         self.dpdk_status = {
56             NETWORK_KERNEL: [],
57             NETWORK_DPDK: [],
58             CRYPTO_KERNEL: [],
59             CRYPTO_DPDK: [],
60             NETWORK_OTHER: [],
61             CRYPTO_OTHER: [],
62         }
63
64     def __init__(self, ssh_helper):
65         self.dpdk_status = None
66         self.status_nic_row_re = None
67         self._dpdk_devbind = None
68         self._status_cmd_attr = None
69
70         self.ssh_helper = ssh_helper
71         self.clean_status()
72
73     def _dpdk_execute(self, *args, **kwargs):
74         res = self.ssh_helper.execute(*args, **kwargs)
75         if res[0] != 0:
76             raise DpdkBindHelperException('{} command failed with rc={}'.format(
77                 self.dpdk_devbind, res[0]))
78         return res
79
80     @property
81     def dpdk_devbind(self):
82         if self._dpdk_devbind is None:
83             self._dpdk_devbind = self.ssh_helper.provision_tool(tool_file="dpdk-devbind.py")
84         return self._dpdk_devbind
85
86     @property
87     def _status_cmd(self):
88         if self._status_cmd_attr is None:
89             self._status_cmd_attr = self.DPDK_STATUS_CMD.format(dpdk_devbind=self.dpdk_devbind)
90         return self._status_cmd_attr
91
92     def _addline(self, active_list, line):
93         if active_list is None:
94             return
95         res = self.NIC_ROW_RE.match(line)
96         if res is None:
97             return
98         new_data = {k: v for k, v in zip(self.NIC_ROW_FIELDS, res.groups())}
99         new_data['active'] = bool(new_data['active'])
100         self.dpdk_status[active_list].append(new_data)
101
102     @classmethod
103     def _switch_active_dict(cls, a_row, active_dict):
104         for regexp, a_dict in cls.HEADER_DICT_PAIRS:
105             if regexp.match(a_row):
106                 return a_dict
107         return active_dict
108
109     def parse_dpdk_status_output(self, input):
110         active_dict = None
111         self.clean_status()
112         for a_row in input.splitlines():
113             if self.SKIP_RE.match(a_row):
114                 continue
115             active_dict = self._switch_active_dict(a_row, active_dict)
116             self._addline(active_dict, a_row)
117         return self.dpdk_status
118
119     def _get_bound_pci_addresses(self, active_dict):
120         return [iface['vpci'] for iface in self.dpdk_status[active_dict]]
121
122     @property
123     def dpdk_bound_pci_addresses(self):
124         return self._get_bound_pci_addresses(NETWORK_DPDK)
125
126     @property
127     def kernel_bound_pci_addresses(self):
128         return self._get_bound_pci_addresses(NETWORK_KERNEL)
129
130     @property
131     def interface_driver_map(self):
132         return {interface['vpci']: interface['driver']
133                 for interface in itertools.chain.from_iterable(self.dpdk_status.values())}
134
135     def read_status(self):
136         return self.parse_dpdk_status_output(self._dpdk_execute(self._status_cmd)[1])
137
138     def bind(self, pci_addresses, driver, force=True):
139         # accept single PCI or list of PCI
140         if isinstance(pci_addresses, six.string_types):
141             pci_addresses = [pci_addresses]
142         cmd = self.DPDK_BIND_CMD.format(dpdk_devbind=self.dpdk_devbind,
143                                         driver=driver,
144                                         vpci=' '.join(list(pci_addresses)),
145                                         force='--force' if force else '')
146         LOG.debug(cmd)
147         self._dpdk_execute(cmd)
148         # update the inner status dict
149         self.read_status()
150
151     def save_used_drivers(self):
152         # invert the map, so we can bind by driver type
153         self.used_drivers = {}
154         # sort for stabililty
155         for vpci, driver in sorted(self.interface_driver_map.items()):
156             self.used_drivers.setdefault(driver, []).append(vpci)
157
158     def rebind_drivers(self, force=True):
159         for driver, vpcis in self.used_drivers.items():
160             self.bind(vpcis, driver, force)