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