75423b84ad1f8e7315409884661e2a0aa9028406
[vpp.git] / extras / vpp_config / vpp_config.py
1 #!/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
18 import re
19 import os
20 import sys
21 import logging
22 import argparse
23
24 from vpplib.AutoConfig import AutoConfig
25 from vpplib.VPPUtil import VPPUtil
26
27 VPP_DRYRUNDIR = '/vpp/vpp-config/dryrun'
28 VPP_AUTO_CONFIGURATION_FILE = '/vpp/vpp-config/configs/auto-config.yaml'
29 VPP_HUGE_PAGE_FILE = '/vpp/vpp-config/dryrun/sysctl.d/80-vpp.conf'
30 VPP_STARTUP_FILE = '/vpp/vpp-config/dryrun/vpp/startup.conf'
31 VPP_GRUB_FILE = '/vpp/vpp-config/dryrun/default/grub'
32 VPP_REAL_HUGE_PAGE_FILE = '/etc/sysctl.d/80-vpp.conf'
33 VPP_REAL_STARTUP_FILE = '/etc/vpp/startup.conf'
34 VPP_REAL_GRUB_FILE = '/etc/default/grub'
35
36 rootdir = ''
37
38
39 def autoconfig_yn(question, default):
40     """
41     Ask the user a yes or no question.
42
43     :param question: The text of the question
44     :param default: Value to be returned if '\n' is entered
45     :type question: string
46     :type default: string
47     :returns: The Answer
48     :rtype: string
49     """
50     input_valid = False
51     default = default.lower()
52     answer = ''
53     while not input_valid:
54         answer = raw_input(question)
55         if len(answer) == 0:
56             answer = default
57         if re.findall(r'[YyNn]', answer):
58             input_valid = True
59             answer = answer[0].lower()
60         else:
61             print "Please answer Y, N or Return."
62
63     return answer
64
65
66 def autoconfig_cp(node, src, dst):
67     """
68     Copies a file, saving the original if needed.
69
70     :param node: Node dictionary with cpuinfo.
71     :param src: Source File
72     :param dst: Destination file
73     :type node: dict
74     :type src: string
75     :type dst: string
76     :raises RuntimeError: If command fails
77     """
78
79     # If the destination file exist, create a copy if one does not already
80     # exist
81     ofile = dst + '.orig'
82     (ret, stdout, stderr) = VPPUtil.exec_command('ls {}'.format(dst))
83     if ret == 0:
84         cmd = 'cp {} {}'.format(dst, ofile)
85         (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
86         if ret != 0:
87             raise RuntimeError('{} failed on node {} {} {}'.
88                                format(cmd,
89                                       node['host'],
90                                       stdout,
91                                       stderr))
92
93     # Copy the source file
94     cmd = 'cp {} {}'.format(src, os.path.dirname(dst))
95     (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
96     if ret != 0:
97         raise RuntimeError('{} failed on node {} {}'.
98                            format(cmd, node['host'], stderr))
99
100
101 def autoconfig_diff(node, src, dst):
102     """
103     Returns the diffs of 2 files.
104
105     :param node: Node dictionary with cpuinfo.
106     :param src: Source File
107     :param dst: Destination file
108     :type node: dict
109     :type src: string
110     :type dst: string
111     :returns: The Answer
112     :rtype: string
113     :raises RuntimeError: If command fails
114     """
115
116     # Diff the files and return the output
117     cmd = "diff {} {}".format(src, dst)
118     (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
119     if stderr != '':
120         raise RuntimeError('{} failed on node {} {} {}'.
121                            format(cmd,
122                                   node['host'],
123                                   ret,
124                                   stderr))
125
126     return stdout
127
128
129 def autoconfig_show_system():
130     """
131     Shows the system information.
132
133     """
134
135     acfg = AutoConfig(rootdir, VPP_AUTO_CONFIGURATION_FILE)
136
137     acfg.discover()
138
139     acfg.sys_info()
140
141
142 def autoconfig_hugepage_apply(node, ask_questions=True):
143     """
144     Apply the huge page configuration.
145     :param node: The node structure
146     :type node: dict
147     :param ask_questions: When True ask the user questions
148     :type ask_questions: bool
149     :returns: -1 if the caller should return, 0 if not
150     :rtype: int
151
152     """
153
154     diffs = autoconfig_diff(node, VPP_REAL_HUGE_PAGE_FILE, rootdir + VPP_HUGE_PAGE_FILE)
155     if diffs != '':
156         print "These are the changes we will apply to"
157         print "the huge page file ({}).\n".format(VPP_REAL_HUGE_PAGE_FILE)
158         print diffs
159         if ask_questions:
160             answer = autoconfig_yn("\nAre you sure you want to apply these changes [Y/n]? ", 'y')
161             if answer == 'n':
162                 return -1
163
164         # Copy and sysctl
165         autoconfig_cp(node, rootdir + VPP_HUGE_PAGE_FILE, VPP_REAL_HUGE_PAGE_FILE)
166         cmd = "sysctl -p {}".format(VPP_REAL_HUGE_PAGE_FILE)
167         (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
168         if ret != 0:
169             raise RuntimeError('{} failed on node {} {} {}'.
170                                format(cmd, node['host'], stdout, stderr))
171     else:
172         print '\nThere are no changes to the huge page configuration.'
173
174     return 0
175
176
177 def autoconfig_vpp_apply(node, ask_questions=True):
178     """
179     Apply the vpp configuration.
180
181     :param node: The node structure
182     :type node: dict
183     :param ask_questions: When True ask the user questions
184     :type ask_questions: bool
185     :returns: -1 if the caller should return, 0 if not
186     :rtype: int
187
188     """
189
190     diffs = autoconfig_diff(node, VPP_REAL_STARTUP_FILE, rootdir + VPP_STARTUP_FILE)
191     if diffs != '':
192         print "These are the changes we will apply to"
193         print "the VPP startup file ({}).\n".format(VPP_REAL_STARTUP_FILE)
194         print diffs
195         if ask_questions:
196             answer = autoconfig_yn("\nAre you sure you want to apply these changes [Y/n]? ", 'y')
197             if answer == 'n':
198                 return -1
199
200         # Copy the VPP startup
201         autoconfig_cp(node, rootdir + VPP_STARTUP_FILE, VPP_REAL_STARTUP_FILE)
202     else:
203         print '\nThere are no changes to VPP startup.'
204
205     return 0
206
207
208 def autoconfig_grub_apply(node, ask_questions=True):
209     """
210     Apply the grub configuration.
211
212     :param node: The node structure
213     :type node: dict
214     :param ask_questions: When True ask the user questions
215     :type ask_questions: bool
216     :returns: -1 if the caller should return, 0 if not
217     :rtype: int
218
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     if ask_questions:
228         question = "\nDo you want to keep the current boot cmdline [Y/n]? "
229         answer = autoconfig_yn(question, 'y')
230         if answer == 'y':
231             return
232
233     node['grub']['keep_cmdline'] = False
234
235     # Diff the file
236     diffs = autoconfig_diff(node, VPP_REAL_GRUB_FILE, rootdir + VPP_GRUB_FILE)
237     if diffs != '':
238         print "These are the changes we will apply to"
239         print "the GRUB file ({}).\n".format(VPP_REAL_GRUB_FILE)
240         print diffs
241         if ask_questions:
242             answer = autoconfig_yn("\nAre you sure you want to apply these changes [y/N]? ", 'n')
243             if answer == 'n':
244                 return -1
245
246         # Copy and update grub
247         autoconfig_cp(node, rootdir + VPP_GRUB_FILE, VPP_REAL_GRUB_FILE)
248         distro = VPPUtil.get_linux_distro()
249         if distro[0] == 'Ubuntu':
250             cmd = "update-grub"
251         else:
252             cmd = "grub2-mkconfig -o /boot/grub2/grub.cfg"
253
254         (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
255         if ret != 0:
256             raise RuntimeError('{} failed on node {} {} {}'.
257                                format(cmd, node['host'], stdout, stderr))
258
259         print "There have been changes to the GRUB config a",
260         print "reboot will be required."
261         return -1
262     else:
263         print '\nThere are no changes to the GRUB config.'
264
265     return 0
266
267
268 def autoconfig_apply(ask_questions=True):
269     """
270     Apply the configuration.
271
272     Show the diff of the dryrun file and the actual configuration file
273     Copy the files from the dryrun directory to the actual file.
274     Peform the system function
275
276     :param ask_questions: When true ask the user questions
277     :type ask_questions: bool
278
279     """
280
281     vutil = VPPUtil()
282     pkgs = vutil.get_installed_vpp_pkgs()
283     if len(pkgs) == 0:
284         print "\nVPP is not installed, Install VPP with option 4."
285         return
286
287     acfg = AutoConfig(rootdir, VPP_AUTO_CONFIGURATION_FILE)
288
289     if ask_questions:
290         print "\nWe are now going to configure your system(s).\n"
291         answer = autoconfig_yn("Are you sure you want to do this [Y/n]? ", 'y')
292         if answer == 'n':
293             return
294
295     nodes = acfg.get_nodes()
296     for i in nodes.items():
297         node = i[1]
298
299         # Check the system resources
300         if not acfg.min_system_resources(node):
301             return
302
303         # Stop VPP
304         VPPUtil.stop(node)
305
306         # Huge Pages
307         ret = autoconfig_hugepage_apply(node, ask_questions)
308         if ret != 0:
309             return
310
311         # VPP
312         ret = autoconfig_vpp_apply(node, ask_questions)
313         if ret != 0:
314             return
315
316         # Grub
317         ret = autoconfig_grub_apply(node, ask_questions)
318         if ret != 0:
319             # We can still start VPP, even if we haven't configured grub
320             VPPUtil.start(node)
321             return
322
323         # Everything is configured start vpp
324         VPPUtil.start(node)
325
326
327 def autoconfig_dryrun(ask_questions=True):
328     """
329     Execute the dryrun function.
330
331     :param ask_questions: When true ask the user for paraameters
332     :type ask_questions: bool
333
334     """
335
336     vutil = VPPUtil()
337     pkgs = vutil.get_installed_vpp_pkgs()
338     if len(pkgs) == 0:
339         print "\nVPP is not installed, please install VPP."
340         return
341
342     acfg = AutoConfig(rootdir, VPP_AUTO_CONFIGURATION_FILE, clean=True)
343
344     # Stop VPP on each node
345     nodes = acfg.get_nodes()
346     for i in nodes.items():
347         node = i[1]
348         VPPUtil.stop(node)
349
350     # Discover
351     acfg.discover()
352
353     # Check the system resources
354     nodes = acfg.get_nodes()
355     for i in nodes.items():
356         node = i[1]
357         if not acfg.min_system_resources(node):
358             return
359
360     # Modify the devices
361     if ask_questions:
362         acfg.modify_devices()
363     else:
364         acfg.update_interfaces_config()
365
366     # Modify CPU
367     acfg.modify_cpu(ask_questions)
368
369     # Calculate the cpu parameters
370     acfg.calculate_cpu_parameters()
371
372     # Acquire TCP stack parameters
373     if ask_questions:
374         acfg.acquire_tcp_params()
375
376     # Apply the startup
377     acfg.apply_vpp_startup()
378
379     # Apply the grub configuration
380     acfg.apply_grub_cmdline()
381
382     # Huge Pages
383     if ask_questions:
384         acfg.modify_huge_pages()
385     acfg.apply_huge_pages()
386
387
388 def autoconfig_install():
389     """
390     Install or Uninstall VPP.
391
392     """
393
394     # Since these commands will take a while, we
395     # want to see the progress
396     logger = logging.getLogger()
397
398     acfg = AutoConfig(rootdir, VPP_AUTO_CONFIGURATION_FILE)
399     vutil = VPPUtil()
400
401     nodes = acfg.get_nodes()
402     for i in nodes.items():
403         node = i[1]
404
405         pkgs = vutil.get_installed_vpp_pkgs()
406
407         if len(pkgs) > 0:
408             print "\nThese packages are installed on node {}" \
409                 .format(node['host'])
410             print "{:25} {}".format("Name", "Version")
411             for pkg in pkgs:
412                 if 'version' in pkg:
413                     print "{:25} {}".format(
414                         pkg['name'], pkg['version'])
415                 else:
416                     print "{}".format(pkg['name'])
417
418             question = "\nDo you want to uninstall these "
419             question += "packages [y/N]? "
420             answer = autoconfig_yn(question, 'n')
421             if answer == 'y':
422                 logger.setLevel(logging.INFO)
423                 vutil.uninstall_vpp(node)
424         else:
425             print "\nThere are no VPP packages on node {}." \
426                 .format(node['host'])
427             question = "Do you want to install VPP [Y/n]? "
428             answer = autoconfig_yn(question, 'y')
429             if answer == 'y':
430                 logger.setLevel(logging.INFO)
431                 vutil.install_vpp(node)
432
433     # Set the logging level back
434     logger.setLevel(logging.ERROR)
435
436
437 def autoconfig_patch_qemu():
438     """
439     Patch the correct qemu version that is needed for openstack
440
441     """
442
443     # Since these commands will take a while, we
444     # want to see the progress
445     logger = logging.getLogger()
446
447     acfg = AutoConfig(rootdir, VPP_AUTO_CONFIGURATION_FILE)
448
449     nodes = acfg.get_nodes()
450     for i in nodes.items():
451         node = i[1]
452
453         logger.setLevel(logging.INFO)
454         acfg.patch_qemu(node)
455
456
457 def autoconfig_ipv4_setup():
458     """
459     Setup IPv4 interfaces
460
461     """
462
463     acfg = AutoConfig(rootdir, VPP_AUTO_CONFIGURATION_FILE)
464     acfg.ipv4_interface_setup()
465
466
467 def autoconfig_create_iperf_vm():
468     """
469     Setup IPv4 interfaces
470
471     """
472
473     acfg = AutoConfig(rootdir, VPP_AUTO_CONFIGURATION_FILE)
474     acfg.destroy_iperf_vm('iperf-server')
475     acfg.create_and_bridge_iperf_virtual_interface()
476     acfg.create_iperf_vm('iperf-server')
477
478
479 def autoconfig_not_implemented():
480     """
481     This feature is not implemented
482
483     """
484
485     print "\nThis Feature is not implemented yet...."
486
487
488 def autoconfig_basic_test_menu():
489     """
490     The auto configuration basic test menu
491
492     """
493
494     basic_menu_text = '\nWhat would you like to do?\n\n\
495 1) List/Create Simple IPv4 Setup\n\
496 2) Create an iperf VM and Connect to VPP an interface\n\
497 9 or q) Back to main menu.'
498
499     print "{}".format(basic_menu_text)
500
501     input_valid = False
502     answer = ''
503     while not input_valid:
504         answer = raw_input("\nCommand: ")
505         if len(answer) > 1:
506             print "Please enter only 1 character."
507             continue
508         if re.findall(r'[Qq1-29]', answer):
509             input_valid = True
510             answer = answer[0].lower()
511         else:
512             print "Please enter a character between 1 and 2 or 9."
513
514         if answer == '9':
515             answer = 'q'
516
517     return answer
518
519
520 def autoconfig_basic_test():
521     """
522     The auto configuration basic test menu
523
524     """
525     vutil = VPPUtil()
526     pkgs = vutil.get_installed_vpp_pkgs()
527     if len(pkgs) == 0:
528         print "\nVPP is not installed, install VPP with option 4."
529         return
530
531     answer = ''
532     while answer != 'q':
533         answer = autoconfig_basic_test_menu()
534         if answer == '1':
535             autoconfig_ipv4_setup()
536         elif answer == '2':
537             autoconfig_create_iperf_vm()
538         elif answer == '9' or answer == 'q':
539             return
540         else:
541             autoconfig_not_implemented()
542
543
544 def autoconfig_main_menu():
545     """
546     The auto configuration main menu
547
548     """
549
550     main_menu_text = '\nWhat would you like to do?\n\n\
551 1) Show basic system information\n\
552 2) Dry Run (Saves the configuration files in {}/vpp/vpp-config/dryrun.\n\
553 3) Full configuration (WARNING: This will change the system configuration)\n\
554 4) List/Install/Uninstall VPP.\n\
555 q) Quit'.format(rootdir, rootdir)
556
557     # 5) Dry Run from {}/vpp/vpp-config/auto-config.yaml (will not ask questions).\n\
558     # 6) Install QEMU patch (Needed when running openstack).\n\
559
560     print "{}".format(main_menu_text)
561
562     input_valid = False
563     answer = ''
564     while not input_valid:
565         answer = raw_input("\nCommand: ")
566         if len(answer) > 1:
567             print "Please enter only 1 character."
568             continue
569         if re.findall(r'[Qq1-4]', answer):
570             input_valid = True
571             answer = answer[0].lower()
572         else:
573             print "Please enter a character between 1 and 4 or q."
574
575     return answer
576
577
578 def autoconfig_main():
579     """
580     The auto configuration main entry point
581
582     """
583
584     # Setup
585     autoconfig_setup()
586
587     answer = ''
588     while answer != 'q':
589         answer = autoconfig_main_menu()
590         if answer == '1':
591             autoconfig_show_system()
592         elif answer == '2':
593             autoconfig_dryrun()
594         elif answer == '3':
595             autoconfig_apply()
596         elif answer == '4':
597             autoconfig_install()
598         elif answer == 'q':
599             return
600         else:
601             autoconfig_not_implemented()
602
603
604 def autoconfig_setup(ask_questions=True):
605     """
606     The auto configuration setup function.
607
608     We will copy the configuration files to the dryrun directory.
609
610     """
611
612     global rootdir
613
614     distro = VPPUtil.get_linux_distro()
615     if distro[0] == 'Ubuntu':
616         rootdir = '/usr/local'
617     else:
618         rootdir = '/usr'
619
620     # If there is a system configuration file use that, if not use the initial auto-config file
621     filename = rootdir + VPP_AUTO_CONFIGURATION_FILE
622     if os.path.isfile(filename) is True:
623         acfg = AutoConfig(rootdir, VPP_AUTO_CONFIGURATION_FILE)
624     else:
625         raise RuntimeError('The Auto configuration file does not exist {}'.
626                            format(filename))
627
628     if ask_questions:
629         print "\nWelcome to the VPP system configuration utility"
630
631         print "\nThese are the files we will modify:"
632         print "    /etc/vpp/startup.conf"
633         print "    /etc/sysctl.d/80-vpp.conf"
634         print "    /etc/default/grub"
635
636         print "\nBefore we change them, we'll create working copies in {}".format(rootdir + VPP_DRYRUNDIR)
637         print "Please inspect them carefully before applying the actual configuration (option 3)!"
638
639     nodes = acfg.get_nodes()
640     for i in nodes.items():
641         node = i[1]
642
643         if (os.path.isfile(rootdir + VPP_STARTUP_FILE) is not True) and \
644                 (os.path.isfile(VPP_REAL_STARTUP_FILE) is True):
645             autoconfig_cp(node, VPP_REAL_STARTUP_FILE, '{}'.format(rootdir + VPP_STARTUP_FILE))
646         if (os.path.isfile(rootdir + VPP_HUGE_PAGE_FILE) is not True) and \
647                 (os.path.isfile(VPP_REAL_HUGE_PAGE_FILE) is True):
648             autoconfig_cp(node, VPP_REAL_HUGE_PAGE_FILE, '{}'.format(rootdir + VPP_HUGE_PAGE_FILE))
649         if (os.path.isfile(rootdir + VPP_GRUB_FILE) is not True) and \
650                 (os.path.isfile(VPP_REAL_GRUB_FILE) is True):
651             autoconfig_cp(node, VPP_REAL_GRUB_FILE, '{}'.format(rootdir + VPP_GRUB_FILE))
652
653         # Be sure the uio_pci_generic driver is installed
654         cmd = 'modprobe uio_pci_generic'
655         (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
656         if ret != 0:
657             logging.warning('{} failed on node {} {}'. format(cmd, node['host'], stderr))
658
659
660 # noinspection PyUnresolvedReferences
661 def execute_with_args(args):
662     """
663     Execute the configuration utility with agruments.
664
665     :param args: The Command line arguments
666     :type args: tuple
667     """
668
669     # Setup
670     autoconfig_setup(ask_questions=False)
671
672     # Execute the command
673     if args.show:
674         autoconfig_show_system()
675     elif args.dry_run:
676         autoconfig_dryrun(ask_questions=False)
677     elif args.apply:
678         autoconfig_apply(ask_questions=False)
679     else:
680         autoconfig_not_implemented()
681
682
683 def config_main():
684     """
685     The vpp configuration utility main entry point.
686
687     """
688
689     # Check for root
690     if not os.geteuid() == 0:
691         sys.exit('\nPlease run the VPP Configuration Utility as root.')
692
693     if len(sys.argv) > 1 and ((sys.argv[1] == '-d') or (sys.argv[1] == '--debug')):
694         logging.basicConfig(level=logging.DEBUG)
695     else:
696         logging.basicConfig(level=logging.ERROR)
697
698     # If no arguments were entered, ask the user questions to get the main parameters
699     if len(sys.argv) == 1:
700         autoconfig_main()
701         return
702     elif len(sys.argv) == 2 and (sys.argv[1] == '-d' or sys.argv[1] == '--debug'):
703         autoconfig_main()
704         return
705
706     # There were arguments specified, so execute the utility using command line arguments
707     description = 'The VPP configuration utility allows the user to configure VPP in a simple and safe manner. \
708 The utility takes input from the user or the speficfied .yaml file. The user should then examine these files \
709 to be sure they are correct and then actually apply the configuration. When run without arguments the utility run \
710 in an interactive mode'
711
712     main_parser = argparse.ArgumentParser(prog='arg-test', description=description,
713                                           epilog='See "%(prog)s help COMMAND" for help on a specific command.')
714     main_parser.add_argument('--apply', '-a', action='store_true', help='Apply the cofiguration.')
715     main_parser.add_argument('--dry-run', '-dr', action='store_true', help='Create the dryrun configuration files.')
716     main_parser.add_argument('--show', '-s', action='store_true', help='Shows basic system information')
717     main_parser.add_argument('--debug', '-d', action='count', help='Print debug output (multiple levels)')
718
719     args = main_parser.parse_args()
720
721     return execute_with_args(args)
722
723
724 if __name__ == '__main__':
725     config_main()