Redhat and small system support
[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_not_implemented():
444     """
445     This feature is not implemented
446
447     """
448
449     print "\nThis Feature is not implemented yet...."
450
451
452 def autoconfig_main_menu():
453     """
454     The auto configuration main menu
455
456     """
457
458     main_menu_text = '\nWhat would you like to do?\n\n\
459 1) Show basic system information\n\
460 2) Dry Run (Will save the configuration files in {}/vpp/vpp-config/dryrun for inspection)\n\
461        and user input in {}/vpp/vpp-config/configs/auto-config.yaml\n\
462 3) Full configuration (WARNING: This will change the system configuration)\n\
463 4) List/Install/Uninstall VPP.\n\
464 9 or q) Quit'.format(rootdir, rootdir)
465
466     # 5) Dry Run from {}/vpp/vpp-config/auto-config.yaml (will not ask questions).\n\
467     # 6) Install QEMU patch (Needed when running openstack).\n\
468
469     print "{}".format(main_menu_text)
470
471     input_valid = False
472     answer = ''
473     while not input_valid:
474         answer = raw_input("\nCommand: ")
475         if len(answer) > 1:
476             print "Please enter only 1 character."
477             continue
478         if re.findall(r'[Qq1-79]', answer):
479             input_valid = True
480             answer = answer[0].lower()
481         else:
482             print "Please enter a character between 1 and 7 or 9."
483
484     if answer == '9':
485         answer = 'q'
486     return answer
487
488
489 def autoconfig_main():
490     """
491     The auto configuration main entry point
492
493     """
494
495     answer = ''
496     while answer != 'q':
497         answer = autoconfig_main_menu()
498         if answer == '1':
499             autoconfig_show_system()
500         elif answer == '2':
501             autoconfig_dryrun()
502         elif answer == '3':
503             autoconfig_apply()
504         elif answer == '4':
505             autoconfig_install()
506         elif answer == '9' or answer == 'q':
507             return
508         else:
509             autoconfig_not_implemented()
510
511
512 def autoconfig_setup():
513     """
514     The auto configuration setup function.
515
516     We will copy the configuration files to the dryrun directory.
517
518     """
519
520     global rootdir
521
522     logging.basicConfig(level=logging.ERROR)
523
524     distro = VPPUtil.get_linux_distro()
525     if distro[0] == 'Ubuntu':
526         rootdir = '/usr/local'
527     else:
528         rootdir = '/usr'
529
530     # If there is a system configuration file use that, if not use the initial auto-config file
531     filename = rootdir + VPP_AUTO_CONFIGURATION_FILE
532     if os.path.isfile(filename) is True:
533         acfg = AutoConfig(rootdir, VPP_AUTO_CONFIGURATION_FILE)
534     else:
535         raise RuntimeError('The Auto configuration file does not exist {}'.
536                            format(filename))
537
538     print "\nWelcome to the VPP system configuration utility"
539
540     print "\nThese are the files we will modify:"
541     print "    /etc/vpp/startup.conf"
542     print "    /etc/sysctl.d/80-vpp.conf"
543     print "    /etc/default/grub"
544
545     print "\nBefore we change them, we'll create working copies in {}".format(rootdir + VPP_DRYRUNDIR)
546     print "Please inspect them carefully before applying the actual configuration (option 3)!"
547
548     nodes = acfg.get_nodes()
549     for i in nodes.items():
550         node = i[1]
551
552         if (os.path.isfile(rootdir + VPP_STARTUP_FILE) is not True) and \
553                 (os.path.isfile(VPP_REAL_STARTUP_FILE) is True):
554             autoconfig_cp(node, VPP_REAL_STARTUP_FILE, '{}'.format(rootdir + VPP_STARTUP_FILE))
555         if (os.path.isfile(rootdir + VPP_HUGE_PAGE_FILE) is not True) and \
556                 (os.path.isfile(VPP_REAL_HUGE_PAGE_FILE) is True):
557             autoconfig_cp(node, VPP_REAL_HUGE_PAGE_FILE, '{}'.format(rootdir + VPP_HUGE_PAGE_FILE))
558         if (os.path.isfile(rootdir + VPP_GRUB_FILE) is not True) and \
559                 (os.path.isfile(VPP_REAL_GRUB_FILE) is True):
560             autoconfig_cp(node, VPP_REAL_GRUB_FILE, '{}'.format(rootdir + VPP_GRUB_FILE))
561
562
563 if __name__ == '__main__':
564
565     # Check for root
566     if not os.geteuid() == 0:
567         sys.exit('\nPlease run the VPP Configuration Utility as root.')
568
569     # Setup
570     autoconfig_setup()
571
572     # Main menu
573     autoconfig_main()