blob: b995f417e6be8c8ad8983879bb3288667fc82f8e [file] [log] [blame]
John DeNisco68b0ee32017-09-27 16:35:23 -04001# 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
16import logging
17import os
18import re
19import yaml
John DeNiscoc6b2a202017-11-01 12:37:47 -040020from netaddr import IPAddress
John DeNisco68b0ee32017-09-27 16:35:23 -040021
22from vpplib.VPPUtil import VPPUtil
23from vpplib.VppPCIUtil import VppPCIUtil
24from vpplib.VppHugePageUtil import VppHugePageUtil
25from vpplib.CpuUtils import CpuUtils
26from vpplib.VppGrubUtil import VppGrubUtil
27from vpplib.QemuUtils import QemuUtils
28
29__all__ = ["AutoConfig"]
30
31# Constants
32MIN_SYSTEM_CPUS = 2
33MIN_TOTAL_HUGE_PAGES = 1024
34MAX_PERCENT_FOR_HUGE_PAGES = 70
35
36
37class AutoConfig(object):
38 """Auto Configuration Tools"""
39
John DeNiscoddecfb32017-11-15 08:50:57 -050040 def __init__(self, rootdir, filename, clean=False):
John DeNisco68b0ee32017-09-27 16:35:23 -040041 """
42 The Auto Configure class.
43
44 :param rootdir: The root directory for all the auto configuration files
45 :param filename: The autoconfiguration file
John DeNiscoddecfb32017-11-15 08:50:57 -050046 :param clean: When set initialize the nodes from the auto-config file
John DeNisco68b0ee32017-09-27 16:35:23 -040047 :type rootdir: str
48 :type filename: str
John DeNiscoddecfb32017-11-15 08:50:57 -050049 :type clean: bool
John DeNisco68b0ee32017-09-27 16:35:23 -040050 """
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 = ""
John DeNiscoddecfb32017-11-15 08:50:57 -050057 self._clean = clean
John DeNisco68b0ee32017-09-27 16:35:23 -040058 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
John DeNiscoddecfb32017-11-15 08:50:57 -050090 # noinspection PyBroadException
John DeNisco68b0ee32017-09-27 16:35:23 -040091 @staticmethod
John DeNiscoa3db0782017-10-17 11:07:22 -040092 def _ask_user_ipv4():
93 """
94 Asks the user for a number within a range.
95 default is returned if return is entered.
96
John DeNiscoc6b2a202017-11-01 12:37:47 -040097 :returns: IP address with cidr
98 :rtype: str
John DeNiscoa3db0782017-10-17 11:07:22 -040099 """
100
101 while True:
John DeNiscoc6b2a202017-11-01 12:37:47 -0400102 answer = raw_input("Please enter the IPv4 Address [n.n.n.n/n]: ")
John DeNiscoa3db0782017-10-17 11:07:22 -0400103 try:
John DeNiscoc6b2a202017-11-01 12:37:47 -0400104 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)
John DeNiscoa3db0782017-10-17 11:07:22 -0400112 except:
113 print "Please enter a valid IPv4 address."
114 continue
115
John DeNiscoa3db0782017-10-17 11:07:22 -0400116 @staticmethod
John DeNisco68b0ee32017-09-27 16:35:23 -0400117 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']
John DeNiscoddecfb32017-11-15 08:50:57 -0500196 if self._clean is False and os.path.isfile(systemfile):
John DeNisco68b0ee32017-09-27 16:35:23 -0400197 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:
John DeNiscoddecfb32017-11-15 08:50:57 -0500228 yaml.dump(ydata, yamlfile)
John DeNisco68b0ee32017-09-27 16:35:23 -0400229
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] = {}
John DeNiscoddecfb32017-11-15 08:50:57 -0500259 addr = '{}'.format(interface['pci_address'])
260 node['interfaces'][port]['pci_address'] = addr
John DeNisco68b0ee32017-09-27 16:35:23 -0400261 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:
John DeNiscoddecfb32017-11-15 08:50:57 -0500288 yaml.dump(ydata, yamlfile)
John DeNisco68b0ee32017-09-27 16:35:23 -0400289
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'
John DeNiscoddecfb32017-11-15 08:50:57 -0500334 if 'vpp_main_core' in node['cpu']:
335 vpp_main_core = node['cpu']['vpp_main_core']
336 else:
337 vpp_main_core = 0
John DeNisco68b0ee32017-09-27 16:35:23 -0400338 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']
John DeNiscoddecfb32017-11-15 08:50:57 -0500706 if 'vpp_main_core' in node['cpu']:
707 vpp_main_core = node['cpu']['vpp_main_core']
708 else:
709 vpp_main_core = 0
John DeNisco68b0ee32017-09-27 16:35:23 -0400710 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 \
932other 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
John DeNiscoddecfb32017-11-15 08:50:57 -0500956 def modify_cpu(self, ask_questions=True):
John DeNisco68b0ee32017-09-27 16:35:23 -0400957 """
958 Modify the cpu configuration, asking for the user for the values.
959
John DeNiscoddecfb32017-11-15 08:50:57 -0500960 :param ask_questions: When true ask the user for config parameters
961
John DeNisco68b0ee32017-09-27 16:35:23 -0400962 """
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
John DeNiscoddecfb32017-11-15 08:50:57 -05001005 if ask_questions:
1006 self._modify_cpu_questions(node, total_cpus, numa_nodes)
John DeNisco68b0ee32017-09-27 16:35:23 -04001007
1008 # Populate the interfaces with the numa node
John DeNiscoddecfb32017-11-15 08:50:57 -05001009 if 'interfaces' in node:
1010 ikeys = node['interfaces'].keys()
1011 VPPUtil.get_interfaces_numa_node(node, *tuple(ikeys))
John DeNisco68b0ee32017-09-27 16:35:23 -04001012
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
John DeNiscoddecfb32017-11-15 08:50:57 -05001077 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
John DeNisco68b0ee32017-09-27 16:35:23 -04001104 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 = \
John DeNiscoa3db0782017-10-17 11:07:22 -04001220 "How many huge pages do you want [{} - {}][{}]? ". \
John DeNisco68b0ee32017-09-27 16:35:23 -04001221 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 \
John DeNiscoa3db0782017-10-17 11:07:22 -04001433 'memfree' in node['hugepages'] and \
1434 'size' in node['hugepages']:
1435 free = node['hugepages']['free']
1436 memfree = float(node['hugepages']['memfree'].split(' ')[0])
John DeNisco68b0ee32017-09-27 16:35:23 -04001437 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=============================="
John DeNiscoa3db0782017-10-17 11:07:22 -04001495
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
John DeNiscoc6b2a202017-11-01 12:37:47 -04001518 question = "Would you like add address to interface {} [Y/n]? ".format(name)
John DeNiscoa3db0782017-10-17 11:07:22 -04001519 answer = self._ask_user_yn(question, 'y')
1520 if answer == 'y':
1521 address = {}
John DeNiscoc6b2a202017-11-01 12:37:47 -04001522 addr = self._ask_user_ipv4()
John DeNiscoa3db0782017-10-17 11:07:22 -04001523 address['name'] = name
1524 address['addr'] = addr
John DeNiscoa3db0782017-10-17 11:07:22 -04001525 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']
John DeNiscoc6b2a202017-11-01 12:37:47 -04001565 setipstr = 'set int ip address {} {}\n'.format(name, addr)
John DeNiscoa3db0782017-10-17 11:07:22 -04001566 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:")
John DeNiscoc6b2a202017-11-01 12:37:47 -04001583 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))