Initial commit for phase 2, Add some simple validation.
[vpp.git] / extras / vpp_config / vpplib / AutoConfig.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 """Library that supports Auto Configuration."""
15
16 import logging
17 import os
18 import re
19 import yaml
20 import ipaddress
21
22 from vpplib.VPPUtil import VPPUtil
23 from vpplib.VppPCIUtil import VppPCIUtil
24 from vpplib.VppHugePageUtil import VppHugePageUtil
25 from vpplib.CpuUtils import CpuUtils
26 from vpplib.VppGrubUtil import VppGrubUtil
27 from vpplib.QemuUtils import QemuUtils
28
29 __all__ = ["AutoConfig"]
30
31 # Constants
32 MIN_SYSTEM_CPUS = 2
33 MIN_TOTAL_HUGE_PAGES = 1024
34 MAX_PERCENT_FOR_HUGE_PAGES = 70
35
36
37 class AutoConfig(object):
38     """Auto Configuration Tools"""
39
40     def __init__(self, rootdir, filename):
41         """
42         The Auto Configure class.
43
44         :param rootdir: The root directory for all the auto configuration files
45         :param filename: The autoconfiguration file
46         :type rootdir: str
47         :type filename: str
48         """
49         self._autoconfig_filename = rootdir + filename
50         self._rootdir = rootdir
51         self._metadata = {}
52         self._nodes = {}
53         self._vpp_devices_node = {}
54         self._hugepage_config = ""
55         self._loadconfig()
56
57     def get_nodes(self):
58         """
59         Returns the nodes dictionary.
60
61         :returns: The nodes
62         :rtype: dictionary
63         """
64
65         return self._nodes
66
67     @staticmethod
68     def _autoconfig_backup_file(filename):
69         """
70         Create a backup file.
71
72         :param filename: The file to backup
73         :type filename: str
74         """
75
76         # Does a copy of the file exist, if not create one
77         ofile = filename + '.orig'
78         (ret, stdout, stderr) = VPPUtil.exec_command('ls {}'.format(ofile))
79         if ret != 0:
80             logging.debug(stderr)
81             if stdout.strip('\n') != ofile:
82                 cmd = 'sudo cp {} {}'.format(filename, ofile)
83                 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
84                 if ret != 0:
85                     logging.debug(stderr)
86
87     @staticmethod
88     def _ask_user_ipv4():
89         """
90         Asks the user for a number within a range.
91         default is returned if return is entered.
92
93         :returns: IP address and prefix len
94         :rtype: tuple
95         """
96
97         while True:
98             answer = raw_input("Please enter the IPv4 Address [n.n.n.n]: ")
99             try:
100                 ipaddr = ipaddress.ip_address(u'{}'.format(answer))
101             except:
102                 print "Please enter a valid IPv4 address."
103                 continue
104
105             answer = raw_input("Please enter the netmask [n.n.n.n]: ")
106             try:
107                 netmask = ipaddress.ip_address(u'{}'.format(answer))
108                 pl = ipaddress.ip_network(u'0.0.0.0/{}'.format(netmask))
109                 plen = pl.exploded.split('/')[1]
110                 break
111             except:
112                 print "Please enter a valid IPv4 address and netmask."
113                 continue
114
115         return ipaddr, plen
116
117     @staticmethod
118     def _ask_user_range(question, first, last, default):
119         """
120         Asks the user for a number within a range.
121         default is returned if return is entered.
122
123         :param question: Text of a question.
124         :param first: First number in the range
125         :param last: Last number in the range
126         :param default: The value returned when return is entered
127         :type question: string
128         :type first: int
129         :type last: int
130         :type default: int
131         :returns: The answer to the question
132         :rtype: int
133         """
134
135         while True:
136             answer = raw_input(question)
137             if answer == '':
138                 answer = default
139                 break
140             if re.findall(r'[0-9+]', answer):
141                 if int(answer) in range(first, last + 1):
142                     break
143                 else:
144                     print "Please a value between {} and {} or Return.". \
145                         format(first, last)
146             else:
147                 print "Please a number between {} and {} or Return.". \
148                     format(first, last)
149
150         return int(answer)
151
152     @staticmethod
153     def _ask_user_yn(question, default):
154         """
155         Asks the user for a yes or no question.
156
157         :param question: Text of a question.
158         :param default: The value returned when return is entered
159         :type question: string
160         :type default: string
161         :returns: The answer to the question
162         :rtype: string
163         """
164
165         input_valid = False
166         default = default.lower()
167         answer = ''
168         while not input_valid:
169             answer = raw_input(question)
170             if answer == '':
171                 answer = default
172             if re.findall(r'[YyNn]', answer):
173                 input_valid = True
174                 answer = answer[0].lower()
175             else:
176                 print "Please answer Y, N or Return."
177
178         return answer
179
180     def _loadconfig(self):
181         """
182         Load the testbed configuration, given the auto configuration file.
183
184         """
185
186         # Get the Topology, from the topology layout file
187         topo = {}
188         with open(self._autoconfig_filename, 'r') as stream:
189             try:
190                 topo = yaml.load(stream)
191                 if 'metadata' in topo:
192                     self._metadata = topo['metadata']
193             except yaml.YAMLError as exc:
194                 raise RuntimeError("Couldn't read the Auto config file {}.".format(self._autoconfig_filename, exc))
195
196         systemfile = self._rootdir + self._metadata['system_config_file']
197         if os.path.isfile(systemfile):
198             with open(systemfile, 'r') as sysstream:
199                 try:
200                     systopo = yaml.load(sysstream)
201                     if 'nodes' in systopo:
202                         self._nodes = systopo['nodes']
203                 except yaml.YAMLError as sysexc:
204                     raise RuntimeError("Couldn't read the System config file {}.".format(systemfile, sysexc))
205         else:
206             # Get the nodes from Auto Config
207             if 'nodes' in topo:
208                 self._nodes = topo['nodes']
209
210         # Set the root directory in all the nodes
211         for i in self._nodes.items():
212             node = i[1]
213             node['rootdir'] = self._rootdir
214
215     def updateconfig(self):
216         """
217         Update the testbed configuration, given the auto configuration file.
218         We will write the system configuration file with the current node
219         information
220
221         """
222
223         # Initialize the yaml data
224         ydata = {'metadata': self._metadata, 'nodes': self._nodes}
225
226         # Write the system config file
227         filename = self._rootdir + self._metadata['system_config_file']
228         with open(filename, 'w') as yamlfile:
229             yaml.dump(ydata, yamlfile, default_flow_style=False)
230
231     def _update_auto_config(self):
232         """
233         Write the auto configuration file with the new configuration data,
234         input from the user.
235
236         """
237
238         # Initialize the yaml data
239         nodes = {}
240         with open(self._autoconfig_filename, 'r') as stream:
241             try:
242                 ydata = yaml.load(stream)
243                 if 'nodes' in ydata:
244                     nodes = ydata['nodes']
245             except yaml.YAMLError as exc:
246                 print exc
247                 return
248
249         for i in nodes.items():
250             key = i[0]
251             node = i[1]
252
253             # Interfaces
254             node['interfaces'] = {}
255             for item in self._nodes[key]['interfaces'].items():
256                 port = item[0]
257                 interface = item[1]
258
259                 node['interfaces'][port] = {}
260                 node['interfaces'][port]['pci_address'] = \
261                     interface['pci_address']
262                 if 'mac_address' in interface:
263                     node['interfaces'][port]['mac_address'] = \
264                         interface['mac_address']
265
266             if 'total_other_cpus' in self._nodes[key]['cpu']:
267                 node['cpu']['total_other_cpus'] = \
268                     self._nodes[key]['cpu']['total_other_cpus']
269             if 'total_vpp_cpus' in self._nodes[key]['cpu']:
270                 node['cpu']['total_vpp_cpus'] = \
271                     self._nodes[key]['cpu']['total_vpp_cpus']
272             if 'reserve_vpp_main_core' in self._nodes[key]['cpu']:
273                 node['cpu']['reserve_vpp_main_core'] = \
274                     self._nodes[key]['cpu']['reserve_vpp_main_core']
275
276             # TCP
277             if 'active_open_sessions' in self._nodes[key]['tcp']:
278                 node['tcp']['active_open_sessions'] = \
279                     self._nodes[key]['tcp']['active_open_sessions']
280             if 'passive_open_sessions' in self._nodes[key]['tcp']:
281                 node['tcp']['passive_open_sessions'] = \
282                     self._nodes[key]['tcp']['passive_open_sessions']
283
284             # Huge pages
285             node['hugepages']['total'] = self._nodes[key]['hugepages']['total']
286
287         # Write the auto config config file
288         with open(self._autoconfig_filename, 'w') as yamlfile:
289             yaml.dump(ydata, yamlfile, default_flow_style=False)
290
291     def apply_huge_pages(self):
292         """
293         Apply the huge page config
294
295         """
296
297         for i in self._nodes.items():
298             node = i[1]
299
300             hpg = VppHugePageUtil(node)
301             hpg.hugepages_dryrun_apply()
302
303     @staticmethod
304     def _apply_vpp_unix(node):
305         """
306         Apply the VPP Unix config
307
308         :param node: Node dictionary with cpuinfo.
309         :type node: dict
310         """
311
312         unix = '  nodaemon\n'
313         if 'unix' not in node['vpp']:
314             return ''
315
316         unixv = node['vpp']['unix']
317         if 'interactive' in unixv:
318             interactive = unixv['interactive']
319             if interactive is True:
320                 unix = '  interactive\n'
321
322         return unix.rstrip('\n')
323
324     @staticmethod
325     def _apply_vpp_cpu(node):
326         """
327         Apply the VPP cpu config
328
329         :param node: Node dictionary with cpuinfo.
330         :type node: dict
331         """
332
333         # Get main core
334         cpu = '\n'
335         vpp_main_core = node['cpu']['vpp_main_core']
336         if vpp_main_core is not 0:
337             cpu += '  main-core {}\n'.format(vpp_main_core)
338
339         # Get workers
340         vpp_workers = node['cpu']['vpp_workers']
341         vpp_worker_len = len(vpp_workers)
342         if vpp_worker_len > 0:
343             vpp_worker_str = ''
344             for i, worker in enumerate(vpp_workers):
345                 if i > 0:
346                     vpp_worker_str += ','
347                 if worker[0] == worker[1]:
348                     vpp_worker_str += "{}".format(worker[0])
349                 else:
350                     vpp_worker_str += "{}-{}".format(worker[0], worker[1])
351
352             cpu += '  corelist-workers {}\n'.format(vpp_worker_str)
353
354         return cpu
355
356     @staticmethod
357     def _apply_vpp_devices(node):
358         """
359         Apply VPP PCI Device configuration to vpp startup.
360
361         :param node: Node dictionary with cpuinfo.
362         :type node: dict
363         """
364
365         devices = ''
366         ports_per_numa = node['cpu']['ports_per_numa']
367         total_mbufs = node['cpu']['total_mbufs']
368
369         for item in ports_per_numa.items():
370             value = item[1]
371             interfaces = value['interfaces']
372
373             # if 0 was specified for the number of vpp workers, use 1 queue
374             num_rx_queues = None
375             num_tx_queues = None
376             if 'rx_queues' in value:
377                 num_rx_queues = value['rx_queues']
378             if 'tx_queues' in value:
379                 num_tx_queues = value['tx_queues']
380
381             num_rx_desc = None
382             num_tx_desc = None
383
384             # Create the devices string
385             for interface in interfaces:
386                 pci_address = interface['pci_address']
387                 pci_address = pci_address.lstrip("'").rstrip("'")
388                 devices += '\n'
389                 devices += '  dev {} {{ \n'.format(pci_address)
390                 if num_rx_queues:
391                     devices += '    num-rx-queues {}\n'.format(num_rx_queues)
392                 else:
393                     devices += '    num-rx-queues {}\n'.format(1)
394                 if num_tx_queues:
395                     devices += '    num-tx-queues {}\n'.format(num_tx_queues)
396                 if num_rx_desc:
397                     devices += '    num-rx-desc {}\n'.format(num_rx_desc)
398                 if num_tx_desc:
399                     devices += '    num-tx-desc {}\n'.format(num_tx_desc)
400                 devices += '  }'
401
402         if total_mbufs is not 0:
403             devices += '\n  num-mbufs {}'.format(total_mbufs)
404
405         return devices
406
407     @staticmethod
408     def _calc_vpp_workers(node, vpp_workers, numa_node,
409                           other_cpus_end, total_vpp_workers,
410                           reserve_vpp_main_core):
411         """
412         Calculate the VPP worker information
413
414         :param node: Node dictionary
415         :param vpp_workers: List of VPP workers
416         :param numa_node: Numa node
417         :param other_cpus_end: The end of the cpus allocated for cores
418         other than vpp
419         :param total_vpp_workers: The number of vpp workers needed
420         :param reserve_vpp_main_core: Is there a core needed for
421         the vpp main core
422         :type node: dict
423         :type numa_node: int
424         :type other_cpus_end: int
425         :type total_vpp_workers: int
426         :type reserve_vpp_main_core: bool
427         :returns: Is a core still needed for the vpp main core
428         :rtype: bool
429         """
430
431         # Can we fit the workers in one of these slices
432         cpus = node['cpu']['cpus_per_node'][numa_node]
433         for cpu in cpus:
434             start = cpu[0]
435             end = cpu[1]
436             if start <= other_cpus_end:
437                 start = other_cpus_end + 1
438
439             if reserve_vpp_main_core:
440                 start += 1
441
442             workers_end = start + total_vpp_workers - 1
443             if workers_end <= end:
444                 if reserve_vpp_main_core:
445                     node['cpu']['vpp_main_core'] = start - 1
446                 reserve_vpp_main_core = False
447                 if total_vpp_workers:
448                     vpp_workers.append((start, workers_end))
449                 break
450
451         # We still need to reserve the main core
452         if reserve_vpp_main_core:
453             node['cpu']['vpp_main_core'] = other_cpus_end + 1
454
455         return reserve_vpp_main_core
456
457     @staticmethod
458     def _calc_desc_and_queues(total_numa_nodes,
459                               total_ports_per_numa,
460                               total_vpp_cpus,
461                               ports_per_numa_value):
462         """
463         Calculate the number of descriptors and queues
464
465         :param total_numa_nodes: The total number of numa nodes
466         :param total_ports_per_numa: The total number of ports for this
467         numa node
468         :param total_vpp_cpus: The total number of cpus to allocate for vpp
469         :param ports_per_numa_value: The value from the ports_per_numa
470         dictionary
471         :type total_numa_nodes: int
472         :type total_ports_per_numa: int
473         :type total_vpp_cpus: int
474         :type ports_per_numa_value: dict
475         :returns The total number of message buffers
476         :returns: The total number of vpp workers
477         :rtype: int
478         :rtype: int
479         """
480
481         # Get the total vpp workers
482         total_vpp_workers = total_vpp_cpus
483         ports_per_numa_value['total_vpp_workers'] = total_vpp_workers
484
485         # Get the number of rx queues
486         rx_queues = max(1, total_vpp_workers)
487         tx_queues = total_vpp_workers * total_numa_nodes + 1
488
489         # Get the descriptor entries
490         desc_entries = 1024
491         ports_per_numa_value['rx_queues'] = rx_queues
492         total_mbufs = (((rx_queues * desc_entries) +
493                         (tx_queues * desc_entries)) *
494                        total_ports_per_numa)
495         total_mbufs = total_mbufs
496
497         return total_mbufs, total_vpp_workers
498
499     @staticmethod
500     def _create_ports_per_numa(node, interfaces):
501         """
502         Create a dictionary or ports per numa node
503         :param node: Node dictionary
504         :param interfaces: All the interfaces to be used by vpp
505         :type node: dict
506         :type interfaces: dict
507         :returns: The ports per numa dictionary
508         :rtype: dict
509         """
510
511         # Make a list of ports by numa node
512         ports_per_numa = {}
513         for item in interfaces.items():
514             i = item[1]
515             if i['numa_node'] not in ports_per_numa:
516                 ports_per_numa[i['numa_node']] = {'interfaces': []}
517                 ports_per_numa[i['numa_node']]['interfaces'].append(i)
518             else:
519                 ports_per_numa[i['numa_node']]['interfaces'].append(i)
520         node['cpu']['ports_per_numa'] = ports_per_numa
521
522         return ports_per_numa
523
524     def calculate_cpu_parameters(self):
525         """
526         Calculate the cpu configuration.
527
528         """
529
530         # Calculate the cpu parameters, needed for the
531         # vpp_startup and grub configuration
532         for i in self._nodes.items():
533             node = i[1]
534
535             # get total number of nic ports
536             interfaces = node['interfaces']
537
538             # Make a list of ports by numa node
539             ports_per_numa = self._create_ports_per_numa(node, interfaces)
540
541             # Get the number of cpus to skip, we never use the first cpu
542             other_cpus_start = 1
543             other_cpus_end = other_cpus_start + \
544                 node['cpu']['total_other_cpus'] - 1
545             other_workers = None
546             if other_cpus_end is not 0:
547                 other_workers = (other_cpus_start, other_cpus_end)
548             node['cpu']['other_workers'] = other_workers
549
550             # Allocate the VPP main core and workers
551             vpp_workers = []
552             reserve_vpp_main_core = node['cpu']['reserve_vpp_main_core']
553             total_vpp_cpus = node['cpu']['total_vpp_cpus']
554
555             # If total_vpp_cpus is 0 or is less than the numa nodes with ports
556             #  then we shouldn't get workers
557             total_with_main = total_vpp_cpus
558             if reserve_vpp_main_core:
559                 total_with_main += 1
560             total_mbufs = 0
561             if total_with_main is not 0:
562                 for item in ports_per_numa.items():
563                     numa_node = item[0]
564                     value = item[1]
565
566                     # Get the number of descriptors and queues
567                     mbufs, total_vpp_workers = self._calc_desc_and_queues(
568                         len(ports_per_numa),
569                         len(value['interfaces']), total_vpp_cpus, value)
570                     total_mbufs += mbufs
571
572                     # Get the VPP workers
573                     reserve_vpp_main_core = self._calc_vpp_workers(
574                         node, vpp_workers, numa_node, other_cpus_end,
575                         total_vpp_workers, reserve_vpp_main_core)
576
577                 total_mbufs *= 2.5
578                 total_mbufs = int(total_mbufs)
579             else:
580                 total_mbufs = 0
581
582             # Save the info
583             node['cpu']['vpp_workers'] = vpp_workers
584             node['cpu']['total_mbufs'] = total_mbufs
585
586         # Write the config
587         self.updateconfig()
588
589     @staticmethod
590     def _apply_vpp_tcp(node):
591         """
592         Apply the VPP Unix config
593
594         :param node: Node dictionary with cpuinfo.
595         :type node: dict
596         """
597
598         active_open_sessions = node['tcp']['active_open_sessions']
599         aos = int(active_open_sessions)
600
601         passive_open_sessions = node['tcp']['passive_open_sessions']
602         pos = int(passive_open_sessions)
603
604         # Generate the api-segment gid vpp sheit in any case
605         if (aos + pos) == 0:
606             tcp = "api-segment {\n"
607             tcp = tcp + "  gid vpp\n"
608             tcp = tcp + "}\n"
609             return tcp.rstrip('\n')
610
611         tcp = "# TCP stack-related configuration parameters\n"
612         tcp = tcp + "# expecting {:d} client sessions, {:d} server sessions\n\n".format(aos, pos)
613         tcp = tcp + "heapsize 4g\n\n"
614         tcp = tcp + "api-segment {\n"
615         tcp = tcp + "  global-size 2000M\n"
616         tcp = tcp + "  api-size 1G\n"
617         tcp = tcp + "}\n\n"
618
619         tcp = tcp + "session {\n"
620         tcp = tcp + "  event-queue-length " + "{:d}".format(aos + pos) + "\n"
621         tcp = tcp + "  preallocated-sessions " + "{:d}".format(aos + pos) + "\n"
622         tcp = tcp + "  v4-session-table-buckets " + "{:d}".format((aos + pos) / 4) + "\n"
623         tcp = tcp + "  v4-session-table-memory 3g\n"
624         if aos > 0:
625             tcp = tcp + "  v4-halfopen-table-buckets " + \
626                   "{:d}".format((aos + pos) / 4) + "\n"
627             tcp = tcp + "  v4-halfopen-table-memory 3g\n"
628         tcp = tcp + "}\n\n"
629
630         tcp = tcp + "tcp {\n"
631         tcp = tcp + "  preallocated-connections " + "{:d}".format(aos + pos) + "\n"
632         if aos > 0:
633             tcp = tcp + "  preallocated-half-open-connections " + "{:d}".format(aos) + "\n"
634             tcp = tcp + "  local-endpoints-table-buckets " + "{:d}".format((aos + pos) / 4) + "\n"
635             tcp = tcp + "  local-endpoints-table-memory 3g\n"
636         tcp = tcp + "}\n\n"
637
638         return tcp.rstrip('\n')
639
640     def apply_vpp_startup(self):
641         """
642         Apply the vpp startup configration
643
644         """
645
646         # Apply the VPP startup configruation
647         for i in self._nodes.items():
648             node = i[1]
649
650             # Get the startup file
651             rootdir = node['rootdir']
652             sfile = rootdir + node['vpp']['startup_config_file']
653
654             # Get the devices
655             devices = self._apply_vpp_devices(node)
656
657             # Get the CPU config
658             cpu = self._apply_vpp_cpu(node)
659
660             # Get the unix config
661             unix = self._apply_vpp_unix(node)
662
663             # Get the TCP configuration, if any
664             tcp = self._apply_vpp_tcp(node)
665
666             # Make a backup if needed
667             self._autoconfig_backup_file(sfile)
668
669             # Get the template
670             tfile = sfile + '.template'
671             (ret, stdout, stderr) = \
672                 VPPUtil.exec_command('cat {}'.format(tfile))
673             if ret != 0:
674                 raise RuntimeError('Executing cat command failed to node {}'.
675                                    format(node['host']))
676             startup = stdout.format(unix=unix,
677                                     cpu=cpu,
678                                     devices=devices,
679                                     tcp=tcp)
680
681             (ret, stdout, stderr) = \
682                 VPPUtil.exec_command('rm {}'.format(sfile))
683             if ret != 0:
684                 logging.debug(stderr)
685
686             cmd = "sudo cat > {0} << EOF\n{1}\n".format(sfile, startup)
687             (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
688             if ret != 0:
689                 raise RuntimeError('Writing config failed node {}'.
690                                    format(node['host']))
691
692     def apply_grub_cmdline(self):
693         """
694         Apply the grub cmdline
695
696         """
697
698         for i in self._nodes.items():
699             node = i[1]
700
701             # Get the isolated CPUs
702             other_workers = node['cpu']['other_workers']
703             vpp_workers = node['cpu']['vpp_workers']
704             vpp_main_core = node['cpu']['vpp_main_core']
705             all_workers = []
706             if other_workers is not None:
707                 all_workers = [other_workers]
708             if vpp_main_core is not 0:
709                 all_workers += [(vpp_main_core, vpp_main_core)]
710             all_workers += vpp_workers
711             isolated_cpus = ''
712             for idx, worker in enumerate(all_workers):
713                 if worker is None:
714                     continue
715                 if idx > 0:
716                     isolated_cpus += ','
717                 if worker[0] == worker[1]:
718                     isolated_cpus += "{}".format(worker[0])
719                 else:
720                     isolated_cpus += "{}-{}".format(worker[0], worker[1])
721
722             vppgrb = VppGrubUtil(node)
723             current_cmdline = vppgrb.get_current_cmdline()
724             if 'grub' not in node:
725                 node['grub'] = {}
726             node['grub']['current_cmdline'] = current_cmdline
727             node['grub']['default_cmdline'] = \
728                 vppgrb.apply_cmdline(node, isolated_cpus)
729
730         self.updateconfig()
731
732     def get_hugepages(self):
733         """
734         Get the hugepage configuration
735
736         """
737
738         for i in self._nodes.items():
739             node = i[1]
740
741             hpg = VppHugePageUtil(node)
742             max_map_count, shmmax = hpg.get_huge_page_config()
743             node['hugepages']['max_map_count'] = max_map_count
744             node['hugepages']['shmax'] = shmmax
745             total, free, size, memtotal, memfree = hpg.get_actual_huge_pages()
746             node['hugepages']['actual_total'] = total
747             node['hugepages']['free'] = free
748             node['hugepages']['size'] = size
749             node['hugepages']['memtotal'] = memtotal
750             node['hugepages']['memfree'] = memfree
751
752         self.updateconfig()
753
754     def get_grub(self):
755         """
756         Get the grub configuration
757
758         """
759
760         for i in self._nodes.items():
761             node = i[1]
762
763             vppgrb = VppGrubUtil(node)
764             current_cmdline = vppgrb.get_current_cmdline()
765             default_cmdline = vppgrb.get_default_cmdline()
766
767             # Get the total number of isolated CPUs
768             current_iso_cpus = 0
769             iso_cpur = re.findall(r'isolcpus=[\w+\-,]+', current_cmdline)
770             iso_cpurl = len(iso_cpur)
771             if iso_cpurl > 0:
772                 iso_cpu_str = iso_cpur[0]
773                 iso_cpu_str = iso_cpu_str.split('=')[1]
774                 iso_cpul = iso_cpu_str.split(',')
775                 for iso_cpu in iso_cpul:
776                     isocpuspl = iso_cpu.split('-')
777                     if len(isocpuspl) is 1:
778                         current_iso_cpus += 1
779                     else:
780                         first = int(isocpuspl[0])
781                         second = int(isocpuspl[1])
782                         if first == second:
783                             current_iso_cpus += 1
784                         else:
785                             current_iso_cpus += second - first
786
787             if 'grub' not in node:
788                 node['grub'] = {}
789             node['grub']['current_cmdline'] = current_cmdline
790             node['grub']['default_cmdline'] = default_cmdline
791             node['grub']['current_iso_cpus'] = current_iso_cpus
792
793         self.updateconfig()
794
795     @staticmethod
796     def _get_device(node):
797         """
798         Get the device configuration for a single node
799
800         :param node: Node dictionary with cpuinfo.
801         :type node: dict
802
803         """
804
805         vpp = VppPCIUtil(node)
806         vpp.get_all_devices()
807
808         # Save the device information
809         node['devices'] = {}
810         node['devices']['dpdk_devices'] = vpp.get_dpdk_devices()
811         node['devices']['kernel_devices'] = vpp.get_kernel_devices()
812         node['devices']['other_devices'] = vpp.get_other_devices()
813         node['devices']['linkup_devices'] = vpp.get_link_up_devices()
814
815     def get_devices_per_node(self):
816         """
817         Get the device configuration for all the nodes
818
819         """
820
821         for i in self._nodes.items():
822             node = i[1]
823             # Update the interface data
824
825             self._get_device(node)
826
827         self.updateconfig()
828
829     @staticmethod
830     def get_cpu_layout(node):
831         """
832         Get the cpu layout
833
834         using lscpu -p get the cpu layout.
835         Returns a list with each item representing a single cpu.
836
837         :param node: Node dictionary.
838         :type node: dict
839         :returns: The cpu layout
840         :rtype: list
841         """
842
843         cmd = 'lscpu -p'
844         (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
845         if ret != 0:
846             raise RuntimeError('{} failed on node {} {}'.
847                                format(cmd, node['host'], stderr))
848
849         pcpus = []
850         lines = stdout.split('\n')
851         for line in lines:
852             if line == '' or line[0] == '#':
853                 continue
854             linesplit = line.split(',')
855             layout = {'cpu': linesplit[0], 'core': linesplit[1],
856                       'socket': linesplit[2], 'node': linesplit[3]}
857
858             # cpu, core, socket, node
859             pcpus.append(layout)
860
861         return pcpus
862
863     def get_cpu(self):
864         """
865         Get the cpu configuration
866
867         """
868
869         # Get the CPU layout
870         CpuUtils.get_cpu_layout_from_all_nodes(self._nodes)
871
872         for i in self._nodes.items():
873             node = i[1]
874
875             # Get the cpu layout
876             layout = self.get_cpu_layout(node)
877             node['cpu']['layout'] = layout
878
879             cpuinfo = node['cpuinfo']
880             smt_enabled = CpuUtils.is_smt_enabled(cpuinfo)
881             node['cpu']['smt_enabled'] = smt_enabled
882
883             # We don't want to write the cpuinfo
884             node['cpuinfo'] = ""
885
886         # Write the config
887         self.updateconfig()
888
889     def discover(self):
890         """
891         Get the current system configuration.
892
893         """
894
895         # Get the Huge Page configuration
896         self.get_hugepages()
897
898         # Get the device configuration
899         self.get_devices_per_node()
900
901         # Get the CPU configuration
902         self.get_cpu()
903
904         # Get the current grub cmdline
905         self.get_grub()
906
907     def _modify_cpu_questions(self, node, total_cpus, numa_nodes):
908         """
909         Ask the user questions related to the cpu configuration.
910
911         :param node: Node dictionary
912         :param total_cpus: The total number of cpus in the system
913         :param numa_nodes: The list of numa nodes in the system
914         :type node: dict
915         :type total_cpus: int
916         :type numa_nodes: list
917         """
918
919         print "\nYour system has {} core(s) and {} Numa Nodes.". \
920             format(total_cpus, len(numa_nodes))
921         print "To begin, we suggest not reserving any cores for VPP",
922         print "or other processes."
923         print "Then to improve performance try reserving cores as needed. "
924
925         max_other_cores = total_cpus / 2
926         question = '\nHow many core(s) do you want to reserve for processes \
927 other than VPP? [0-{}][0]? '.format(str(max_other_cores))
928         total_other_cpus = self._ask_user_range(question, 0, max_other_cores,
929                                                 0)
930         node['cpu']['total_other_cpus'] = total_other_cpus
931
932         max_vpp_cpus = 4
933         total_vpp_cpus = 0
934         if max_vpp_cpus > 0:
935             question = "How many core(s) shall we reserve for VPP workers[0-{}][0]? ". \
936                 format(max_vpp_cpus)
937             total_vpp_cpus = self._ask_user_range(question, 0, max_vpp_cpus, 0)
938             node['cpu']['total_vpp_cpus'] = total_vpp_cpus
939
940         max_main_cpus = max_vpp_cpus - total_vpp_cpus
941         reserve_vpp_main_core = False
942         if max_main_cpus > 0:
943             question = "Should we reserve 1 core for the VPP Main thread? "
944             question += "[y/N]? "
945             answer = self._ask_user_yn(question, 'n')
946             if answer == 'y':
947                 reserve_vpp_main_core = True
948             node['cpu']['reserve_vpp_main_core'] = reserve_vpp_main_core
949             node['cpu']['vpp_main_core'] = 0
950
951     def modify_cpu(self):
952         """
953         Modify the cpu configuration, asking for the user for the values.
954
955         """
956
957         # Get the CPU layout
958         CpuUtils.get_cpu_layout_from_all_nodes(self._nodes)
959
960         for i in self._nodes.items():
961             node = i[1]
962             total_cpus = 0
963             total_cpus_per_slice = 0
964             cpus_per_node = {}
965             numa_nodes = []
966             cores = []
967             cpu_layout = self.get_cpu_layout(node)
968
969             # Assume the number of cpus per slice is always the same as the
970             # first slice
971             first_node = '0'
972             for cpu in cpu_layout:
973                 if cpu['node'] != first_node:
974                     break
975                 total_cpus_per_slice += 1
976
977             # Get the total number of cpus, cores, and numa nodes from the
978             # cpu layout
979             for cpul in cpu_layout:
980                 numa_node = cpul['node']
981                 core = cpul['core']
982                 cpu = cpul['cpu']
983                 total_cpus += 1
984
985                 if numa_node not in cpus_per_node:
986                     cpus_per_node[numa_node] = []
987                 cpuperslice = int(cpu) % total_cpus_per_slice
988                 if cpuperslice == 0:
989                     cpus_per_node[numa_node].append((int(cpu), int(cpu) +
990                                                      total_cpus_per_slice - 1))
991                 if numa_node not in numa_nodes:
992                     numa_nodes.append(numa_node)
993                 if core not in cores:
994                     cores.append(core)
995             node['cpu']['cpus_per_node'] = cpus_per_node
996
997             # Ask the user some questions
998             self._modify_cpu_questions(node, total_cpus, numa_nodes)
999
1000             # Populate the interfaces with the numa node
1001             ikeys = node['interfaces'].keys()
1002             VPPUtil.get_interfaces_numa_node(node, *tuple(ikeys))
1003
1004             # We don't want to write the cpuinfo
1005             node['cpuinfo'] = ""
1006
1007         # Write the configs
1008         self._update_auto_config()
1009         self.updateconfig()
1010
1011     def _modify_other_devices(self, node,
1012                               other_devices, kernel_devices, dpdk_devices):
1013         """
1014         Modify the devices configuration, asking for the user for the values.
1015
1016         """
1017
1018         odevices_len = len(other_devices)
1019         if odevices_len > 0:
1020             print "\nThese device(s) are currently NOT being used",
1021             print "by VPP or the OS.\n"
1022             VppPCIUtil.show_vpp_devices(other_devices, show_interfaces=False)
1023             question = "\nWould you like to give any of these devices"
1024             question += " back to the OS [Y/n]? "
1025             answer = self._ask_user_yn(question, 'Y')
1026             if answer == 'y':
1027                 vppd = {}
1028                 for dit in other_devices.items():
1029                     dvid = dit[0]
1030                     device = dit[1]
1031                     question = "Would you like to use device {} for". \
1032                         format(dvid)
1033                     question += " the OS [y/N]? "
1034                     answer = self._ask_user_yn(question, 'n')
1035                     if answer == 'y':
1036                         driver = device['unused'][0]
1037                         VppPCIUtil.bind_vpp_device(node, driver, dvid)
1038                         vppd[dvid] = device
1039                 for dit in vppd.items():
1040                     dvid = dit[0]
1041                     device = dit[1]
1042                     kernel_devices[dvid] = device
1043                     del other_devices[dvid]
1044
1045         odevices_len = len(other_devices)
1046         if odevices_len > 0:
1047             print "\nThese device(s) are still NOT being used ",
1048             print "by VPP or the OS.\n"
1049             VppPCIUtil.show_vpp_devices(other_devices, show_interfaces=False)
1050             question = "\nWould you like use any of these for VPP [y/N]? "
1051             answer = self._ask_user_yn(question, 'N')
1052             if answer == 'y':
1053                 vppd = {}
1054                 for dit in other_devices.items():
1055                     dvid = dit[0]
1056                     device = dit[1]
1057                     question = "Would you like to use device {} ".format(dvid)
1058                     question += "for VPP [y/N]? "
1059                     answer = self._ask_user_yn(question, 'n')
1060                     if answer == 'y':
1061                         vppd[dvid] = device
1062                 for dit in vppd.items():
1063                     dvid = dit[0]
1064                     device = dit[1]
1065                     dpdk_devices[dvid] = device
1066                     del other_devices[dvid]
1067
1068     def modify_devices(self):
1069         """
1070         Modify the devices configuration, asking for the user for the values.
1071
1072         """
1073
1074         for i in self._nodes.items():
1075             node = i[1]
1076             devices = node['devices']
1077             other_devices = devices['other_devices']
1078             kernel_devices = devices['kernel_devices']
1079             dpdk_devices = devices['dpdk_devices']
1080
1081             if other_devices:
1082                 self._modify_other_devices(node, other_devices,
1083                                            kernel_devices, dpdk_devices)
1084
1085                 # Get the devices again for this node
1086                 self._get_device(node)
1087                 devices = node['devices']
1088                 kernel_devices = devices['kernel_devices']
1089                 dpdk_devices = devices['dpdk_devices']
1090
1091             klen = len(kernel_devices)
1092             if klen > 0:
1093                 print "\nThese devices have kernel interfaces, but",
1094                 print "appear to be safe to use with VPP.\n"
1095                 VppPCIUtil.show_vpp_devices(kernel_devices)
1096                 question = "\nWould you like to use any of these "
1097                 question += "device(s) for VPP [y/N]? "
1098                 answer = self._ask_user_yn(question, 'n')
1099                 if answer == 'y':
1100                     vppd = {}
1101                     for dit in kernel_devices.items():
1102                         dvid = dit[0]
1103                         device = dit[1]
1104                         question = "Would you like to use device {} ". \
1105                             format(dvid)
1106                         question += "for VPP [y/N]? "
1107                         answer = self._ask_user_yn(question, 'n')
1108                         if answer == 'y':
1109                             vppd[dvid] = device
1110                     for dit in vppd.items():
1111                         dvid = dit[0]
1112                         device = dit[1]
1113                         dpdk_devices[dvid] = device
1114                         del kernel_devices[dvid]
1115
1116             dlen = len(dpdk_devices)
1117             if dlen > 0:
1118                 print "\nThese device(s) will be used by VPP.\n"
1119                 VppPCIUtil.show_vpp_devices(dpdk_devices, show_interfaces=False)
1120                 question = "\nWould you like to remove any of "
1121                 question += "these device(s) [y/N]? "
1122                 answer = self._ask_user_yn(question, 'n')
1123                 if answer == 'y':
1124                     vppd = {}
1125                     for dit in dpdk_devices.items():
1126                         dvid = dit[0]
1127                         device = dit[1]
1128                         question = "Would you like to remove {} [y/N]? ". \
1129                             format(dvid)
1130                         answer = self._ask_user_yn(question, 'n')
1131                         if answer == 'y':
1132                             vppd[dvid] = device
1133                     for dit in vppd.items():
1134                         dvid = dit[0]
1135                         device = dit[1]
1136                         driver = device['unused'][0]
1137                         VppPCIUtil.bind_vpp_device(node, driver, dvid)
1138                         kernel_devices[dvid] = device
1139                         del dpdk_devices[dvid]
1140
1141             interfaces = {}
1142             for dit in dpdk_devices.items():
1143                 dvid = dit[0]
1144                 device = dit[1]
1145                 VppPCIUtil.vpp_create_interface(interfaces, dvid, device)
1146             node['interfaces'] = interfaces
1147
1148             print "\nThese device(s) will be used by VPP, please",
1149             print "rerun this option if this is incorrect.\n"
1150             VppPCIUtil.show_vpp_devices(dpdk_devices, show_interfaces=False)
1151
1152         self._update_auto_config()
1153         self.updateconfig()
1154
1155     def modify_huge_pages(self):
1156         """
1157         Modify the huge page configuration, asking for the user for the values.
1158
1159         """
1160
1161         for i in self._nodes.items():
1162             node = i[1]
1163
1164             total = node['hugepages']['actual_total']
1165             free = node['hugepages']['free']
1166             size = node['hugepages']['size']
1167             memfree = node['hugepages']['memfree'].split(' ')[0]
1168             hugesize = int(size.split(' ')[0])
1169             # The max number of huge pages should be no more than
1170             # 70% of total free memory
1171             maxpages = (int(memfree) * MAX_PERCENT_FOR_HUGE_PAGES / 100) / hugesize
1172             print "\nThere currently {} {} huge pages free.". \
1173                 format(free, size)
1174             question = "Do you want to reconfigure the number of "
1175             question += "huge pages [y/N]? "
1176             answer = self._ask_user_yn(question, 'n')
1177             if answer == 'n':
1178                 node['hugepages']['total'] = total
1179                 continue
1180
1181             print "\nThere currently a total of {} huge pages.". \
1182                 format(total)
1183             question = \
1184                 "How many huge pages do you want [{} - {}][{}]? ". \
1185                 format(MIN_TOTAL_HUGE_PAGES, maxpages, MIN_TOTAL_HUGE_PAGES)
1186             answer = self._ask_user_range(question, 1024, maxpages, 1024)
1187             node['hugepages']['total'] = str(answer)
1188
1189         # Update auto-config.yaml
1190         self._update_auto_config()
1191
1192         # Rediscover just the hugepages
1193         self.get_hugepages()
1194
1195     def get_tcp_params(self):
1196         """
1197         Get the tcp configuration
1198
1199         """
1200         # maybe nothing to do here?
1201         self.updateconfig()
1202
1203     def acquire_tcp_params(self):
1204         """
1205         Ask the user for TCP stack configuration parameters
1206
1207         """
1208
1209         for i in self._nodes.items():
1210             node = i[1]
1211
1212             question = "\nHow many active-open / tcp client sessions are expected "
1213             question = question + "[0-10000000][0]? "
1214             answer = self._ask_user_range(question, 0, 10000000, 0)
1215             # Less than 10K is equivalent to 0
1216             if int(answer) < 10000:
1217                 answer = 0
1218             node['tcp']['active_open_sessions'] = answer
1219
1220             question = "How many passive-open / tcp server sessions are expected "
1221             question = question + "[0-10000000][0]? "
1222             answer = self._ask_user_range(question, 0, 10000000, 0)
1223             # Less than 10K is equivalent to 0
1224             if int(answer) < 10000:
1225                 answer = 0
1226             node['tcp']['passive_open_sessions'] = answer
1227
1228         # Update auto-config.yaml
1229         self._update_auto_config()
1230
1231         # Rediscover tcp parameters
1232         self.get_tcp_params()
1233
1234     @staticmethod
1235     def patch_qemu(node):
1236         """
1237         Patch qemu with the correct patches.
1238
1239         :param node: Node dictionary
1240         :type node: dict
1241         """
1242
1243         print '\nWe are patching the node "{}":\n'.format(node['host'])
1244         QemuUtils.build_qemu(node, force_install=True, apply_patch=True)
1245
1246     @staticmethod
1247     def cpu_info(node):
1248         """
1249         print the CPU information
1250
1251         """
1252
1253         cpu = CpuUtils.get_cpu_info_per_node(node)
1254
1255         item = 'Model name'
1256         if item in cpu:
1257             print "{:>20}:    {}".format(item, cpu[item])
1258         item = 'CPU(s)'
1259         if item in cpu:
1260             print "{:>20}:    {}".format(item, cpu[item])
1261         item = 'Thread(s) per core'
1262         if item in cpu:
1263             print "{:>20}:    {}".format(item, cpu[item])
1264         item = 'Core(s) per socket'
1265         if item in cpu:
1266             print "{:>20}:    {}".format(item, cpu[item])
1267         item = 'Socket(s)'
1268         if item in cpu:
1269             print "{:>20}:    {}".format(item, cpu[item])
1270         item = 'NUMA node(s)'
1271         numa_nodes = 0
1272         if item in cpu:
1273             numa_nodes = int(cpu[item])
1274         for i in xrange(0, numa_nodes):
1275             item = "NUMA node{} CPU(s)".format(i)
1276             print "{:>20}:    {}".format(item, cpu[item])
1277         item = 'CPU max MHz'
1278         if item in cpu:
1279             print "{:>20}:    {}".format(item, cpu[item])
1280         item = 'CPU min MHz'
1281         if item in cpu:
1282             print "{:>20}:    {}".format(item, cpu[item])
1283
1284         if node['cpu']['smt_enabled']:
1285             smt = 'Enabled'
1286         else:
1287             smt = 'Disabled'
1288         print "{:>20}:    {}".format('SMT', smt)
1289
1290         # VPP Threads
1291         print "\nVPP Threads: (Name: Cpu Number)"
1292         vpp_processes = cpu['vpp_processes']
1293         for i in vpp_processes.items():
1294             print "  {:10}: {:4}".format(i[0], i[1])
1295
1296     @staticmethod
1297     def device_info(node):
1298         """
1299         Show the device information.
1300
1301         """
1302
1303         if 'cpu' in node and 'total_mbufs' in node['cpu']:
1304             total_mbufs = node['cpu']['total_mbufs']
1305             if total_mbufs is not 0:
1306                 print "Total Number of Buffers: {}".format(total_mbufs)
1307
1308         vpp = VppPCIUtil(node)
1309         vpp.get_all_devices()
1310         linkup_devs = vpp.get_link_up_devices()
1311         if len(linkup_devs):
1312             print ("\nDevices with link up (can not be used with VPP):")
1313             vpp.show_vpp_devices(linkup_devs, show_header=False)
1314             # for dev in linkup_devs:
1315             #    print ("    " + dev)
1316         kernel_devs = vpp.get_kernel_devices()
1317         if len(kernel_devs):
1318             print ("\nDevices bound to kernel drivers:")
1319             vpp.show_vpp_devices(kernel_devs, show_header=False)
1320         else:
1321             print ("\nNo devices bound to kernel drivers")
1322
1323         dpdk_devs = vpp.get_dpdk_devices()
1324         if len(dpdk_devs):
1325             print ("\nDevices bound to DPDK drivers:")
1326             vpp.show_vpp_devices(dpdk_devs, show_interfaces=True,
1327                                  show_header=False)
1328         else:
1329             print ("\nNo devices bound to DPDK drivers")
1330
1331         vpputl = VPPUtil()
1332         interfaces = vpputl.get_hardware(node)
1333         if interfaces == {}:
1334             return
1335
1336         print ("\nDevices in use by VPP:")
1337
1338         if len(interfaces.items()) < 2:
1339             print ("None")
1340             return
1341
1342         print "{:30} {:6} {:4} {:7} {:4} {:7}". \
1343             format('Name', 'Socket', 'RXQs',
1344                    'RXDescs', 'TXQs', 'TXDescs')
1345         for intf in sorted(interfaces.items()):
1346             name = intf[0]
1347             value = intf[1]
1348             if name == 'local0':
1349                 continue
1350             socket = rx_qs = rx_ds = tx_qs = tx_ds = ''
1351             if 'cpu socket' in value:
1352                 socket = int(value['cpu socket'])
1353             if 'rx queues' in value:
1354                 rx_qs = int(value['rx queues'])
1355             if 'rx descs' in value:
1356                 rx_ds = int(value['rx descs'])
1357             if 'tx queues' in value:
1358                 tx_qs = int(value['tx queues'])
1359             if 'tx descs' in value:
1360                 tx_ds = int(value['tx descs'])
1361
1362             print ("{:30} {:>6} {:>4} {:>7} {:>4} {:>7}".
1363                    format(name, socket, rx_qs, rx_ds, tx_qs, tx_ds))
1364
1365     @staticmethod
1366     def hugepage_info(node):
1367         """
1368         Show the huge page information.
1369
1370         """
1371
1372         hpg = VppHugePageUtil(node)
1373         hpg.show_huge_pages()
1374
1375     @staticmethod
1376     def min_system_resources(node):
1377         """
1378         Check the system for basic minimum resources, return true if
1379         there is enough.
1380
1381         :returns: boolean
1382         :rtype: dict
1383         """
1384
1385         min_sys_res = True
1386
1387         # CPUs
1388         if 'layout' in node['cpu']:
1389             total_cpus = len(node['cpu']['layout'])
1390             if total_cpus < 2:
1391                 print "\nThere is only {} CPU(s) available on this system.".format(total_cpus)
1392                 print "This is not enough to run VPP."
1393                 min_sys_res = False
1394
1395         # System Memory
1396         if 'free' in node['hugepages'] and \
1397                 'memfree' in node['hugepages'] and \
1398                 'size' in node['hugepages']:
1399             free = node['hugepages']['free']
1400             memfree = float(node['hugepages']['memfree'].split(' ')[0])
1401             hugesize = float(node['hugepages']['size'].split(' ')[0])
1402
1403             memhugepages = MIN_TOTAL_HUGE_PAGES * hugesize
1404             percentmemhugepages = (memhugepages / memfree) * 100
1405             if free is '0' and \
1406                     percentmemhugepages > MAX_PERCENT_FOR_HUGE_PAGES:
1407                 print "\nThe System has only {} of free memory.".format(int(memfree))
1408                 print "You will not be able to allocate enough Huge Pages for VPP."
1409                 min_sys_res = False
1410
1411         return min_sys_res
1412
1413     def sys_info(self):
1414         """
1415         Print the system information
1416
1417         """
1418
1419         for i in self._nodes.items():
1420             print "\n=============================="
1421             name = i[0]
1422             node = i[1]
1423
1424             print "NODE: {}\n".format(name)
1425
1426             # CPU
1427             print "CPU:"
1428             self.cpu_info(node)
1429
1430             # Grub
1431             print "\nGrub Command Line:"
1432             if 'grub' in node:
1433                 print \
1434                     "  Current: {}".format(
1435                         node['grub']['current_cmdline'])
1436                 print \
1437                     "  Configured: {}".format(
1438                         node['grub']['default_cmdline'])
1439
1440             # Huge Pages
1441             print "\nHuge Pages:"
1442             self.hugepage_info(node)
1443
1444             # Devices
1445             print "\nDevices:"
1446             self.device_info(node)
1447
1448             # Status
1449             print "\nVPP Service Status:"
1450             state, errors = VPPUtil.status(node)
1451             print "  {}".format(state)
1452             for e in errors:
1453                 print "  {}".format(e)
1454
1455             # Minimum system resources
1456             self.min_system_resources(node)
1457
1458             print "\n=============================="
1459
1460     def _ipv4_interface_setup_questions(self, node):
1461         """
1462         Ask the user some questions and get a list of interfaces
1463         and IPv4 addresses associated with those interfaces
1464
1465         :param node: Node dictionary.
1466         :type node: dict
1467         :returns: A list or interfaces with ip addresses
1468         :rtype: dict
1469         """
1470
1471         vpputl = VPPUtil()
1472         interfaces = vpputl.get_hardware(node)
1473         if interfaces == {}:
1474             return
1475
1476         interfaces_with_ip = []
1477         for intf in sorted(interfaces.items()):
1478             name = intf[0]
1479             if name == 'local0':
1480                 continue
1481
1482             question = "Would you like an address to interface {} [Y/n]? ".format(name)
1483             answer = self._ask_user_yn(question, 'y')
1484             if answer == 'y':
1485                 address = {}
1486                 addr, plen = self._ask_user_ipv4()
1487                 address['name'] = name
1488                 address['addr'] = addr
1489                 address['plen'] = plen
1490                 interfaces_with_ip.append(address)
1491
1492         return interfaces_with_ip
1493
1494     def ipv4_interface_setup(self):
1495         """
1496         After asking the user some questions, get a list of interfaces
1497         and IPv4 addresses associated with those interfaces
1498
1499         """
1500
1501         for i in self._nodes.items():
1502             node = i[1]
1503
1504             # Show the current interfaces with IP addresses
1505             current_ints = VPPUtil.get_int_ip(node)
1506             if current_ints is not {}:
1507                 print ("\nThese are the current interfaces with IP addresses:")
1508                 for items in sorted(current_ints.items()):
1509                     name = items[0]
1510                     value = items[1]
1511                     if 'address' not in value:
1512                         address = 'Not Set'
1513                     else:
1514                         address = value['address']
1515                     print ("{:30} {:20} {:10}".format(name, address, value['state']))
1516                 question = "\nWould you like to keep this configuration [Y/n]? "
1517                 answer = self._ask_user_yn(question, 'y')
1518                 if answer == 'y':
1519                     continue
1520             else:
1521                 print ("\nThere are currently no interfaces with IP addresses.")
1522
1523             # Create a script that add the ip addresses to the interfaces
1524             # and brings the interfaces up
1525             ints_with_addrs = self._ipv4_interface_setup_questions(node)
1526             content = ''
1527             for ints in ints_with_addrs:
1528                 name = ints['name']
1529                 addr = ints['addr']
1530                 plen = ints['plen']
1531                 setipstr = 'set int ip address {} {}/{}\n'.format(name, addr, plen)
1532                 setintupstr = 'set int state {} up\n'.format(name)
1533                 content += setipstr + setintupstr
1534
1535             # Write the content to the script
1536             rootdir = node['rootdir']
1537             filename = rootdir + '/vpp/vpp-config/scripts/set_int_ipv4_and_up'
1538             with open(filename, 'w+') as sfile:
1539                 sfile.write(content)
1540
1541             # Execute the script
1542             cmd = 'vppctl exec {}'.format(filename)
1543             (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
1544             if ret != 0:
1545                 logging.debug(stderr)
1546
1547             print("\nA script as been created at {}".format(filename))
1548             print("This script can be run using the following:")
1549             print("vppctl exec {}\n".format(filename))