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