Add support for 18.01.
[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_vm():
468     """
469     Setup IPv4 interfaces
470
471     """
472
473     acfg = AutoConfig(rootdir, VPP_AUTO_CONFIGURATION_FILE)
474     acfg.create_and_bridge_virtual_interfaces()
475
476
477 def autoconfig_not_implemented():
478     """
479     This feature is not implemented
480
481     """
482
483     print "\nThis Feature is not implemented yet...."
484
485
486 def autoconfig_basic_test_menu():
487     """
488     The auto configuration basic test menu
489
490     """
491
492     basic_menu_text = '\nWhat would you like to do?\n\n\
493 1) List/Create Simple IPv4 Setup\n\
494 9 or q) Back to main menu.'
495
496     # 1) List/Create Simple IPv4 Setup\n\
497     # 2) List/Create Create VM and Connect to VPP interfaces\n\
498     # 9 or q) Back to main menu.'
499
500     print "{}".format(basic_menu_text)
501
502     input_valid = False
503     answer = ''
504     while not input_valid:
505         answer = raw_input("\nCommand: ")
506         if len(answer) > 1:
507             print "Please enter only 1 character."
508             continue
509         if re.findall(r'[Qq1-29]', answer):
510             input_valid = True
511             answer = answer[0].lower()
512         else:
513             print "Please enter a character between 1 and 2 or 9."
514
515         if answer == '9':
516             answer = 'q'
517
518     return answer
519
520
521 def autoconfig_basic_test():
522     """
523     The auto configuration basic test menu
524
525     """
526     vutil = VPPUtil()
527     pkgs = vutil.get_installed_vpp_pkgs()
528     if len(pkgs) == 0:
529         print "\nVPP is not installed, install VPP with option 4."
530         return
531
532     answer = ''
533     while answer != 'q':
534         answer = autoconfig_basic_test_menu()
535         if answer == '1':
536             autoconfig_ipv4_setup()
537         # elif answer == '2':
538         #    autoconfig_create_vm()
539         elif answer == '9' or answer == 'q':
540             return
541         else:
542             autoconfig_not_implemented()
543
544
545 def autoconfig_main_menu():
546     """
547     The auto configuration main menu
548
549     """
550
551     main_menu_text = '\nWhat would you like to do?\n\n\
552 1) Show basic system information\n\
553 2) Dry Run (Will save the configuration files in {}/vpp/vpp-config/dryrun for inspection)\n\
554        and user input in {}/vpp/vpp-config/configs/auto-config.yaml\n\
555 3) Full configuration (WARNING: This will change the system configuration)\n\
556 4) List/Install/Uninstall VPP.\n\
557 5) Execute some basic tests.\n\
558 9 or q) Quit'.format(rootdir, rootdir)
559
560     # 5) Dry Run from {}/vpp/vpp-config/auto-config.yaml (will not ask questions).\n\
561     # 6) Install QEMU patch (Needed when running openstack).\n\
562
563     print "{}".format(main_menu_text)
564
565     input_valid = False
566     answer = ''
567     while not input_valid:
568         answer = raw_input("\nCommand: ")
569         if len(answer) > 1:
570             print "Please enter only 1 character."
571             continue
572         if re.findall(r'[Qq1-79]', answer):
573             input_valid = True
574             answer = answer[0].lower()
575         else:
576             print "Please enter a character between 1 and 5 or 9."
577
578     if answer == '9':
579         answer = 'q'
580     return answer
581
582
583 def autoconfig_main():
584     """
585     The auto configuration main entry point
586
587     """
588
589     # Setup
590     autoconfig_setup()
591
592     answer = ''
593     while answer != 'q':
594         answer = autoconfig_main_menu()
595         if answer == '1':
596             autoconfig_show_system()
597         elif answer == '2':
598             autoconfig_dryrun()
599         elif answer == '3':
600             autoconfig_apply()
601         elif answer == '4':
602             autoconfig_install()
603         elif answer == '5':
604             autoconfig_basic_test()
605         elif answer == '9' or answer == 'q':
606             return
607         else:
608             autoconfig_not_implemented()
609
610
611 def autoconfig_setup(ask_questions=True):
612     """
613     The auto configuration setup function.
614
615     We will copy the configuration files to the dryrun directory.
616
617     """
618
619     global rootdir
620
621     distro = VPPUtil.get_linux_distro()
622     if distro[0] == 'Ubuntu':
623         rootdir = '/usr/local'
624     else:
625         rootdir = '/usr'
626
627     # If there is a system configuration file use that, if not use the initial auto-config file
628     filename = rootdir + VPP_AUTO_CONFIGURATION_FILE
629     if os.path.isfile(filename) is True:
630         acfg = AutoConfig(rootdir, VPP_AUTO_CONFIGURATION_FILE)
631     else:
632         raise RuntimeError('The Auto configuration file does not exist {}'.
633                            format(filename))
634
635     if ask_questions:
636         print "\nWelcome to the VPP system configuration utility"
637
638         print "\nThese are the files we will modify:"
639         print "    /etc/vpp/startup.conf"
640         print "    /etc/sysctl.d/80-vpp.conf"
641         print "    /etc/default/grub"
642
643         print "\nBefore we change them, we'll create working copies in {}".format(rootdir + VPP_DRYRUNDIR)
644         print "Please inspect them carefully before applying the actual configuration (option 3)!"
645
646     nodes = acfg.get_nodes()
647     for i in nodes.items():
648         node = i[1]
649
650         if (os.path.isfile(rootdir + VPP_STARTUP_FILE) is not True) and \
651                 (os.path.isfile(VPP_REAL_STARTUP_FILE) is True):
652             autoconfig_cp(node, VPP_REAL_STARTUP_FILE, '{}'.format(rootdir + VPP_STARTUP_FILE))
653         if (os.path.isfile(rootdir + VPP_HUGE_PAGE_FILE) is not True) and \
654                 (os.path.isfile(VPP_REAL_HUGE_PAGE_FILE) is True):
655             autoconfig_cp(node, VPP_REAL_HUGE_PAGE_FILE, '{}'.format(rootdir + VPP_HUGE_PAGE_FILE))
656         if (os.path.isfile(rootdir + VPP_GRUB_FILE) is not True) and \
657                 (os.path.isfile(VPP_REAL_GRUB_FILE) is True):
658             autoconfig_cp(node, VPP_REAL_GRUB_FILE, '{}'.format(rootdir + VPP_GRUB_FILE))
659
660         # Be sure the uio_pci_generic driver is installed
661         cmd = 'modprobe uio_pci_generic'
662         (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
663         if ret != 0:
664             raise RuntimeError('{} failed on node {} {}'. format(cmd, node['host'], stderr))
665
666
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()