Fix minor issues.
[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 (Will save the configuration files in {}/vpp/vpp-config/dryrun for inspection)\n\
553        and user input in {}/vpp/vpp-config/configs/auto-config.yaml\n\
554 3) Full configuration (WARNING: This will change the system configuration)\n\
555 4) List/Install/Uninstall VPP.\n\
556 5) Execute some basic tests.\n\
557 9 or q) Quit'.format(rootdir, rootdir)
558
559     # 5) Dry Run from {}/vpp/vpp-config/auto-config.yaml (will not ask questions).\n\
560     # 6) Install QEMU patch (Needed when running openstack).\n\
561
562     print "{}".format(main_menu_text)
563
564     input_valid = False
565     answer = ''
566     while not input_valid:
567         answer = raw_input("\nCommand: ")
568         if len(answer) > 1:
569             print "Please enter only 1 character."
570             continue
571         if re.findall(r'[Qq1-79]', answer):
572             input_valid = True
573             answer = answer[0].lower()
574         else:
575             print "Please enter a character between 1 and 5 or 9."
576
577     if answer == '9':
578         answer = 'q'
579     return answer
580
581
582 def autoconfig_main():
583     """
584     The auto configuration main entry point
585
586     """
587
588     # Setup
589     autoconfig_setup()
590
591     answer = ''
592     while answer != 'q':
593         answer = autoconfig_main_menu()
594         if answer == '1':
595             autoconfig_show_system()
596         elif answer == '2':
597             autoconfig_dryrun()
598         elif answer == '3':
599             autoconfig_apply()
600         elif answer == '4':
601             autoconfig_install()
602         elif answer == '5':
603             autoconfig_basic_test()
604         elif answer == '9' or answer == 'q':
605             return
606         else:
607             autoconfig_not_implemented()
608
609
610 def autoconfig_setup(ask_questions=True):
611     """
612     The auto configuration setup function.
613
614     We will copy the configuration files to the dryrun directory.
615
616     """
617
618     global rootdir
619
620     distro = VPPUtil.get_linux_distro()
621     if distro[0] == 'Ubuntu':
622         rootdir = '/usr/local'
623     else:
624         rootdir = '/usr'
625
626     # If there is a system configuration file use that, if not use the initial auto-config file
627     filename = rootdir + VPP_AUTO_CONFIGURATION_FILE
628     if os.path.isfile(filename) is True:
629         acfg = AutoConfig(rootdir, VPP_AUTO_CONFIGURATION_FILE)
630     else:
631         raise RuntimeError('The Auto configuration file does not exist {}'.
632                            format(filename))
633
634     if ask_questions:
635         print "\nWelcome to the VPP system configuration utility"
636
637         print "\nThese are the files we will modify:"
638         print "    /etc/vpp/startup.conf"
639         print "    /etc/sysctl.d/80-vpp.conf"
640         print "    /etc/default/grub"
641
642         print "\nBefore we change them, we'll create working copies in {}".format(rootdir + VPP_DRYRUNDIR)
643         print "Please inspect them carefully before applying the actual configuration (option 3)!"
644
645     nodes = acfg.get_nodes()
646     for i in nodes.items():
647         node = i[1]
648
649         if (os.path.isfile(rootdir + VPP_STARTUP_FILE) is not True) and \
650                 (os.path.isfile(VPP_REAL_STARTUP_FILE) is True):
651             autoconfig_cp(node, VPP_REAL_STARTUP_FILE, '{}'.format(rootdir + VPP_STARTUP_FILE))
652         if (os.path.isfile(rootdir + VPP_HUGE_PAGE_FILE) is not True) and \
653                 (os.path.isfile(VPP_REAL_HUGE_PAGE_FILE) is True):
654             autoconfig_cp(node, VPP_REAL_HUGE_PAGE_FILE, '{}'.format(rootdir + VPP_HUGE_PAGE_FILE))
655         if (os.path.isfile(rootdir + VPP_GRUB_FILE) is not True) and \
656                 (os.path.isfile(VPP_REAL_GRUB_FILE) is True):
657             autoconfig_cp(node, VPP_REAL_GRUB_FILE, '{}'.format(rootdir + VPP_GRUB_FILE))
658
659         # Be sure the uio_pci_generic driver is installed
660         cmd = 'modprobe uio_pci_generic'
661         (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
662         if ret != 0:
663             logging.warning('{} failed on node {} {}'. format(cmd, node['host'], stderr))
664
665
666 # noinspection PyUnresolvedReferences
667 def execute_with_args(args):
668     """
669     Execute the configuration utility with agruments.
670
671     :param args: The Command line arguments
672     :type args: tuple
673     """
674
675     # Setup
676     autoconfig_setup(ask_questions=False)
677
678     # Execute the command
679     if args.show:
680         autoconfig_show_system()
681     elif args.dry_run:
682         autoconfig_dryrun(ask_questions=False)
683     elif args.apply:
684         autoconfig_apply(ask_questions=False)
685     else:
686         autoconfig_not_implemented()
687
688
689 def config_main():
690     """
691     The vpp configuration utility main entry point.
692
693     """
694
695     # Check for root
696     if not os.geteuid() == 0:
697         sys.exit('\nPlease run the VPP Configuration Utility as root.')
698
699     if len(sys.argv) > 1 and ((sys.argv[1] == '-d') or (sys.argv[1] == '--debug')):
700         logging.basicConfig(level=logging.DEBUG)
701     else:
702         logging.basicConfig(level=logging.ERROR)
703
704     # If no arguments were entered, ask the user questions to get the main parameters
705     if len(sys.argv) == 1:
706         autoconfig_main()
707         return
708     elif len(sys.argv) == 2 and (sys.argv[1] == '-d' or sys.argv[1] == '--debug'):
709         autoconfig_main()
710         return
711
712     # There were arguments specified, so execute the utility using command line arguments
713     description = 'The VPP configuration utility allows the user to configure VPP in a simple and safe manner. \
714 The utility takes input from the user or the speficfied .yaml file. The user should then examine these files \
715 to be sure they are correct and then actually apply the configuration. When run without arguments the utility run \
716 in an interactive mode'
717
718     main_parser = argparse.ArgumentParser(prog='arg-test', description=description,
719                                           epilog='See "%(prog)s help COMMAND" for help on a specific command.')
720     main_parser.add_argument('--apply', '-a', action='store_true', help='Apply the cofiguration.')
721     main_parser.add_argument('--dry-run', '-dr', action='store_true', help='Create the dryrun configuration files.')
722     main_parser.add_argument('--show', '-s', action='store_true', help='Shows basic system information')
723     main_parser.add_argument('--debug', '-d', action='count', help='Print debug output (multiple levels)')
724
725     args = main_parser.parse_args()
726
727     return execute_with_args(args)
728
729
730 if __name__ == '__main__':
731     config_main()