blob: da455c178a4bc032f038009a5d81924e77ce9312 [file] [log] [blame]
John DeNisco68b0ee32017-09-27 16:35:23 -04001#!/usr/bin/python
2
3# Copyright (c) 2016 Cisco and/or its affiliates.
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at:
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16"""VPP Configuration Main Entry"""
17
18import re
19import os
20import sys
21import logging
22
23from vpplib.AutoConfig import AutoConfig
24from vpplib.VPPUtil import VPPUtil
25
26VPP_DRYRUNDIR = '/vpp/vpp-config/dryrun'
27VPP_AUTO_CONFIGURATION_FILE = '/vpp/vpp-config/configs/auto-config.yaml'
28VPP_HUGE_PAGE_FILE = '/vpp/vpp-config/dryrun/sysctl.d/80-vpp.conf'
29VPP_STARTUP_FILE = '/vpp/vpp-config/dryrun/vpp/startup.conf'
30VPP_GRUB_FILE = '/vpp/vpp-config/dryrun/default/grub'
31VPP_REAL_HUGE_PAGE_FILE = '/etc/sysctl.d/80-vpp.conf'
32VPP_REAL_STARTUP_FILE = '/etc/vpp/startup.conf'
33VPP_REAL_GRUB_FILE = '/etc/default/grub'
34
35rootdir = ''
36
37
38def autoconfig_yn(question, default):
39 """
40 Ask the user a yes or no question.
41
42 :param question: The text of the question
43 :param default: Value to be returned if '\n' is entered
44 :type question: string
45 :type default: string
46 :returns: The Answer
47 :rtype: string
48 """
49 input_valid = False
50 default = default.lower()
51 answer = ''
52 while not input_valid:
53 answer = raw_input(question)
54 if len(answer) == 0:
55 answer = default
56 if re.findall(r'[YyNn]', answer):
57 input_valid = True
58 answer = answer[0].lower()
59 else:
60 print "Please answer Y, N or Return."
61
62 return answer
63
64
65def autoconfig_cp(node, src, dst):
66 """
67 Copies a file, saving the original if needed.
68
69 :param node: Node dictionary with cpuinfo.
70 :param src: Source File
71 :param dst: Destination file
72 :type node: dict
73 :type src: string
74 :type dst: string
75 :raises RuntimeError: If command fails
76 """
77
78 # If the destination file exist, create a copy if one does not already
79 # exist
80 ofile = dst + '.orig'
81 (ret, stdout, stderr) = VPPUtil.exec_command('ls {}'.format(dst))
82 if ret == 0:
83 cmd = 'cp {} {}'.format(dst, ofile)
84 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
85 if ret != 0:
86 raise RuntimeError('{} failed on node {} {} {}'.
87 format(cmd,
88 node['host'],
89 stdout,
90 stderr))
91
92 # Copy the source file
93 cmd = 'cp {} {}'.format(src, os.path.dirname(dst))
94 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
95 if ret != 0:
96 raise RuntimeError('{} failed on node {} {}'.
97 format(cmd, node['host'], stderr))
98
99
100def autoconfig_diff(node, src, dst):
101 """
102 Returns the diffs of 2 files.
103
104 :param node: Node dictionary with cpuinfo.
105 :param src: Source File
106 :param dst: Destination file
107 :type node: dict
108 :type src: string
109 :type dst: string
110 :returns: The Answer
111 :rtype: string
112 :raises RuntimeError: If command fails
113 """
114
115 # Diff the files and return the output
116 cmd = "diff {} {}".format(src, dst)
117 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
118 if stderr != '':
119 raise RuntimeError('{} failed on node {} {} {}'.
120 format(cmd,
121 node['host'],
122 ret,
123 stderr))
124
125 return stdout
126
127
128def autoconfig_show_system():
129 """
130 Shows the system information.
131
132 """
133
134 acfg = AutoConfig(rootdir, VPP_AUTO_CONFIGURATION_FILE)
135
136 acfg.discover()
137
138 acfg.sys_info()
139
140
141def autoconfig_hugepage_apply(node):
142 """
143 Apply the huge page configuration.
144 :param node: The node structure
145 :type node: dict
146 :returns: -1 if the caller should return, 0 if not
147 :rtype: int
148
149 """
150
151 diffs = autoconfig_diff(node, VPP_REAL_HUGE_PAGE_FILE, rootdir + VPP_HUGE_PAGE_FILE)
152 if diffs != '':
153 print "These are the changes we will apply to"
154 print "the huge page file ({}).\n".format(VPP_REAL_HUGE_PAGE_FILE)
155 print diffs
156 answer = autoconfig_yn(
157 "\nAre you sure you want to apply these changes [Y/n]? ",
158 'y')
159 if answer == 'n':
160 return -1
161
162 # Copy and sysctl
163 autoconfig_cp(node, rootdir + VPP_HUGE_PAGE_FILE, VPP_REAL_HUGE_PAGE_FILE)
164 cmd = "sysctl -p {}".format(VPP_REAL_HUGE_PAGE_FILE)
165 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
166 if ret != 0:
167 raise RuntimeError('{} failed on node {} {} {}'.
168 format(cmd, node['host'], stdout, stderr))
169 else:
170 print '\nThere are no changes to the huge page configuration.'
171
172 return 0
173
174
175def autoconfig_vpp_apply(node):
176 """
177 Apply the vpp configuration.
178
179 :param node: The node structure
180 :type node: dict
181 :returns: -1 if the caller should return, 0 if not
182 :rtype: int
183
184 """
185
186 cmd = "service vpp stop"
187 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
188 if ret != 0:
189 raise RuntimeError('{} failed on node {} {} {}'.
190 format(cmd, node['host'], stdout, stderr))
191
192 diffs = autoconfig_diff(node, VPP_REAL_STARTUP_FILE, rootdir + VPP_STARTUP_FILE)
193 if diffs != '':
194 print "These are the changes we will apply to"
195 print "the VPP startup file ({}).\n".format(VPP_REAL_STARTUP_FILE)
196 print diffs
197 answer = autoconfig_yn(
198 "\nAre you sure you want to apply these changes [Y/n]? ",
199 'y')
200 if answer == 'n':
201 return -1
202
203 # Copy the VPP startup
204 autoconfig_cp(node, rootdir + VPP_STARTUP_FILE, VPP_REAL_STARTUP_FILE)
205 else:
206 print '\nThere are no changes to VPP startup.'
207
208 return 0
209
210
211def autoconfig_grub_apply(node):
212 """
213 Apply the grub configuration.
214
215 :param node: The node structure
216 :type node: dict
217 :returns: -1 if the caller should return, 0 if not
218 :rtype: int
219
220 """
221 print "\nThe configured grub cmdline looks like this:"
222 configured_cmdline = node['grub']['default_cmdline']
223 current_cmdline = node['grub']['current_cmdline']
224 print configured_cmdline
225 print "\nThe current boot cmdline looks like this:"
226 print current_cmdline
227 question = "\nDo you want to keep the current boot cmdline [Y/n]? "
228 answer = autoconfig_yn(question, 'y')
229 if answer == 'n':
230 node['grub']['keep_cmdline'] = False
231
232 # Diff the file
233 diffs = autoconfig_diff(node, VPP_REAL_GRUB_FILE, rootdir + VPP_GRUB_FILE)
234 if diffs != '':
235 print "These are the changes we will apply to"
236 print "the GRUB file ({}).\n".format(VPP_REAL_GRUB_FILE)
237 print diffs
238 answer = autoconfig_yn(
239 "\nAre you sure you want to apply these changes [y/N]? ",
240 'n')
241 if answer == 'n':
242 return -1
243
244 # Copy and update grub
245 autoconfig_cp(node, rootdir + VPP_GRUB_FILE, VPP_REAL_GRUB_FILE)
246 distro = VPPUtil.get_linux_distro()
247 if distro[0] == 'Ubuntu':
248 cmd = "update-grub"
249 else:
250 cmd = "grub2-mkconfig -o /boot/grub2/grub.cfg"
251 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
252 if ret != 0:
253 raise RuntimeError('{} failed on node {} {} {}'.
254 format(cmd,
255 node['host'],
256 stdout,
257 stderr))
258 print "There have been changes to the GRUB config a",
259 print "reboot will be required."
260 return -1
261 else:
262 print '\nThere are no changes to the GRUB config.'
263
264 return 0
265
266
267def autoconfig_apply():
268 """
269 Apply the configuration.
270
271 Show the diff of the dryrun file and the actual configuration file
272 Copy the files from the dryrun directory to the actual file.
273 Peform the system function
274
275 """
276
277 vutil = VPPUtil()
278 pkgs = vutil.get_installed_vpp_pkgs()
279 if len(pkgs) == 0:
280 print "\nVPP is not installed, Install VPP with option 4."
281 return
282
283 acfg = AutoConfig(rootdir, VPP_AUTO_CONFIGURATION_FILE)
284
285 print "\nWe are now going to configure your system(s).\n"
286 answer = autoconfig_yn("Are you sure you want to do this [Y/n]? ", 'y')
287 if answer == 'n':
288 return
289
290 nodes = acfg.get_nodes()
291 for i in nodes.items():
292 node = i[1]
293
294 # Check the system resources
295 if not acfg.min_system_resources(node):
296 return
297
298 # Huge Pages
299 ret = autoconfig_hugepage_apply(node)
300 if ret != 0:
301 return
302
303 # VPP
304 ret = autoconfig_vpp_apply(node)
305 if ret != 0:
306 return
307
308 # Grub
309 ret = autoconfig_grub_apply(node)
310 if ret != 0:
311 return
312
313 # Everything is configured start vpp
314 cmd = "service vpp start"
315 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
316 if ret != 0:
317 raise RuntimeError('{} failed on node {} {} {}'.
318 format(cmd, node['host'], stdout, stderr))
319
320
321def autoconfig_dryrun():
322 """
323 Execute the dryrun function.
324
325 """
326
327 vutil = VPPUtil()
328 pkgs = vutil.get_installed_vpp_pkgs()
329 if len(pkgs) == 0:
330 print "\nVPP is not installed, install VPP with option 4."
331 return
332
333 acfg = AutoConfig(rootdir, VPP_AUTO_CONFIGURATION_FILE)
334
335 # Stop VPP on each node
336 nodes = acfg.get_nodes()
337 for i in nodes.items():
338 node = i[1]
339 VPPUtil.stop(node)
340
341 # Discover
342 acfg.discover()
343
344 # Check the system resources
345 nodes = acfg.get_nodes()
346 for i in nodes.items():
347 node = i[1]
348 if not acfg.min_system_resources(node):
349 return
350
351 # Modify the devices
352 acfg.modify_devices()
353
354 # Modify CPU
355 acfg.modify_cpu()
356
357 # Calculate the cpu parameters
358 acfg.calculate_cpu_parameters()
359
360 # Acquire TCP stack parameters
361 acfg.acquire_tcp_params()
362
363 # Apply the startup
364 acfg.apply_vpp_startup()
365
366 # Apply the grub configuration
367 acfg.apply_grub_cmdline()
368
369 # Huge Pages
370 acfg.modify_huge_pages()
371 acfg.apply_huge_pages()
372
373
374def autoconfig_install():
375 """
376 Install or Uninstall VPP.
377
378 """
379
380 # Since these commands will take a while, we
381 # want to see the progress
382 logger = logging.getLogger()
383
384 acfg = AutoConfig(rootdir, VPP_AUTO_CONFIGURATION_FILE)
385 vutil = VPPUtil()
386
387 nodes = acfg.get_nodes()
388 for i in nodes.items():
389 node = i[1]
390
391 pkgs = vutil.get_installed_vpp_pkgs()
392
393 if len(pkgs) > 0:
394 print "\nThese packages are installed on node {}" \
395 .format(node['host'])
396 print "{:25} {}".format("Name", "Version")
397 for pkg in pkgs:
398 if 'version' in pkg:
399 print "{:25} {}".format(
400 pkg['name'], pkg['version'])
401 else:
402 print "{}".format(pkg['name'])
403
404 question = "\nDo you want to uninstall these "
405 question += "packages [y/N]? "
406 answer = autoconfig_yn(question, 'n')
407 if answer == 'y':
408 logger.setLevel(logging.INFO)
409 vutil.uninstall_vpp(node)
410 else:
411 print "\nThere are no VPP packages on node {}." \
412 .format(node['host'])
413 question = "Do you want to install VPP [Y/n]? "
414 answer = autoconfig_yn(question, 'y')
415 if answer == 'y':
416 logger.setLevel(logging.INFO)
417 vutil.install_vpp(node)
418
419 # Set the logging level back
420 logger.setLevel(logging.ERROR)
421
422
423def autoconfig_patch_qemu():
424 """
425 Patch the correct qemu version that is needed for openstack
426
427 """
428
429 # Since these commands will take a while, we
430 # want to see the progress
431 logger = logging.getLogger()
432
433 acfg = AutoConfig(rootdir, VPP_AUTO_CONFIGURATION_FILE)
434
435 nodes = acfg.get_nodes()
436 for i in nodes.items():
437 node = i[1]
438
439 logger.setLevel(logging.INFO)
440 acfg.patch_qemu(node)
441
442
John DeNiscoa3db0782017-10-17 11:07:22 -0400443def autoconfig_ipv4_setup():
444 """
445 Setup IPv4 interfaces
446
447 """
448
449 acfg = AutoConfig(rootdir, VPP_AUTO_CONFIGURATION_FILE)
450 acfg.ipv4_interface_setup()
451
452
John DeNiscoc6b2a202017-11-01 12:37:47 -0400453def autoconfig_create_vm():
454 """
455 Setup IPv4 interfaces
456
457 """
458
459 acfg = AutoConfig(rootdir, VPP_AUTO_CONFIGURATION_FILE)
460 acfg.create_and_bridge_virtual_interfaces()
461
462
John DeNisco68b0ee32017-09-27 16:35:23 -0400463def autoconfig_not_implemented():
464 """
465 This feature is not implemented
466
467 """
468
469 print "\nThis Feature is not implemented yet...."
470
471
John DeNiscoa3db0782017-10-17 11:07:22 -0400472def autoconfig_basic_test_menu():
473 """
474 The auto configuration basic test menu
475
476 """
477
John DeNiscoc6b2a202017-11-01 12:37:47 -0400478# basic_menu_text = '\nWhat would you like to do?\n\n\
479# 1) List/Create Simple IPv4 Setup\n\
480# 2) List/Create Create VM and Connect to VPP interfaces\n\
481# 9 or q) Back to main menu.'
482
John DeNiscoa3db0782017-10-17 11:07:22 -0400483 basic_menu_text = '\nWhat would you like to do?\n\n\
4841) List/Create Simple IPv4 Setup\n\
4859 or q) Back to main menu.'
John DeNiscoa3db0782017-10-17 11:07:22 -0400486 print "{}".format(basic_menu_text)
487
488 input_valid = False
489 answer = ''
490 while not input_valid:
491 answer = raw_input("\nCommand: ")
492 if len(answer) > 1:
493 print "Please enter only 1 character."
494 continue
495 if re.findall(r'[Qq1-29]', answer):
496 input_valid = True
497 answer = answer[0].lower()
498 else:
499 print "Please enter a character between 1 and 2 or 9."
500
501 if answer == '9':
502 answer = 'q'
503
504 return answer
505
506
507def autoconfig_basic_test():
508 """
509 The auto configuration basic test menu
510
511 """
512 vutil = VPPUtil()
513 pkgs = vutil.get_installed_vpp_pkgs()
514 if len(pkgs) == 0:
515 print "\nVPP is not installed, install VPP with option 4."
516 return
517
518 answer = ''
519 while answer != 'q':
520 answer = autoconfig_basic_test_menu()
521 if answer == '1':
522 autoconfig_ipv4_setup()
John DeNiscoc6b2a202017-11-01 12:37:47 -0400523 # elif answer == '2':
524 # autoconfig_create_vm()
John DeNiscoa3db0782017-10-17 11:07:22 -0400525 elif answer == '9' or answer == 'q':
526 return
527 else:
528 autoconfig_not_implemented()
529
530
John DeNisco68b0ee32017-09-27 16:35:23 -0400531def autoconfig_main_menu():
532 """
533 The auto configuration main menu
534
535 """
536
537 main_menu_text = '\nWhat would you like to do?\n\n\
5381) Show basic system information\n\
5392) Dry Run (Will save the configuration files in {}/vpp/vpp-config/dryrun for inspection)\n\
540 and user input in {}/vpp/vpp-config/configs/auto-config.yaml\n\
5413) Full configuration (WARNING: This will change the system configuration)\n\
5424) List/Install/Uninstall VPP.\n\
John DeNiscoa3db0782017-10-17 11:07:22 -04005435) Execute some basic tests.\n\
John DeNisco68b0ee32017-09-27 16:35:23 -04005449 or q) Quit'.format(rootdir, rootdir)
545
546 # 5) Dry Run from {}/vpp/vpp-config/auto-config.yaml (will not ask questions).\n\
547 # 6) Install QEMU patch (Needed when running openstack).\n\
548
549 print "{}".format(main_menu_text)
550
551 input_valid = False
552 answer = ''
553 while not input_valid:
554 answer = raw_input("\nCommand: ")
555 if len(answer) > 1:
556 print "Please enter only 1 character."
557 continue
558 if re.findall(r'[Qq1-79]', answer):
559 input_valid = True
560 answer = answer[0].lower()
561 else:
John DeNiscoa3db0782017-10-17 11:07:22 -0400562 print "Please enter a character between 1 and 5 or 9."
John DeNisco68b0ee32017-09-27 16:35:23 -0400563
564 if answer == '9':
565 answer = 'q'
566 return answer
567
568
569def autoconfig_main():
570 """
571 The auto configuration main entry point
572
573 """
574
575 answer = ''
576 while answer != 'q':
577 answer = autoconfig_main_menu()
578 if answer == '1':
579 autoconfig_show_system()
580 elif answer == '2':
581 autoconfig_dryrun()
582 elif answer == '3':
583 autoconfig_apply()
584 elif answer == '4':
585 autoconfig_install()
John DeNiscoa3db0782017-10-17 11:07:22 -0400586 elif answer == '5':
587 autoconfig_basic_test()
John DeNisco68b0ee32017-09-27 16:35:23 -0400588 elif answer == '9' or answer == 'q':
589 return
590 else:
591 autoconfig_not_implemented()
592
593
594def autoconfig_setup():
595 """
596 The auto configuration setup function.
597
598 We will copy the configuration files to the dryrun directory.
599
600 """
601
602 global rootdir
603
604 logging.basicConfig(level=logging.ERROR)
605
606 distro = VPPUtil.get_linux_distro()
607 if distro[0] == 'Ubuntu':
608 rootdir = '/usr/local'
609 else:
610 rootdir = '/usr'
611
612 # If there is a system configuration file use that, if not use the initial auto-config file
613 filename = rootdir + VPP_AUTO_CONFIGURATION_FILE
614 if os.path.isfile(filename) is True:
615 acfg = AutoConfig(rootdir, VPP_AUTO_CONFIGURATION_FILE)
616 else:
617 raise RuntimeError('The Auto configuration file does not exist {}'.
618 format(filename))
619
620 print "\nWelcome to the VPP system configuration utility"
621
622 print "\nThese are the files we will modify:"
623 print " /etc/vpp/startup.conf"
624 print " /etc/sysctl.d/80-vpp.conf"
625 print " /etc/default/grub"
626
627 print "\nBefore we change them, we'll create working copies in {}".format(rootdir + VPP_DRYRUNDIR)
628 print "Please inspect them carefully before applying the actual configuration (option 3)!"
629
630 nodes = acfg.get_nodes()
631 for i in nodes.items():
632 node = i[1]
633
634 if (os.path.isfile(rootdir + VPP_STARTUP_FILE) is not True) and \
635 (os.path.isfile(VPP_REAL_STARTUP_FILE) is True):
636 autoconfig_cp(node, VPP_REAL_STARTUP_FILE, '{}'.format(rootdir + VPP_STARTUP_FILE))
637 if (os.path.isfile(rootdir + VPP_HUGE_PAGE_FILE) is not True) and \
638 (os.path.isfile(VPP_REAL_HUGE_PAGE_FILE) is True):
639 autoconfig_cp(node, VPP_REAL_HUGE_PAGE_FILE, '{}'.format(rootdir + VPP_HUGE_PAGE_FILE))
640 if (os.path.isfile(rootdir + VPP_GRUB_FILE) is not True) and \
641 (os.path.isfile(VPP_REAL_GRUB_FILE) is True):
642 autoconfig_cp(node, VPP_REAL_GRUB_FILE, '{}'.format(rootdir + VPP_GRUB_FILE))
643
644
645if __name__ == '__main__':
646
647 # Check for root
648 if not os.geteuid() == 0:
649 sys.exit('\nPlease run the VPP Configuration Utility as root.')
650
651 # Setup
652 autoconfig_setup()
653
654 # Main menu
655 autoconfig_main()