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