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