blob: f042e80bd8f3c2d4e4778041935d6f732a71c4f5 [file] [log] [blame]
John DeNisco68b0ee32017-09-27 16:35:23 -04001# 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"""
15import logging
16import re
17import subprocess
18import platform
19
20from collections import Counter
21
22# VPP_VERSION = '1707'
23VPP_VERSION = '1710'
24
25
26class 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
John DeNiscoa3db0782017-10-17 11:07:22 -0400376 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
John DeNisco68b0ee32017-09-27 16:35:23 -0400415 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
John DeNiscoa3db0782017-10-17 11:07:22 -0400422 :returns: Dictionary containing VPP hardware information
John DeNisco68b0ee32017-09-27 16:35:23 -0400423 :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