blob: b8b49a032b48a95218b66a52b0096f64757c9aa2 [file] [log] [blame]
John DeNisco68b0ee32017-09-27 16:35:23 -04001#!/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
18import re
19import os
20import sys
21import logging
22
23from vpplib.AutoConfig import AutoConfig
24from vpplib.VPPUtil import VPPUtil
25
26VPP_DRYRUNDIR = '/vpp/vpp-config/dryrun'
27VPP_AUTO_CONFIGURATION_FILE = '/vpp/vpp-config/configs/auto-config.yaml'
28VPP_HUGE_PAGE_FILE = '/vpp/vpp-config/dryrun/sysctl.d/80-vpp.conf'
29VPP_STARTUP_FILE = '/vpp/vpp-config/dryrun/vpp/startup.conf'
30VPP_GRUB_FILE = '/vpp/vpp-config/dryrun/default/grub'
31VPP_REAL_HUGE_PAGE_FILE = '/etc/sysctl.d/80-vpp.conf'
32VPP_REAL_STARTUP_FILE = '/etc/vpp/startup.conf'
33VPP_REAL_GRUB_FILE = '/etc/default/grub'
34
35rootdir = ''
36
37
38def 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
65def 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
100def 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
128def 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
141def 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
175def 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
211def 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
267def 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
321def 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
374def 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
423def 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
John DeNiscoa3db0782017-10-17 11:07:22 -0400443def 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
John DeNisco68b0ee32017-09-27 16:35:23 -0400453def autoconfig_not_implemented():
454 """
455 This feature is not implemented
456
457 """
458
459 print "\nThis Feature is not implemented yet...."
460
461
John DeNiscoa3db0782017-10-17 11:07:22 -0400462def 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\
4691) List/Create Simple IPv4 Setup\n\
4709 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
493def 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
John DeNisco68b0ee32017-09-27 16:35:23 -0400515def 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\
5221) Show basic system information\n\
5232) 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\
5253) Full configuration (WARNING: This will change the system configuration)\n\
5264) List/Install/Uninstall VPP.\n\
John DeNiscoa3db0782017-10-17 11:07:22 -04005275) Execute some basic tests.\n\
John DeNisco68b0ee32017-09-27 16:35:23 -04005289 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:
John DeNiscoa3db0782017-10-17 11:07:22 -0400546 print "Please enter a character between 1 and 5 or 9."
John DeNisco68b0ee32017-09-27 16:35:23 -0400547
548 if answer == '9':
549 answer = 'q'
550 return answer
551
552
553def 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()
John DeNiscoa3db0782017-10-17 11:07:22 -0400570 elif answer == '5':
571 autoconfig_basic_test()
John DeNisco68b0ee32017-09-27 16:35:23 -0400572 elif answer == '9' or answer == 'q':
573 return
574 else:
575 autoconfig_not_implemented()
576
577
578def 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
629if __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()