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