vnfs: Enable PVP using vhost-user
[vswitchperf.git] / src / dpdk / dpdk.py
1 # Copyright 2015 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 """Automation of system configuration for DPDK use.
16
17 Parts of this based on ``tools/pci_unbind.py`` script from Intel(R) DPDK.
18 """
19
20 from sys import platform as _platform
21
22 import os
23 import re
24 import subprocess
25 import logging
26 import locale
27
28 from tools import tasks
29 from conf import settings
30 from tools.module_manager import ModuleManager, KernelModuleInsertMode
31
32 _LOGGER = logging.getLogger(__name__)
33 RTE_PCI_TOOL = os.path.join(
34     settings.getValue('RTE_SDK'), 'tools', 'dpdk_nic_bind.py')
35
36 _DPDK_MODULE_MANAGER = ModuleManager(KernelModuleInsertMode.MODPROBE)
37
38 #
39 # system management
40 #
41
42
43 def init():
44     """Setup system for DPDK.
45     """
46     if not _is_linux():
47         _LOGGER.error('Not running on a compatible Linux version. Exiting...')
48         return
49
50     _mount_hugepages()
51     _insert_modules()
52     _remove_vhost_net()
53     _bind_nics()
54     _copy_dpdk_for_guest()
55
56
57 def cleanup():
58     """Setup system for DPDK.
59     """
60     if not _is_linux():
61         _LOGGER.error('Not running on a compatible Linux version. Exiting...')
62         return
63
64     _unbind_nics()
65     _remove_modules()
66     _umount_hugepages()
67     _vhost_user_cleanup()
68
69
70 #
71 # vhost specific modules management
72 #
73
74
75 def insert_vhost_modules():
76     """Inserts VHOST related kernel modules
77     """
78     mod_path_prefix = os.path.join(settings.getValue('RTE_SDK'),
79                                    'lib',
80                                    'librte_vhost')
81     _insert_module_group('VHOST_MODULE', mod_path_prefix)
82
83
84 def remove_vhost_modules():
85     """Removes all VHOST related kernel modules
86     """
87     _remove_module_group('VHOST_MODULE')
88
89 #
90 # basic compatibility test
91 #
92
93
94 def _is_linux():
95     """Check if running on Linux.
96
97     Many of the functions in this file rely on features commonly found
98     only on Linux (i.e. ``/proc`` is not present on FreeBSD). Hence, this
99     check is important to ensure someone doesn't run this on an incompatible
100     OS or distro.
101     """
102     return _platform.startswith('linux') and os.path.isdir('/proc')
103
104 #
105 # hugepage management
106 #
107
108
109 def _is_hugepage_available():
110     """Check if hugepages are available on the system.
111     """
112     hugepage_re = re.compile(r'^HugePages_Free:\s+(?P<num_hp>\d+)$')
113
114     # read in meminfo
115     with open('/proc/meminfo') as mem_file:
116         mem_info = mem_file.readlines()
117
118     # first check if module is loaded
119     for line in mem_info:
120         result = hugepage_re.match(line)
121         if not result:
122             continue
123
124         num_huge = result.group('num_hp')
125         if not num_huge:
126             _LOGGER.info('No free hugepages.')
127         else:
128             _LOGGER.info('Found \'%s\' free hugepage(s).', num_huge)
129         return True
130
131     return False
132
133
134 def _is_hugepage_mounted():
135     """Check if hugepages are mounted.
136     """
137     output = subprocess.check_output(['mount'], shell=True)
138     my_encoding = locale.getdefaultlocale()[1]
139     for line in output.decode(my_encoding).split('\n'):
140         if 'hugetlbfs' in line:
141             return True
142
143     return False
144
145
146 def _mount_hugepages():
147     """Ensure hugepages are mounted.
148     """
149     if not _is_hugepage_available():
150         return
151
152     if _is_hugepage_mounted():
153         return
154
155     if not os.path.exists(settings.getValue('HUGEPAGE_DIR')):
156         os.makedirs(settings.getValue('HUGEPAGE_DIR'))
157     try:
158         tasks.run_task(['sudo', 'mount', '-t', 'hugetlbfs', 'nodev',
159                         settings.getValue('HUGEPAGE_DIR')],
160                        _LOGGER, 'Mounting hugepages...', True)
161     except subprocess.CalledProcessError:
162         _LOGGER.error('Unable to mount hugepages.')
163
164
165 def _umount_hugepages():
166     """Ensure hugepages are unmounted.
167     """
168     if not _is_hugepage_mounted():
169         return
170
171     try:
172         tasks.run_task(['sudo', 'umount', settings.getValue('HUGEPAGE_DIR')],
173                        _LOGGER, 'Unmounting hugepages...', True)
174     except subprocess.CalledProcessError:
175         _LOGGER.error('Unable to umount hugepages.')
176
177 #
178 # module management
179 #
180
181
182 def _is_module_inserted(module):
183     """Check if a module is inserted on system.
184     """
185     with open('/proc/modules') as mod_file:
186         loaded_mods = mod_file.readlines()
187
188     # first check if module is loaded
189     for line in loaded_mods:
190         if line.startswith(module):
191             return True
192     return False
193
194
195 def _insert_modules():
196     """Ensure required modules are inserted on system.
197     """
198
199     _DPDK_MODULE_MANAGER.insert_modules(settings.getValue('SYS_MODULES'))
200
201     mod_path_prefix = settings.getValue('OVS_DIR')
202     _insert_module_group('OVS_MODULES', mod_path_prefix)
203     mod_path_prefix = os.path.join(settings.getValue('RTE_SDK'),
204                                    settings.getValue('RTE_TARGET'))
205     _insert_module_group('DPDK_MODULES', mod_path_prefix)
206
207
208 def _insert_module_group(module_group, group_path_prefix):
209     """Ensure all modules in a group are inserted into the system.
210
211     :param module_group: A name of configuration item containing a list
212     of module names
213     """
214     for module in settings.getValue(module_group):
215         # first check if module is loaded
216         if _is_module_inserted(module[1]):
217             continue
218
219         try:
220             mod_path = os.path.join(group_path_prefix, module[0],
221                                     '%s.ko' % module[1])
222             tasks.run_task(['sudo', 'insmod', mod_path], _LOGGER,
223                            'Inserting module \'%s\'...' % module[1], True)
224         except subprocess.CalledProcessError:
225             _LOGGER.error('Unable to insert module \'%s\'.', module[1])
226             raise  # fail catastrophically
227
228
229 def _remove_modules():
230     """Ensure required modules are removed from system.
231     """
232     _remove_module_group('OVS_MODULES')
233     _remove_module_group('DPDK_MODULES')
234
235     _DPDK_MODULE_MANAGER.remove_modules()
236
237 def _remove_module_group(module_group):
238     """Ensure all modules in a group are removed from the system.
239
240     :param module_group: A name of configuration item containing a list
241     of module names
242     """
243     for module in settings.getValue(module_group):
244         # first check if module is loaded
245         if not _is_module_inserted(module[1]):
246             continue
247
248         try:
249             tasks.run_task(['sudo', 'rmmod', module[1]], _LOGGER,
250                            'Removing module \'%s\'...' % module[1], True)
251         except subprocess.CalledProcessError:
252             _LOGGER.error('Unable to remove module \'%s\'.', module[1])
253             continue
254
255
256 #
257 # 'vhost-net' module management
258 #
259
260 def _remove_vhost_net():
261     """Remove vhost-net driver and file.
262     """
263     if _is_module_inserted('vhost_net'):
264         try:
265             tasks.run_task(['sudo', 'rmmod', 'vhost_net'], _LOGGER,
266                            'Removing \'/dev/vhost-net\' directory...', True)
267         except subprocess.CalledProcessError:
268             _LOGGER.error('Unable to remove module \'vhost_net\'.')
269
270     try:
271         tasks.run_task(['sudo', 'rm', '-f', '/dev/vhost-net'], _LOGGER,
272                        'Removing \'/dev/vhost-net\' directory...', True)
273     except subprocess.CalledProcessError:
274         _LOGGER.error('Unable to remove directory \'/dev/vhost-net\'.')
275
276 #
277 # NIC management
278 #
279
280
281 def _bind_nics():
282     """Bind NICs using the Intel DPDK ``pci_unbind.py`` tool.
283     """
284     try:
285         tasks.run_task(['sudo', RTE_PCI_TOOL, '--bind', 'igb_uio'] +
286                        settings.getValue('WHITELIST_NICS'), _LOGGER,
287                        'Binding NICs %s...' %
288                        settings.getValue('WHITELIST_NICS'),
289                        True)
290     except subprocess.CalledProcessError:
291         _LOGGER.error('Unable to bind NICs %s',
292                       str(settings.getValue('WHITELIST_NICS')))
293
294
295 def _unbind_nics():
296     """Unbind NICs using the Intel DPDK ``pci_unbind.py`` tool.
297     """
298     try:
299         tasks.run_task(['sudo', RTE_PCI_TOOL, '--unbind'] +
300                        settings.getValue('WHITELIST_NICS'), _LOGGER,
301                        'Unbinding NICs %s...' %
302                        str(settings.getValue('WHITELIST_NICS')),
303                        True)
304     except subprocess.CalledProcessError:
305         _LOGGER.error('Unable to unbind NICs %s',
306                       str(settings.getValue('WHITELIST_NICS')))
307
308
309 def _copy_dpdk_for_guest():
310     """Copy dpdk code to GUEST_SHARE_DIR for use by guests.
311     """
312     guest_share_dir = os.path.join(
313         settings.getValue('GUEST_SHARE_DIR'), 'DPDK')
314
315     if not os.path.exists(guest_share_dir):
316         os.makedirs(guest_share_dir)
317
318     try:
319         tasks.run_task(['rsync', '-a', '-r', '-l', r'--exclude="\.git"',
320                         os.path.join(settings.getValue('RTE_SDK'), ''),
321                         guest_share_dir],
322                        _LOGGER,
323                        'Copying DPDK to shared directory...',
324                        True)
325     except subprocess.CalledProcessError:
326         _LOGGER.error('Unable to copy DPDK to shared directory')
327
328
329 #
330 # Vhost-user cleanup
331 #
332
333 def _vhost_user_cleanup():
334     """Remove files created by vhost-user tests.
335     """
336     for sock in settings.getValue('VHOST_USER_SOCKS'):
337         if os.path.exists(sock):
338             try:
339                 tasks.run_task(['sudo', 'rm', sock],
340                                _LOGGER,
341                                'Deleting vhost-user socket \'%s\'...' %
342                                sock,
343                                True)
344
345             except subprocess.CalledProcessError:
346                 _LOGGER.error('Unable to delete vhost-user socket \'%s\'.',
347                               sock)
348                 continue
349
350
351 class Dpdk(object):
352     """A context manager for the system init/cleanup.
353     """
354     def __enter__(self):
355         _LOGGER.info('Setting up DPDK')
356         init()
357         return self
358
359     def __exit__(self, type_, value, traceback):
360         _LOGGER.info('Cleaning up DPDK')
361         cleanup()