blob: 2e644185236d3c6bb9e8880fa65ba830e9e72579 [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
443def autoconfig_not_implemented():
444 """
445 This feature is not implemented
446
447 """
448
449 print "\nThis Feature is not implemented yet...."
450
451
452def 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\
4591) Show basic system information\n\
4602) 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\
4623) Full configuration (WARNING: This will change the system configuration)\n\
4634) List/Install/Uninstall VPP.\n\
4649 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
489def 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
512def 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
563if __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()