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