vpp_config: correct usage of 'is' for equality tests.
[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 from __future__ import print_function
15
16 """VPP util library"""
17 import logging
18 import re
19 import subprocess
20 import platform
21 import requests
22
23 from collections import Counter
24
25 ubuntu_pkgs = {'release': ['vpp', 'vpp-plugins', 'vpp-api-java', 'vpp-api-lua', 'vpp-api-python',
26                            'vpp-dbg', 'vpp-dev'],
27                'master': ['vpp', 'vpp-plugin-core', 'vpp-api-python',
28                           'vpp-dbg', 'vpp-dev', 'vpp-plugin-dpdk']}
29
30 centos_pkgs = {'release': ['vpp', 'vpp-plugins', 'vpp-api-java', 'vpp-api-lua',
31                            'vpp-api-python', 'vpp-debuginfo', 'vpp-devel', 'libvpp0'],
32                'master': ['vpp', 'vpp-plugins', 'vpp-api-java', 'vpp-api-lua',
33                           'vpp-api-python', 'vpp-debuginfo', 'vpp-devel', 'libvpp0']}
34
35
36 class VPPUtil(object):
37     """General class for any VPP related methods/functions."""
38
39     @staticmethod
40     def exec_command(cmd, timeout=None):
41         """Execute a command on the local node.
42
43         :param cmd: Command to run locally.
44         :param timeout: Timeout value
45         :type cmd: str
46         :type timeout: int
47         :return return_code, stdout, stderr
48         :rtype: tuple(int, str, str)
49         """
50
51         logging.info(" Local Command: {}".format(cmd))
52         out = ''
53         err = ''
54         prc = subprocess.Popen(cmd, shell=True, bufsize=1,
55                                stdin=subprocess.PIPE,
56                                stdout=subprocess.PIPE,
57                                stderr=subprocess.PIPE)
58
59         with prc.stdout:
60             lines = prc.stdout.readlines()
61             for line in lines:
62                 if type(line) != str:
63                     line = line.decode()
64                 logging.info("  {}".format(line.strip('\n')))
65                 out += line
66
67         with prc.stderr:
68             lines = prc.stderr.readlines()
69             for line in lines:
70                 if type(line) != str:
71                     line = line.decode()
72                 logging.warning("  {}".format(line.strip('\n')))
73                 err += line
74
75         ret = prc.wait()
76
77         return ret, out, err
78
79     def _autoconfig_backup_file(self, filename):
80         """
81         Create a backup file.
82
83         :param filename: The file to backup
84         :type filename: str
85         """
86
87         # Does a copy of the file exist, if not create one
88         ofile = filename + '.orig'
89         (ret, stdout, stderr) = self.exec_command('ls {}'.format(ofile))
90         if ret != 0:
91             logging.debug(stderr)
92             if stdout.strip('\n') != ofile:
93                 cmd = 'sudo cp {} {}'.format(filename, ofile)
94                 (ret, stdout, stderr) = self.exec_command(cmd)
95                 if ret != 0:
96                     logging.debug(stderr)
97
98     def _install_vpp_ubuntu(self, node, branch, ubuntu_version='xenial'):
99         """
100         Install the VPP packages
101
102         :param node: Node dictionary with cpuinfo.
103         :param branch: VPP branch
104         :param ubuntu_version: Ubuntu Version
105         :type node: dict
106         :type branch: string
107         :type ubuntu_version: string
108         """
109
110         # Modify the sources list
111         sfile = '/etc/apt/sources.list.d/99fd.io.list'
112
113         # Backup the sources list
114         self._autoconfig_backup_file(sfile)
115
116         reps = 'deb [trusted=yes] https://packagecloud.io/fdio/'
117         reps += '{}/ubuntu {} main\n'.format(branch, ubuntu_version)
118
119         with open(sfile, 'w') as sfd:
120             sfd.write(reps)
121             sfd.close()
122
123         # Add the key
124
125         key = requests.get(
126             'https://packagecloud.io/fdio/{}/gpgkey'.format(branch))
127         cmd = 'echo "{}" | apt-key add -'.format(key.content.decode(key.encoding))
128         (ret, stdout, stderr) = self.exec_command(cmd)
129         if ret != 0:
130             raise RuntimeError('{} failed on node {} {}'.format(
131                 cmd,
132                 node['host'],
133                 stderr))
134
135         # Install the package
136         cmd = 'apt-get -y update'
137         (ret, stdout, stderr) = self.exec_command(cmd)
138         if ret != 0:
139             raise RuntimeError('{} apt-get update failed on node {} {}'.format(
140                 cmd,
141                 node['host'],
142                 stderr))
143
144         # Get the package list
145         pkgstr = ''
146         for ps in ubuntu_pkgs[branch]:
147             pkgstr += ps + ' '
148
149         cmd = 'apt-get -y install {}'.format(pkgstr)
150         (ret, stdout, stderr) = self.exec_command(cmd)
151         if ret != 0:
152             raise RuntimeError('{} failed on node {} {} {}'.format(
153                 cmd, node['host'], stdout, stderr))
154
155     def _install_vpp_centos(self, node, branch):
156         """
157         Install the VPP packages
158
159         :param node: Node dictionary with cpuinfo.
160         :param branch: The branch name  release or master
161         :type node: dict
162         :type branch: string
163         """
164
165         # Be sure the correct system packages are installed
166         cmd = 'yum -y update'
167         (ret, stdout, stderr) = self.exec_command(cmd)
168         if ret != 0:
169             logging.debug('{} failed on node {} {}'.format(
170                 cmd,
171                 node['host'],
172                 stderr))
173
174         cmd = 'yum -y install pygpgme yum-utils'
175         (ret, stdout, stderr) = self.exec_command(cmd)
176         if ret != 0:
177             logging.debug('{} failed on node {} {}'.format(
178                 cmd,
179                 node['host'],
180                 stderr))
181
182         # Modify the sources list
183         sfile = '/etc/yum.repos.d/fdio-release.repo'
184
185         # Backup the sources list
186         self._autoconfig_backup_file(sfile)
187
188         # Remove the current file
189         cmd = 'rm {}'.format(sfile)
190         (ret, stdout, stderr) = self.exec_command(cmd)
191         if ret != 0:
192             logging.debug('{} failed on node {} {}'.format(
193                 cmd,
194                 node['host'],
195                 stderr))
196
197         # Get the file contents
198
199         reps = '\n'.join([
200             '[fdio_{}]'.format(branch),
201             'name=fdio_{}'.format(branch),
202             'baseurl=https://packagecloud.io/fdio/{}/el/7/$basearch'.format(
203                 branch),
204             'repo_gpgcheck=1',
205             'gpgcheck=0',
206             'enabled=1',
207             'gpgkey=https://packagecloud.io/fdio/{}/gpgkey'.format(branch),
208             'sslverify=1',
209             'sslcacert=/etc/pki/tls/certs/ca-bundle.crt',
210             'metadata_expire=300\n',
211             '[fdio_{}-source]'.format(branch),
212             'name=fdio_release-{}'.format(branch),
213             'baseurl=https://packagecloud.io/fdio/{}/el/7/SRPMS'.format(
214                 branch),
215             'repo_gpgcheck=1',
216             'gpgcheck=0',
217             'enabled=1',
218             'gpgkey=https://packagecloud.io/fdio/{}/gpgkey'.format(branch),
219             'sslverify =1',
220             'sslcacert=/etc/pki/tls/certs/ca-bundle.crt',
221             'metadata_expire=300\n'
222         ])
223         with open(sfile, 'w') as sfd:
224             sfd.write(reps)
225             sfd.close()
226
227         # Update the fdio repo
228         cmd = 'yum clean all'
229         (ret, stdout, stderr) = self.exec_command(cmd)
230         if ret != 0:
231             logging.debug('{} failed on node {} {}'.format(
232                 cmd,
233                 node['host'],
234                 stderr))
235
236         cmd = "yum -q makecache -y --disablerepo='*' " \
237               "--enablerepo='fdio_{}'".format(branch)
238         (ret, stdout, stderr) = self.exec_command(cmd)
239         if ret != 0:
240             logging.debug('{} failed on node {} {}'.format(
241                 cmd,
242                 node['host'],
243                 stderr))
244
245         # Get the package list
246         pkgstr = ''
247         for ps in centos_pkgs[branch]:
248             pkgstr += ps + ' '
249
250         cmd = 'yum -y install {}'.format(pkgstr)
251         (ret, stdout, stderr) = self.exec_command(cmd)
252         if ret != 0:
253             raise RuntimeError('{} failed on node {} {} {}'.format(
254                 cmd, node['host'], stdout, stderr))
255
256     def install_vpp(self, node, branch):
257         """
258         Install the VPP packages
259
260         :param node: Node dictionary with cpuinfo.
261         :param branch: The branch name
262         :type node: dict
263         :type branch: string
264
265         """
266         distro = self.get_linux_distro()
267         logging.info("  {}".format(distro[0]))
268         if distro[0] == 'Ubuntu':
269             logging.info("Install Ubuntu")
270             self._install_vpp_ubuntu(node, branch, ubuntu_version=distro[2])
271         elif distro[0] == 'CentOS Linux':
272             logging.info("Install CentOS")
273             self._install_vpp_centos(node, branch)
274         else:
275             logging.info("Install CentOS (default)")
276             self._install_vpp_centos(node, branch)
277         return
278
279     def _uninstall_vpp_ubuntu(self, node):
280         """
281         Uninstall the VPP packages
282
283         :param node: Node dictionary with cpuinfo.
284         :type node: dict
285         """
286
287         # get the package list
288         pkgstr = ''
289         pkgs = self.get_installed_vpp_pkgs()
290         for pkg in pkgs:
291             pkgname = pkg['name']
292             pkgstr += pkgname + ' '
293
294         cmd = 'dpkg --purge {}'.format(pkgstr)
295         (ret, stdout, stderr) = self.exec_command(cmd)
296         if ret != 0:
297             raise RuntimeError('{} failed on node {} {} {}'.format(
298                 cmd, node['host'], stdout, stderr))
299
300     def _uninstall_vpp_centos(self, node):
301         """
302         Uninstall the VPP packages
303
304         :param node: Node dictionary with cpuinfo.
305         :type node: dict
306         """
307
308         pkgstr = ''
309         pkgs = self.get_installed_vpp_pkgs()
310         for pkg in pkgs:
311             pkgname = pkg['name']
312             pkgstr += pkgname + ' '
313
314         logging.info("Uninstalling {}".format(pkgstr))
315         cmd = 'yum -y remove {}'.format(pkgstr)
316         (ret, stdout, stderr) = self.exec_command(cmd)
317         if ret != 0:
318             raise RuntimeError('{} failed on node {} {} {}'.format(
319                 cmd, node['host'], stdout, stderr))
320
321     def uninstall_vpp(self, node):
322         """
323         Uninstall the VPP packages
324
325         :param node: Node dictionary with cpuinfo.
326         :type node: dict
327         """
328
329         # First stop VPP
330         self.stop(node)
331         distro = self.get_linux_distro()
332         if distro[0] == 'Ubuntu':
333             logging.info("Uninstall Ubuntu")
334             self._uninstall_vpp_ubuntu(node)
335         elif distro[0] == 'CentOS Linux':
336             logging.info("Uninstall CentOS")
337             self._uninstall_vpp_centos(node)
338         else:
339             logging.info("Uninstall CentOS (Default)")
340             self._uninstall_vpp_centos(node)
341             return
342
343     def show_vpp_settings(self, *additional_cmds):
344         """
345         Print default VPP settings. In case others are needed, can be
346         accepted as next parameters (each setting one parameter), preferably
347         in form of a string.
348
349         :param additional_cmds: Additional commands that the vpp should print
350         settings for.
351         :type additional_cmds: tuple
352         """
353         def_setting_tb_displayed = {
354             'IPv6 FIB': 'ip6 fib',
355             'IPv4 FIB': 'ip fib',
356             'Interface IP': 'int addr',
357             'Interfaces': 'int',
358             'ARP': 'ip arp',
359             'Errors': 'err'
360         }
361
362         if additional_cmds:
363             for cmd in additional_cmds:
364                 def_setting_tb_displayed['Custom Setting: {}'.format(cmd)] \
365                     = cmd
366
367                 for _, value in def_setting_tb_displayed.items():
368                     self.exec_command('vppctl sh {}'.format(value))
369
370     @staticmethod
371     def get_vms(node):
372         """
373         Get a list of VMs that are connected to VPP interfaces
374
375         :param node: VPP node.
376         :type node: dict
377         :returns: Dictionary containing a list of VMs and the interfaces
378                   that are connected to VPP
379         :rtype: dictionary
380         """
381
382         vmdict = {}
383
384         print("Need to implement get vms")
385
386         return vmdict
387
388     @staticmethod
389     def get_int_ip(node):
390         """
391         Get the VPP interfaces and IP addresses
392
393         :param node: VPP node.
394         :type node: dict
395         :returns: Dictionary containing VPP interfaces and IP addresses
396         :rtype: dictionary
397         """
398         interfaces = {}
399         cmd = 'vppctl show int addr'
400         (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
401         if ret != 0:
402             return interfaces
403
404         lines = stdout.split('\n')
405         if len(lines[0]) != 0:
406             if lines[0].split(' ')[0] == 'FileNotFoundError':
407                 return interfaces
408
409         name = ''
410         for line in lines:
411             if len(line) == 0:
412                 continue
413
414             # If the first character is not whitespace
415             # create a new interface
416             if len(re.findall(r'\s', line[0])) == 0:
417                 spl = line.split()
418                 name = spl[0]
419                 if name == 'local0':
420                     continue
421                 interfaces[name] = {}
422                 interfaces[name]['state'] = spl[1].lstrip('(').rstrip('):\r')
423             else:
424                 interfaces[name]['address'] = line.lstrip(' ').rstrip('\r')
425
426         return interfaces
427
428     @staticmethod
429     def get_hardware(node):
430         """
431         Get the VPP hardware information and return it in a
432         dictionary
433
434         :param node: VPP node.
435         :type node: dict
436         :returns: Dictionary containing VPP hardware information
437         :rtype: dictionary
438         """
439
440         interfaces = {}
441         cmd = 'vppctl show hard'
442         (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
443         if ret != 0:
444             return interfaces
445
446         lines = stdout.split('\n')
447         if len(lines[0]) != 0:
448             if lines[0].split(' ')[0] == 'FileNotFoundError':
449                 return interfaces
450
451         for line in lines:
452             if len(line) == 0:
453                 continue
454
455             # If the first character is not whitespace
456             # create a new interface
457             if len(re.findall(r'\s', line[0])) == 0:
458                 spl = line.split()
459                 name = spl[0]
460                 interfaces[name] = {}
461                 interfaces[name]['index'] = spl[1]
462                 interfaces[name]['state'] = spl[2]
463
464             # Ethernet address
465             rfall = re.findall(r'Ethernet address', line)
466             if rfall:
467                 spl = line.split()
468                 interfaces[name]['mac'] = spl[2]
469
470             # Carrier
471             rfall = re.findall(r'carrier', line)
472             if rfall:
473                 spl = line.split('carrier ')
474                 interfaces[name]['carrier'] = spl[1]
475
476             # Socket
477             spl = ''
478             rfall = re.findall(r'numa \d+', line)
479             if rfall:
480                 spl = rfall[0].split()
481                 interfaces[name]['numa'] = rfall[0].split()[1]
482
483             # Queues and Descriptors
484             rfall = re.findall(r'rx\: queues \d+', line)
485             if rfall:
486                 interfaces[name]['rx queues'] = rfall[0].split()[2]
487                 rdesc = re.findall(r'desc \d+', line)
488                 if rdesc:
489                     interfaces[name]['rx descs'] = rdesc[0].split()[1]
490
491             rfall = re.findall(r'tx\: queues \d+', line)
492             if rfall:
493                 interfaces[name]['tx queues'] = rfall[0].split()[2]
494                 rdesc = re.findall(r'desc \d+', line)
495                 if rdesc:
496                     interfaces[name]['tx descs'] = rdesc[0].split()[1]
497
498         return interfaces
499
500     def _get_installed_vpp_pkgs_ubuntu(self):
501         """
502         Get the VPP hardware information and return it in a
503         dictionary
504
505         :returns: List of the packages installed
506         :rtype: list
507         """
508
509         pkgs = []
510         cmd = 'dpkg -l | grep vpp'
511         (ret, stdout, stderr) = self.exec_command(cmd)
512         if ret != 0:
513             return pkgs
514
515         lines = stdout.split('\n')
516         for line in lines:
517             items = line.split()
518             if len(items) < 2:
519                 continue
520             pkg = {'name': items[1], 'version': items[2]}
521             pkgs.append(pkg)
522
523         return pkgs
524
525     def _get_installed_vpp_pkgs_centos(self):
526         """
527         Get the VPP hardware information and return it in a
528         dictionary
529
530         :returns: List of the packages installed
531         :rtype: list
532         """
533
534         pkgs = []
535         cmd = 'rpm -qa | grep vpp'
536         (ret, stdout, stderr) = self.exec_command(cmd)
537         if ret != 0:
538             return pkgs
539
540         lines = stdout.split('\n')
541         for line in lines:
542             if len(line) == 0:
543                 continue
544
545             items = line.split()
546             if len(items) < 2:
547                 pkg = {'name': items[0]}
548             else:
549                 pkg = {'name': items[1], 'version': items[2]}
550
551             pkgs.append(pkg)
552
553         return pkgs
554
555     def get_installed_vpp_pkgs(self):
556         """
557         Get the VPP hardware information and return it in a
558         dictionary
559
560         :returns: List of the packages installed
561         :rtype: list
562         """
563
564         distro = self.get_linux_distro()
565         if distro[0] == 'Ubuntu':
566             pkgs = self._get_installed_vpp_pkgs_ubuntu()
567         elif distro[0] == 'CentOS Linux':
568             pkgs = self._get_installed_vpp_pkgs_centos()
569         else:
570             pkgs = self._get_installed_vpp_pkgs_centos()
571             return []
572
573         return pkgs
574
575     @staticmethod
576     def get_interfaces_numa_node(node, *iface_keys):
577         """Get numa node on which are located most of the interfaces.
578
579         Return numa node with highest count of interfaces provided as
580         arguments.
581         Return 0 if the interface does not have numa_node information
582         available.
583         If all interfaces have unknown location (-1), then return 0.
584         If most of interfaces have unknown location (-1), but there are
585         some interfaces with known location, then return the second most
586         location of the provided interfaces.
587
588         :param node: Node from DICT__nodes.
589         :param iface_keys: Interface keys for lookup.
590         :type node: dict
591         :type iface_keys: strings
592         """
593         numa_list = []
594         for if_key in iface_keys:
595             try:
596                 numa_list.append(node['interfaces'][if_key].get('numa_node'))
597             except KeyError:
598                 pass
599
600         numa_cnt_mc = Counter(numa_list).most_common()
601         numa_cnt_mc_len = len(numa_cnt_mc)
602         if numa_cnt_mc_len > 0 and numa_cnt_mc[0][0] != -1:
603             return numa_cnt_mc[0][0]
604         elif numa_cnt_mc_len > 1 and numa_cnt_mc[0][0] == -1:
605             return numa_cnt_mc[1][0]
606
607         return 0
608
609     @staticmethod
610     def restart(node):
611         """
612
613         Starts vpp for a given node
614
615         :param node: VPP node.
616         :type node: dict
617         """
618
619         cmd = 'service vpp restart'
620         (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
621         if ret != 0:
622             raise RuntimeError('{} failed on node {} {} {}'.
623                                format(cmd, node['host'],
624                                       stdout, stderr))
625
626     @staticmethod
627     def start(node):
628         """
629
630         Starts vpp for a given node
631
632         :param node: VPP node.
633         :type node: dict
634         """
635
636         cmd = 'service vpp start'
637         (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
638         if ret != 0:
639             raise RuntimeError('{} failed on node {} {} {}'.
640                                format(cmd, node['host'],
641                                       stdout, stderr))
642
643     @staticmethod
644     def stop(node):
645         """
646
647         Stops vpp for a given node
648
649         :param node: VPP node.
650         :type node: dict
651         """
652
653         cmd = 'service vpp stop'
654         (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
655         if ret != 0:
656             logging.debug('{} failed on node {} {} {}'.
657                           format(cmd, node['host'],
658                                  stdout, stderr))
659
660     # noinspection RegExpRedundantEscape
661     @staticmethod
662     def status(node):
663         """
664
665         Gets VPP status
666
667         :param: node
668         :type node: dict
669         :returns: status, errors
670         :rtype: tuple(str, list)
671         """
672         errors = []
673         vutil = VPPUtil()
674         pkgs = vutil.get_installed_vpp_pkgs()
675         if len(pkgs) == 0:
676             return "Not Installed", errors
677
678         cmd = 'service vpp status'
679         (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
680
681         # Get the active status
682         state = re.findall(r'Active:[\w (\)]+', stdout)[0].split(' ')
683         if len(state) > 2:
684             statestr = "{} {}".format(state[1], state[2])
685         else:
686             statestr = "Invalid"
687
688         # For now we won't look for DPDK errors
689         # lines = stdout.split('\n')
690         # for line in lines:
691         #    if 'EAL' in line or \
692         #                     'FAILURE' in line or \
693         #                     'failed' in line or \
694         #                     'Failed' in line:
695         #         errors.append(line.lstrip(' '))
696
697         return statestr, errors
698
699     @staticmethod
700     def get_linux_distro():
701         """
702         Get the linux distribution and check if it is supported
703
704         :returns: linux distro, None if the distro is not supported
705         :rtype: list
706         """
707
708         distro = platform.linux_distribution()
709         if distro[0] == 'Ubuntu' or \
710                 distro[0] == 'CentOS Linux' or \
711                 distro[:7] == 'Red Hat':
712             return distro
713         else:
714             raise RuntimeError(
715                 'Linux Distribution {} is not supported'.format(distro[0]))
716
717     @staticmethod
718     def version():
719         """
720
721         Gets VPP Version information
722
723         :returns: version
724         :rtype: dict
725         """
726
727         version = {}
728         cmd = 'vppctl show version verbose'
729         (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
730         if ret != 0:
731             return version
732
733         lines = stdout.split('\n')
734         if len(lines[0]) != 0:
735             if lines[0].split(' ')[0] == 'FileNotFoundError':
736                 return version
737
738         for line in lines:
739             if len(line) == 0:
740                 continue
741             dct = line.split(':')
742             version[dct[0]] = dct[1].lstrip(' ')
743
744         return version
745
746     @staticmethod
747     def show_bridge(node):
748         """
749         Shows the current bridge configuration
750
751         :param node: VPP node.
752         :type node: dict
753         :returns: A list of interfaces
754         """
755
756         ifaces = []
757         cmd = 'vppctl show bridge'
758         (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
759         if ret != 0:
760             raise RuntimeError('{} failed on node {} {} {}'.
761                                format(cmd, node['host'],
762                                       stdout, stderr))
763         lines = stdout.split('\r\n')
764         bridges = []
765         for line in lines:
766             if line == 'no bridge-domains in use':
767                 print(line)
768                 return ifaces
769             if len(line) == 0:
770                 continue
771
772             lspl = line.lstrip(' ').split()
773             if lspl[0] != 'BD-ID':
774                 bridges.append(lspl[0])
775
776         for bridge in bridges:
777             cmd = 'vppctl show bridge {} detail'.format(bridge)
778             (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
779             if ret != 0:
780                 raise RuntimeError('{} failed on node {} {} {}'.
781                                    format(cmd, node['host'],
782                                           stdout, stderr))
783
784         lines = stdout.split('\r\n')
785         for line in lines:
786             iface = re.findall(r'[a-zA-z]+\d+/\d+/\d+', line)
787             if len(iface):
788                 ifcidx = {'name': iface[0], 'index': line.split()[1]}
789                 ifaces.append(ifcidx)
790
791         print(stdout)
792         return ifaces