Redhat and small system support
[vpp.git] / extras / vpp_config / vpplib / VPPUtil.py
1 # Copyright (c) 2016 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
5 #
6 #     http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14 """VPP util library"""
15 import logging
16 import re
17 import subprocess
18 import platform
19
20 from collections import Counter
21
22 # VPP_VERSION = '1707'
23 VPP_VERSION = '1710'
24
25
26 class VPPUtil(object):
27     """General class for any VPP related methods/functions."""
28
29     @staticmethod
30     def exec_command(cmd, timeout=None):
31         """Execute a command on the local node.
32
33         :param cmd: Command to run locally.
34         :param timeout: Timeout value
35         :type cmd: str
36         :type timeout: int
37         :return return_code, stdout, stderr
38         :rtype: tuple(int, str, str)
39         """
40
41         logging.info(" Local Command: {}".format(cmd))
42         out = ''
43         err = ''
44         prc = subprocess.Popen(cmd, shell=True, bufsize=1,
45                                stdin=subprocess.PIPE,
46                                stdout=subprocess.PIPE,
47                                stderr=subprocess.PIPE)
48
49         with prc.stdout:
50             for line in iter(prc.stdout.readline, b''):
51                 logging.info("  {}".format(line.strip('\n')))
52                 out += line
53
54         with prc.stderr:
55             for line in iter(prc.stderr.readline, b''):
56                 logging.warn("  {}".format(line.strip('\n')))
57                 err += line
58
59         ret = prc.wait()
60
61         return ret, out, err
62
63     def _autoconfig_backup_file(self, filename):
64         """
65         Create a backup file.
66
67         :param filename: The file to backup
68         :type filename: str
69         """
70
71         # Does a copy of the file exist, if not create one
72         ofile = filename + '.orig'
73         (ret, stdout, stderr) = self.exec_command('ls {}'.format(ofile))
74         if ret != 0:
75             logging.debug(stderr)
76             if stdout.strip('\n') != ofile:
77                 cmd = 'sudo cp {} {}'.format(filename, ofile)
78                 (ret, stdout, stderr) = self.exec_command(cmd)
79                 if ret != 0:
80                     logging.debug(stderr)
81
82     def _install_vpp_pkg_ubuntu(self, node, pkg):
83         """
84         Install the VPP packages
85
86         :param node: Node dictionary
87         :param pkg: The vpp packages
88         :type node: dict
89         :type pkg: string
90         """
91
92         cmd = 'apt-get -y install {}'.format(pkg)
93         (ret, stdout, stderr) = self.exec_command(cmd)
94         if ret != 0:
95             raise RuntimeError('{} failed on node {} {} {}'.format(
96                 cmd, node['host'], stdout, stderr))
97
98     def _install_vpp_pkg_centos(self, node, pkg):
99         """
100         Install the VPP packages
101
102         :param node: Node dictionary
103         :param pkg: The vpp packages
104         :type node: dict
105         :type pkg: string
106         """
107
108         cmd = 'yum -y install {}'.format(pkg)
109         (ret, stdout, stderr) = self.exec_command(cmd)
110         if ret != 0:
111             raise RuntimeError('{} failed on node {} {} {}'.format(
112                 cmd, node['host'], stdout, stderr))
113
114     def _install_vpp_ubuntu(self, node, fdio_release=VPP_VERSION,
115                             ubuntu_version='xenial'):
116         """
117         Install the VPP packages
118
119         :param node: Node dictionary with cpuinfo.
120         :param fdio_release: VPP release number
121         :param ubuntu_version: Ubuntu Version
122         :type node: dict
123         :type fdio_release: string
124         :type ubuntu_version: string
125         """
126
127         # Modify the sources list
128         sfile = '/etc/apt/sources.list.d/99fd.io.list'
129
130         # Backup the sources list
131         self._autoconfig_backup_file(sfile)
132
133         # Remove the current file
134         cmd = 'rm {}'.format(sfile)
135         (ret, stdout, stderr) = self.exec_command(cmd)
136         if ret != 0:
137             logging.debug('{} failed on node {} {}'.format(
138                 cmd,
139                 node['host'],
140                 stderr))
141
142         reps = 'deb [trusted=yes] https://nexus.fd.io/content/'
143         reps += 'repositories/fd.io.stable.{}.ubuntu.{}.main/ ./\n' \
144             .format(fdio_release, ubuntu_version)
145
146         cmd = 'echo "{0}" | sudo tee {1}'.format(reps, sfile)
147         (ret, stdout, stderr) = self.exec_command(cmd)
148         if ret != 0:
149             raise RuntimeError('{} failed on node {} {}'.format(
150                 cmd,
151                 node['host'],
152                 stderr))
153
154         # Install the package
155         cmd = 'apt-get -y update'
156         (ret, stdout, stderr) = self.exec_command(cmd)
157         if ret != 0:
158             raise RuntimeError('{} apt-get update failed on node {} {}'.format(
159                 cmd,
160                 node['host'],
161                 stderr))
162
163         self._install_vpp_pkg_ubuntu(node, 'vpp-lib')
164         self._install_vpp_pkg_ubuntu(node, 'vpp')
165         self._install_vpp_pkg_ubuntu(node, 'vpp-plugins')
166         self._install_vpp_pkg_ubuntu(node, 'vpp-dpdk-dkms')
167         self._install_vpp_pkg_ubuntu(node, 'vpp-dpdk-dev')
168         self._install_vpp_pkg_ubuntu(node, 'vpp-api-python')
169         self._install_vpp_pkg_ubuntu(node, 'vpp-api-java')
170         self._install_vpp_pkg_ubuntu(node, 'vpp-api-lua')
171         self._install_vpp_pkg_ubuntu(node, 'vpp-dev')
172         self._install_vpp_pkg_ubuntu(node, 'vpp-dbg')
173
174     def _install_vpp_centos(self, node, fdio_release=VPP_VERSION,
175                             centos_version='centos7'):
176         """
177         Install the VPP packages
178
179         :param node: Node dictionary with cpuinfo.
180         :param fdio_release: VPP release number
181         :param centos_version: Ubuntu Version
182         :type node: dict
183         :type fdio_release: string
184         :type centos_version: string
185         """
186
187         # Modify the sources list
188         sfile = '/etc/yum.repos.d/fdio-release.repo'
189
190         # Backup the sources list
191         self._autoconfig_backup_file(sfile)
192
193         # Remove the current file
194         cmd = 'rm {}'.format(sfile)
195         (ret, stdout, stderr) = self.exec_command(cmd)
196         if ret != 0:
197             logging.debug('{} failed on node {} {}'.format(
198                 cmd,
199                 node['host'],
200                 stderr))
201
202         reps = '[fdio-stable-{}]\n'.format(fdio_release)
203         reps += 'name=fd.io stable/{} branch latest merge\n'.format(fdio_release)
204         reps += 'baseurl=https://nexus.fd.io/content/repositories/fd.io.stable.{}.{}/\n'.\
205             format(fdio_release, centos_version)
206         reps += 'enabled=1\n'
207         reps += 'gpgcheck=0'
208
209         cmd = 'echo "{0}" | sudo tee {1}'.format(reps, sfile)
210         (ret, stdout, stderr) = self.exec_command(cmd)
211         if ret != 0:
212             raise RuntimeError('{} failed on node {} {}'.format(
213                 cmd,
214                 node['host'],
215                 stderr))
216
217         # Install the packages
218  
219         self._install_vpp_pkg_centos(node, 'vpp-lib')
220         self._install_vpp_pkg_centos(node, 'vpp')
221         self._install_vpp_pkg_centos(node, 'vpp-plugins')
222         # jadfix Check with Ole
223         # self._install_vpp_pkg_centos(node, 'vpp-dpdk-devel')
224         self._install_vpp_pkg_centos(node, 'vpp-api-python')
225         self._install_vpp_pkg_centos(node, 'vpp-api-java')
226         self._install_vpp_pkg_centos(node, 'vpp-api-lua')
227         self._install_vpp_pkg_centos(node, 'vpp-devel')
228
229     def install_vpp(self, node):
230         """
231         Install the VPP packages
232
233         :param node: Node dictionary with cpuinfo.
234         :type node: dict
235         """
236         distro = self.get_linux_distro()
237         if distro[0] == 'Ubuntu':
238             self._install_vpp_ubuntu(node)
239         elif distro[0] == 'CentOS Linux':
240             logging.info("Install CentOS")
241             self._install_vpp_centos(node)
242         else:
243             return
244
245     def _uninstall_vpp_pkg_ubuntu(self, node, pkg):
246         """
247         Uninstall the VPP packages
248
249         :param node: Node dictionary
250         :param pkg: The vpp packages
251         :type node: dict
252         :type pkg: string
253         """
254         cmd = 'dpkg --purge {}'.format(pkg)
255         (ret, stdout, stderr) = self.exec_command(cmd)
256         if ret != 0:
257             raise RuntimeError('{} failed on node {} {} {}'.format(
258                 cmd, node['host'], stdout, stderr))
259
260     def _uninstall_vpp_pkg_centos(self, node, pkg):
261         """
262         Uninstall the VPP packages
263
264         :param node: Node dictionary
265         :param pkg: The vpp packages
266         :type node: dict
267         :type pkg: string
268         """
269         cmd = 'yum -y remove {}'.format(pkg)
270         (ret, stdout, stderr) = self.exec_command(cmd)
271         if ret != 0:
272             raise RuntimeError('{} failed on node {} {} {}'.format(
273                 cmd, node['host'], stdout, stderr))
274
275     def _uninstall_vpp_ubuntu(self, node):
276         """
277         Uninstall the VPP packages
278
279         :param node: Node dictionary with cpuinfo.
280         :type node: dict
281         """
282         pkgs = self.get_installed_vpp_pkgs()
283
284         if len(pkgs) > 0:
285             if 'version' in pkgs[0]:
286                 logging.info("Uninstall Ubuntu Packages")
287                 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-api-python')
288                 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-api-java')
289                 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-api-lua')
290                 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-plugins')
291                 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-dpdk-dev')
292                 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-dpdk-dkms')
293                 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-dev')
294                 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-dbg')
295                 self._uninstall_vpp_pkg_ubuntu(node, 'vpp')
296                 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-lib')
297             else:
298                 logging.info("Uninstall locally installed Ubuntu Packages")
299                 for pkg in pkgs:
300                     self._uninstall_vpp_pkg_ubuntu(node, pkg['name'])
301         else:
302             logging.error("There are no Ubuntu packages installed")
303
304     def _uninstall_vpp_centos(self, node):
305         """
306         Uninstall the VPP packages
307
308         :param node: Node dictionary with cpuinfo.
309         :type node: dict
310             """
311
312         pkgs = self.get_installed_vpp_pkgs()
313
314         if len(pkgs) > 0:
315             if 'version' in pkgs[0]:
316                 logging.info("Uninstall CentOS Packages")
317                 self._uninstall_vpp_pkg_centos(node, 'vpp-api-python')
318                 self._uninstall_vpp_pkg_centos(node, 'vpp-api-java')
319                 self._uninstall_vpp_pkg_centos(node, 'vpp-api-lua')
320                 self._uninstall_vpp_pkg_centos(node, 'vpp-plugins')
321                 self._uninstall_vpp_pkg_centos(node, 'vpp-dpdk-devel')
322                 self._uninstall_vpp_pkg_centos(node, 'vpp-devel')
323                 self._uninstall_vpp_pkg_centos(node, 'vpp')
324                 self._uninstall_vpp_pkg_centos(node, 'vpp-lib')
325             else:
326                 logging.info("Uninstall locally installed CentOS Packages")
327                 for pkg in pkgs:
328                     self._uninstall_vpp_pkg_centos(node, pkg['name'])
329         else:
330             logging.error("There are no CentOS packages installed")
331
332     def uninstall_vpp(self, node):
333         """
334         Uninstall the VPP packages
335
336         :param node: Node dictionary with cpuinfo.
337         :type node: dict
338         """
339         distro = self.get_linux_distro()
340         if distro[0] == 'Ubuntu':
341             self._uninstall_vpp_ubuntu(node)
342         elif distro[0] == 'CentOS Linux':
343             logging.info("Uninstall CentOS")
344             self._uninstall_vpp_centos(node)
345         else:
346             return
347
348     def show_vpp_settings(self, *additional_cmds):
349         """
350         Print default VPP settings. In case others are needed, can be
351         accepted as next parameters (each setting one parameter), preferably
352         in form of a string.
353
354         :param additional_cmds: Additional commands that the vpp should print
355         settings for.
356         :type additional_cmds: tuple
357         """
358         def_setting_tb_displayed = {
359             'IPv6 FIB': 'ip6 fib',
360             'IPv4 FIB': 'ip fib',
361             'Interface IP': 'int addr',
362             'Interfaces': 'int',
363             'ARP': 'ip arp',
364             'Errors': 'err'
365         }
366
367         if additional_cmds:
368             for cmd in additional_cmds:
369                 def_setting_tb_displayed['Custom Setting: {}'.format(cmd)] \
370                     = cmd
371
372                 for _, value in def_setting_tb_displayed.items():
373                     self.exec_command('vppctl sh {}'.format(value))
374
375     @staticmethod
376     def get_hardware(node):
377         """
378         Get the VPP hardware information and return it in a
379         dictionary
380
381         :param node: VPP node.
382         :type node: dict
383         :returns: Dictionary containing improtant VPP information
384         :rtype: dictionary
385         """
386
387         interfaces = {}
388         cmd = 'vppctl show hard'
389         (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
390         if ret != 0:
391             return interfaces
392
393         lines = stdout.split('\n')
394         if len(lines[0]) is not 0:
395             if lines[0].split(' ')[0] == 'FileNotFoundError':
396                 return interfaces
397
398         for line in lines:
399             if len(line) is 0:
400                 continue
401
402             # If the first character is not whitespace
403             # create a new interface
404             if len(re.findall(r'\s', line[0])) is 0:
405                 spl = line.split()
406                 name = spl[0]
407                 interfaces[name] = {}
408                 interfaces[name]['index'] = spl[1]
409                 interfaces[name]['state'] = spl[2]
410
411             # Ethernet address
412             rfall = re.findall(r'Ethernet address', line)
413             if rfall:
414                 spl = line.split()
415                 interfaces[name]['mac'] = spl[2]
416
417             # Carrier
418             rfall = re.findall(r'carrier', line)
419             if rfall:
420                 spl = line.split('carrier ')
421                 interfaces[name]['carrier'] = spl[1]
422
423             # Socket
424             rfall = re.findall(r'cpu socket', line)
425             if rfall:
426                 spl = line.split('cpu socket ')
427                 interfaces[name]['cpu socket'] = spl[1]
428
429             # Queues and Descriptors
430             rfall = re.findall(r'rx queues', line)
431             if rfall:
432                 spl = line.split(',')
433                 interfaces[name]['rx queues'] = spl[0].lstrip(' ').split(' ')[2]
434                 interfaces[name]['rx descs'] = spl[1].split(' ')[3]
435                 interfaces[name]['tx queues'] = spl[2].split(' ')[3]
436                 interfaces[name]['tx descs'] = spl[3].split(' ')[3]
437
438         return interfaces
439
440     def _get_installed_vpp_pkgs_ubuntu(self):
441         """
442         Get the VPP hardware information and return it in a
443         dictionary
444
445         :returns: List of the packages installed
446         :rtype: list
447         """
448
449         pkgs = []
450         cmd = 'dpkg -l | grep vpp'
451         (ret, stdout, stderr) = self.exec_command(cmd)
452         if ret != 0:
453             return pkgs
454
455         lines = stdout.split('\n')
456         for line in lines:
457             items = line.split()
458             if len(items) < 2:
459                 continue
460             pkg = {'name': items[1], 'version': items[2]}
461             pkgs.append(pkg)
462
463         return pkgs
464
465     def _get_installed_vpp_pkgs_centos(self):
466         """
467         Get the VPP hardware information and return it in a
468         dictionary
469
470         :returns: List of the packages installed
471         :rtype: list
472         """
473
474         pkgs = []
475         cmd = 'rpm -qa | grep vpp'
476         (ret, stdout, stderr) = self.exec_command(cmd)
477         if ret != 0:
478             return pkgs
479
480         lines = stdout.split('\n')
481         for line in lines:
482             if len(line) == 0:
483                 continue
484
485             items = line.split()
486             if len(items) < 2:
487                 pkg = {'name': items[0]}
488             else:
489                 pkg = {'name': items[1], 'version': items[2]}
490
491             pkgs.append(pkg)
492
493         return pkgs
494
495     def get_installed_vpp_pkgs(self):
496         """
497         Get the VPP hardware information and return it in a
498         dictionary
499
500         :returns: List of the packages installed
501         :rtype: list
502         """
503
504         distro = self.get_linux_distro()
505         if distro[0] == 'Ubuntu':
506             pkgs = self._get_installed_vpp_pkgs_ubuntu()
507         elif distro[0] == 'CentOS Linux':
508             pkgs = self._get_installed_vpp_pkgs_centos()
509         else:
510             return []
511
512         return pkgs
513
514     @staticmethod
515     def get_interfaces_numa_node(node, *iface_keys):
516         """Get numa node on which are located most of the interfaces.
517
518         Return numa node with highest count of interfaces provided as arguments.
519         Return 0 if the interface does not have numa_node information available.
520         If all interfaces have unknown location (-1), then return 0.
521         If most of interfaces have unknown location (-1), but there are
522         some interfaces with known location, then return the second most
523         location of the provided interfaces.
524
525         :param node: Node from DICT__nodes.
526         :param iface_keys: Interface keys for lookup.
527         :type node: dict
528         :type iface_keys: strings
529         """
530         numa_list = []
531         for if_key in iface_keys:
532             try:
533                 numa_list.append(node['interfaces'][if_key].get('numa_node'))
534             except KeyError:
535                 pass
536
537         numa_cnt_mc = Counter(numa_list).most_common()
538         numa_cnt_mc_len = len(numa_cnt_mc)
539         if numa_cnt_mc_len > 0 and numa_cnt_mc[0][0] != -1:
540             return numa_cnt_mc[0][0]
541         elif numa_cnt_mc_len > 1 and numa_cnt_mc[0][0] == -1:
542             return numa_cnt_mc[1][0]
543
544         return 0
545
546     @staticmethod
547     def start(node):
548         """
549
550         Starts vpp for a given node
551
552         :param node: VPP node.
553         :type node: dict
554         """
555
556         cmd = 'service vpp start'
557         (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
558         if ret != 0:
559             raise RuntimeError('{} failed on node {} {} {}'.
560                                format(cmd, node['host'],
561                                       stdout, stderr))
562
563     @staticmethod
564     def stop(node):
565         """
566
567         Stops vpp for a given node
568
569         :param node: VPP node.
570         :type node: dict
571         """
572
573         cmd = 'service vpp stop'
574         (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
575         if ret != 0:
576             raise RuntimeError('{} failed on node {} {} {}'.
577                                format(cmd, node['host'],
578                                       stdout, stderr))
579
580     @staticmethod
581     def status(node):
582         """
583
584         Gets VPP status
585
586         :param: node
587         :type node: dict
588         :returns: status, errors
589         :rtype: tuple(str, list)
590         """
591         errors = []
592         vutil = VPPUtil()
593         pkgs = vutil.get_installed_vpp_pkgs()
594         if len(pkgs) == 0:
595             return "Not Installed", errors
596
597         cmd = 'service vpp status'
598         (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
599
600         # Get the active status
601         state = re.findall(r'Active:[\w (\)]+', stdout)[0].split(' ')
602         if len(state) > 2:
603             statestr = "{} {}".format(state[1], state[2])
604         else:
605             statestr = "Invalid"
606
607         # For now we won't look for DPDK errors
608         # lines = stdout.split('\n')
609         # for line in lines:
610         #    if 'EAL' in line or \
611         #                     'FAILURE' in line or \
612         #                     'failed' in line or \
613         #                     'Failed' in line:
614         #         errors.append(line.lstrip(' '))
615
616         return statestr, errors
617
618     @staticmethod
619     def get_linux_distro():
620         """
621         Get the linux distribution and check if it is supported
622
623         :returns: linux distro, None if the distro is not supported
624         :rtype: list
625         """
626
627         distro = platform.linux_distribution()
628         if distro[0] == 'Ubuntu' or \
629                         distro[0] == 'CentOS Linux' or \
630                         distro[:26] == 'Linux Distribution Red Hat':
631             return distro
632         else:
633             raise RuntimeError('Linux Distribution {} is not supported'.format(distro[0]))
634
635     @staticmethod
636     def version():
637         """
638
639         Gets VPP Version information
640
641         :returns: version
642         :rtype: dict
643         """
644
645         version = {}
646         cmd = 'vppctl show version verbose'
647         (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
648         if ret != 0:
649             return version
650
651         lines = stdout.split('\n')
652         if len(lines[0]) is not 0:
653             if lines[0].split(' ')[0] == 'FileNotFoundError':
654                 return version
655
656         for line in lines:
657             if len(line) is 0:
658                 continue
659             dct = line.split(':')
660             version[dct[0]] = dct[1].lstrip(' ')
661
662         return version