Allow the user to select master or release during the install
[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                 question = "Do you want to install the release version [Y/n]? "
431                 answer = autoconfig_yn(question, 'y')
432                 if answer == 'y':
433                     branch = 'release'
434                 else:
435                     branch = 'master'
436                 logger.setLevel(logging.INFO)
437                 vutil.install_vpp(node, branch)
438
439     # Set the logging level back
440     logger.setLevel(logging.ERROR)
441
442
443 def autoconfig_patch_qemu():
444     """
445     Patch the correct qemu version that is needed for openstack
446
447     """
448
449     # Since these commands will take a while, we
450     # want to see the progress
451     logger = logging.getLogger()
452
453     acfg = AutoConfig(rootdir, VPP_AUTO_CONFIGURATION_FILE)
454
455     nodes = acfg.get_nodes()
456     for i in nodes.items():
457         node = i[1]
458
459         logger.setLevel(logging.INFO)
460         acfg.patch_qemu(node)
461
462
463 def autoconfig_ipv4_setup():
464     """
465     Setup IPv4 interfaces
466
467     """
468
469     acfg = AutoConfig(rootdir, VPP_AUTO_CONFIGURATION_FILE)
470     acfg.ipv4_interface_setup()
471
472
473 def autoconfig_create_iperf_vm():
474     """
475     Setup IPv4 interfaces
476
477     """
478
479     acfg = AutoConfig(rootdir, VPP_AUTO_CONFIGURATION_FILE)
480     acfg.destroy_iperf_vm('iperf-server')
481     acfg.create_and_bridge_iperf_virtual_interface()
482     acfg.create_iperf_vm('iperf-server')
483
484
485 def autoconfig_not_implemented():
486     """
487     This feature is not implemented
488
489     """
490
491     print "\nThis Feature is not implemented yet...."
492
493
494 def autoconfig_basic_test_menu():
495     """
496     The auto configuration basic test menu
497
498     """
499
500     basic_menu_text = '\nWhat would you like to do?\n\n\
501 1) List/Create Simple IPv4 Setup\n\
502 2) Create an iperf VM and Connect to VPP an interface\n\
503 9 or q) Back to main menu.'
504
505     print "{}".format(basic_menu_text)
506
507     input_valid = False
508     answer = ''
509     while not input_valid:
510         answer = raw_input("\nCommand: ")
511         if len(answer) > 1:
512             print "Please enter only 1 character."
513             continue
514         if re.findall(r'[Qq1-29]', answer):
515             input_valid = True
516             answer = answer[0].lower()
517         else:
518             print "Please enter a character between 1 and 2 or 9."
519
520         if answer == '9':
521             answer = 'q'
522
523     return answer
524
525
526 def autoconfig_basic_test():
527     """
528     The auto configuration basic test menu
529
530     """
531     vutil = VPPUtil()
532     pkgs = vutil.get_installed_vpp_pkgs()
533     if len(pkgs) == 0:
534         print "\nVPP is not installed, install VPP with option 4."
535         return
536
537     answer = ''
538     while answer != 'q':
539         answer = autoconfig_basic_test_menu()
540         if answer == '1':
541             autoconfig_ipv4_setup()
542         elif answer == '2':
543             autoconfig_create_iperf_vm()
544         elif answer == '9' or answer == 'q':
545             return
546         else:
547             autoconfig_not_implemented()
548
549
550 def autoconfig_main_menu():
551     """
552     The auto configuration main menu
553
554     """
555
556     main_menu_text = '\nWhat would you like to do?\n\n\
557 1) Show basic system information\n\
558 2) Dry Run (Saves the configuration files in {}/vpp/vpp-config/dryrun.\n\
559 3) Full configuration (WARNING: This will change the system configuration)\n\
560 4) List/Install/Uninstall VPP.\n\
561 q) Quit'.format(rootdir, rootdir)
562
563     # 5) Dry Run from {}/vpp/vpp-config/auto-config.yaml (will not ask questions).\n\
564     # 6) Install QEMU patch (Needed when running openstack).\n\
565
566     print "{}".format(main_menu_text)
567
568     input_valid = False
569     answer = ''
570     while not input_valid:
571         answer = raw_input("\nCommand: ")
572         if len(answer) > 1:
573             print "Please enter only 1 character."
574             continue
575         if re.findall(r'[Qq1-4]', answer):
576             input_valid = True
577             answer = answer[0].lower()
578         else:
579             print "Please enter a character between 1 and 4 or q."
580
581     return answer
582
583
584 def autoconfig_main():
585     """
586     The auto configuration main entry point
587
588     """
589
590     # Setup
591     autoconfig_setup()
592
593     answer = ''
594     while answer != 'q':
595         answer = autoconfig_main_menu()
596         if answer == '1':
597             autoconfig_show_system()
598         elif answer == '2':
599             autoconfig_dryrun()
600         elif answer == '3':
601             autoconfig_apply()
602         elif answer == '4':
603             autoconfig_install()
604         elif 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()