Initial commit for phase 2, Add some simple validation.
[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_int_ip(node):
377         """
378         Get the VPP interfaces and IP addresses
379
380         :param node: VPP node.
381         :type node: dict
382         :returns: Dictionary containing VPP interfaces and IP addresses
383         :rtype: dictionary
384         """
385         interfaces = {}
386         cmd = 'vppctl show int addr'
387         (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
388         if ret != 0:
389             return interfaces
390
391         lines = stdout.split('\n')
392         if len(lines[0]) is not 0:
393             if lines[0].split(' ')[0] == 'FileNotFoundError':
394                 return interfaces
395
396         for line in lines:
397             if len(line) is 0:
398                 continue
399
400             # If the first character is not whitespace
401             # create a new interface
402             if len(re.findall(r'\s', line[0])) is 0:
403                 spl = line.split()
404                 name = spl[0]
405                 if name == 'local0':
406                     continue
407                 interfaces[name] = {}
408                 interfaces[name]['state'] = spl[1].lstrip('(').rstrip('):\r')
409             else:
410                 interfaces[name]['address'] = line.lstrip(' ').rstrip('\r')
411
412         return interfaces
413
414     @staticmethod
415     def get_hardware(node):
416         """
417         Get the VPP hardware information and return it in a
418         dictionary
419
420         :param node: VPP node.
421         :type node: dict
422         :returns: Dictionary containing VPP hardware information
423         :rtype: dictionary
424         """
425
426         interfaces = {}
427         cmd = 'vppctl show hard'
428         (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
429         if ret != 0:
430             return interfaces
431
432         lines = stdout.split('\n')
433         if len(lines[0]) is not 0:
434             if lines[0].split(' ')[0] == 'FileNotFoundError':
435                 return interfaces
436
437         for line in lines:
438             if len(line) is 0:
439                 continue
440
441             # If the first character is not whitespace
442             # create a new interface
443             if len(re.findall(r'\s', line[0])) is 0:
444                 spl = line.split()
445                 name = spl[0]
446                 interfaces[name] = {}
447                 interfaces[name]['index'] = spl[1]
448                 interfaces[name]['state'] = spl[2]
449
450             # Ethernet address
451             rfall = re.findall(r'Ethernet address', line)
452             if rfall:
453                 spl = line.split()
454                 interfaces[name]['mac'] = spl[2]
455
456             # Carrier
457             rfall = re.findall(r'carrier', line)
458             if rfall:
459                 spl = line.split('carrier ')
460                 interfaces[name]['carrier'] = spl[1]
461
462             # Socket
463             rfall = re.findall(r'cpu socket', line)
464             if rfall:
465                 spl = line.split('cpu socket ')
466                 interfaces[name]['cpu socket'] = spl[1]
467
468             # Queues and Descriptors
469             rfall = re.findall(r'rx queues', line)
470             if rfall:
471                 spl = line.split(',')
472                 interfaces[name]['rx queues'] = spl[0].lstrip(' ').split(' ')[2]
473                 interfaces[name]['rx descs'] = spl[1].split(' ')[3]
474                 interfaces[name]['tx queues'] = spl[2].split(' ')[3]
475                 interfaces[name]['tx descs'] = spl[3].split(' ')[3]
476
477         return interfaces
478
479     def _get_installed_vpp_pkgs_ubuntu(self):
480         """
481         Get the VPP hardware information and return it in a
482         dictionary
483
484         :returns: List of the packages installed
485         :rtype: list
486         """
487
488         pkgs = []
489         cmd = 'dpkg -l | grep vpp'
490         (ret, stdout, stderr) = self.exec_command(cmd)
491         if ret != 0:
492             return pkgs
493
494         lines = stdout.split('\n')
495         for line in lines:
496             items = line.split()
497             if len(items) < 2:
498                 continue
499             pkg = {'name': items[1], 'version': items[2]}
500             pkgs.append(pkg)
501
502         return pkgs
503
504     def _get_installed_vpp_pkgs_centos(self):
505         """
506         Get the VPP hardware information and return it in a
507         dictionary
508
509         :returns: List of the packages installed
510         :rtype: list
511         """
512
513         pkgs = []
514         cmd = 'rpm -qa | grep vpp'
515         (ret, stdout, stderr) = self.exec_command(cmd)
516         if ret != 0:
517             return pkgs
518
519         lines = stdout.split('\n')
520         for line in lines:
521             if len(line) == 0:
522                 continue
523
524             items = line.split()
525             if len(items) < 2:
526                 pkg = {'name': items[0]}
527             else:
528                 pkg = {'name': items[1], 'version': items[2]}
529
530             pkgs.append(pkg)
531
532         return pkgs
533
534     def get_installed_vpp_pkgs(self):
535         """
536         Get the VPP hardware information and return it in a
537         dictionary
538
539         :returns: List of the packages installed
540         :rtype: list
541         """
542
543         distro = self.get_linux_distro()
544         if distro[0] == 'Ubuntu':
545             pkgs = self._get_installed_vpp_pkgs_ubuntu()
546         elif distro[0] == 'CentOS Linux':
547             pkgs = self._get_installed_vpp_pkgs_centos()
548         else:
549             return []
550
551         return pkgs
552
553     @staticmethod
554     def get_interfaces_numa_node(node, *iface_keys):
555         """Get numa node on which are located most of the interfaces.
556
557         Return numa node with highest count of interfaces provided as arguments.
558         Return 0 if the interface does not have numa_node information available.
559         If all interfaces have unknown location (-1), then return 0.
560         If most of interfaces have unknown location (-1), but there are
561         some interfaces with known location, then return the second most
562         location of the provided interfaces.
563
564         :param node: Node from DICT__nodes.
565         :param iface_keys: Interface keys for lookup.
566         :type node: dict
567         :type iface_keys: strings
568         """
569         numa_list = []
570         for if_key in iface_keys:
571             try:
572                 numa_list.append(node['interfaces'][if_key].get('numa_node'))
573             except KeyError:
574                 pass
575
576         numa_cnt_mc = Counter(numa_list).most_common()
577         numa_cnt_mc_len = len(numa_cnt_mc)
578         if numa_cnt_mc_len > 0 and numa_cnt_mc[0][0] != -1:
579             return numa_cnt_mc[0][0]
580         elif numa_cnt_mc_len > 1 and numa_cnt_mc[0][0] == -1:
581             return numa_cnt_mc[1][0]
582
583         return 0
584
585     @staticmethod
586     def start(node):
587         """
588
589         Starts vpp for a given node
590
591         :param node: VPP node.
592         :type node: dict
593         """
594
595         cmd = 'service vpp start'
596         (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
597         if ret != 0:
598             raise RuntimeError('{} failed on node {} {} {}'.
599                                format(cmd, node['host'],
600                                       stdout, stderr))
601
602     @staticmethod
603     def stop(node):
604         """
605
606         Stops vpp for a given node
607
608         :param node: VPP node.
609         :type node: dict
610         """
611
612         cmd = 'service vpp stop'
613         (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
614         if ret != 0:
615             raise RuntimeError('{} failed on node {} {} {}'.
616                                format(cmd, node['host'],
617                                       stdout, stderr))
618
619     @staticmethod
620     def status(node):
621         """
622
623         Gets VPP status
624
625         :param: node
626         :type node: dict
627         :returns: status, errors
628         :rtype: tuple(str, list)
629         """
630         errors = []
631         vutil = VPPUtil()
632         pkgs = vutil.get_installed_vpp_pkgs()
633         if len(pkgs) == 0:
634             return "Not Installed", errors
635
636         cmd = 'service vpp status'
637         (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
638
639         # Get the active status
640         state = re.findall(r'Active:[\w (\)]+', stdout)[0].split(' ')
641         if len(state) > 2:
642             statestr = "{} {}".format(state[1], state[2])
643         else:
644             statestr = "Invalid"
645
646         # For now we won't look for DPDK errors
647         # lines = stdout.split('\n')
648         # for line in lines:
649         #    if 'EAL' in line or \
650         #                     'FAILURE' in line or \
651         #                     'failed' in line or \
652         #                     'Failed' in line:
653         #         errors.append(line.lstrip(' '))
654
655         return statestr, errors
656
657     @staticmethod
658     def get_linux_distro():
659         """
660         Get the linux distribution and check if it is supported
661
662         :returns: linux distro, None if the distro is not supported
663         :rtype: list
664         """
665
666         distro = platform.linux_distribution()
667         if distro[0] == 'Ubuntu' or \
668                         distro[0] == 'CentOS Linux' or \
669                         distro[:26] == 'Linux Distribution Red Hat':
670             return distro
671         else:
672             raise RuntimeError('Linux Distribution {} is not supported'.format(distro[0]))
673
674     @staticmethod
675     def version():
676         """
677
678         Gets VPP Version information
679
680         :returns: version
681         :rtype: dict
682         """
683
684         version = {}
685         cmd = 'vppctl show version verbose'
686         (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
687         if ret != 0:
688             return version
689
690         lines = stdout.split('\n')
691         if len(lines[0]) is not 0:
692             if lines[0].split(' ')[0] == 'FileNotFoundError':
693                 return version
694
695         for line in lines:
696             if len(line) is 0:
697                 continue
698             dct = line.split(':')
699             version[dct[0]] = dct[1].lstrip(' ')
700
701         return version