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