Merge branch 'geni-v3' of git://git.onelab.eu/sfa into geni-v3
[sfa.git] / config / sfa-config-tty
1 #!/usr/bin/env python
2
3 import os
4 import sys
5 import re
6 import time
7 import traceback
8 import types
9 import readline
10 from StringIO import StringIO
11 from optparse import OptionParser
12
13 from sfa.util.version import version_tag
14 from sfa.util.config import Config
15
16
17 def validator(validated_variables):
18     pass
19 #    maint_user = validated_variables["PLC_API_MAINTENANCE_USER"]
20 #    root_user = validated_variables["PLC_ROOT_USER"]
21 #    if maint_user == root_user:
22 #        errStr="PLC_API_MAINTENANCE_USER=%s cannot be the same as PLC_ROOT_USER=%s"%(maint_user, root_user)
23 #        raise plc_config.ConfigurationException(errStr)
24
25 usual_variables = [
26     "SFA_GENERIC_FLAVOUR",
27     "SFA_INTERFACE_HRN",
28     "SFA_REGISTRY_ROOT_AUTH",
29     "SFA_REGISTRY_HOST",
30     "SFA_AGGREGATE_HOST",
31     "SFA_SM_HOST",
32     "SFA_DB_HOST",
33 ]
34
35 flavour_xml_section_hash = {
36     'pl': 'sfa_plc',
37     'openstack': 'sfa_nova',
38     'fd': 'sfa_federica',
39     'nitos': 'sfa_nitos',
40     'dummy': 'sfa_dummy',
41 }
42 configuration = {
43     'name': 'sfa',
44     'service': "sfa",
45     'usual_variables': usual_variables,
46     'config_dir': "/etc/sfa",
47     'validate_variables': {},
48     'validator': validator,
49 }
50
51
52 # GLOBAL VARIABLES
53 #
54 g_configuration = None
55 usual_variables = None
56 config_dir = None
57 service = None
58
59
60 def noop_validator(validated_variables):
61     pass
62
63 # historically we could also configure the devel pkg....
64
65
66 def init_configuration():
67     global g_configuration
68     global usual_variables, config_dir, service
69
70     usual_variables = g_configuration["usual_variables"]
71     config_dir = g_configuration["config_dir"]
72     service = g_configuration["service"]
73
74     global def_default_config, def_site_config, def_consolidated_config
75     def_default_config = "%s/default_config.xml" % config_dir
76     def_site_config = "%s/configs/site_config" % config_dir
77     def_consolidated_config = "%s/%s_config" % (config_dir, service)
78
79     global mainloop_usage
80     mainloop_usage = """Available commands:
81  Uppercase versions give variables comments, when available
82  u/U\t\t\tEdit usual variables
83  w\t\t\tWrite
84  r\t\t\tRestart %(service)s service
85  R\t\t\tReload %(service)s service (rebuild config files for sh, python....)
86  q\t\t\tQuit (without saving)
87  h/?\t\t\tThis help
88 ---
89  l/L [<cat>|<var>]\tShow Locally modified variables/values
90  s/S [<cat>|<var>]\tShow variables/values (all, in category, single)
91  e/E [<cat>|<var>]\tEdit variables (all, in category, single)
92 ---
93  c\t\t\tList categories
94  v/V [<cat>|<var>]\tList Variables (all, in category, single)
95 ---
96 Typical usage involves: u, [l,] w, r, q
97 """ % globals()
98
99
100 def usage():
101     command_usage = "%prog [options] [default-xml [site-xml [consolidated-xml]]]"
102     init_configuration()
103     command_usage += """
104 \t default-xml defaults to %s
105 \t site-xml defaults to %s
106 \t consolidated-xml defaults to %s""" % (def_default_config, def_site_config, def_consolidated_config)
107     return command_usage
108
109 ####################
110 variable_usage = """Edit Commands :
111 #\tShow variable comments
112 .\tStops prompting, return to mainloop
113 /\tCleans any site-defined value, reverts to default
114 =\tShows default value
115 >\tSkips to next category
116 ?\tThis help
117 """
118
119 ####################
120
121
122 def get_value(config,  category_id, variable_id):
123     value = config.get(category_id, variable_id)
124     return value
125
126
127 def get_type(config, category_id, variable_id):
128     value = config.get(category_id, variable_id)
129     # return variable['type']
130     return str
131
132
133 def get_current_value(cread, cwrite, category_id, variable_id):
134     # the value stored in cwrite, if present, is the one we want
135     try:
136         result = get_value(cwrite, category_id, variable_id)
137     except:
138         result = get_value(cread, category_id, variable_id)
139     return result
140
141 # refrain from using plc_config's _sanitize
142
143
144 def get_varname(config,  category_id, variable_id):
145     varname = category_id + "_" + variable_id
146     config.locate_varname(varname)
147     return varname
148
149 # could not avoid using _sanitize here..
150
151
152 def get_name_comments(config, cid, vid):
153     try:
154         (category, variable) = config.get(cid, vid)
155         (id, name, value, comments) = config._sanitize_variable(cid, variable)
156         return (name, comments)
157     except:
158         return (None, [])
159
160
161 def print_name_comments(config, cid, vid):
162     name, comments = get_name_comments(config, cid, vid)
163     if name:
164         print "### %s" % name
165     if comments:
166         for line in comments:
167             print "# %s" % line
168     else:
169         print "!!! No comment associated to %s_%s" % (cid, vid)
170
171 ####################
172
173
174 def list_categories(config):
175     result = []
176     for section in config.sections():
177         result += [section]
178     return result
179
180
181 def print_categories(config):
182     print "Known categories"
183     for cid in list_categories(config):
184         print "%s" % (cid.upper())
185
186 ####################
187
188
189 def list_category(config, cid):
190     result = []
191     for section in config.sections():
192         if section == cid.lower():
193             for (name, value) in config.items(section):
194                 result += ["%s_%s" % (cid, name)]
195     return result
196
197
198 def print_category(config, cid, show_comments=True):
199     cid = cid.lower()
200     CID = cid.upper()
201     vids = list_category(config, cid)
202     if (len(vids) == 0):
203         print "%s : no such category" % CID
204     else:
205         print "Category %s contains" % (CID)
206         for vid in vids:
207             print vid.upper()
208
209 ####################
210
211
212 def consolidate(default_config, site_config, consolidated_config):
213     global service
214     try:
215         conso = Config(default_config)
216         conso.load(site_config)
217         conso.save(consolidated_config)
218     except Exception, inst:
219         print "Could not consolidate, %s" % (str(inst))
220         return
221     print("Merged\n\t%s\nand\t%s\ninto\t%s" % (default_config, site_config,
222                                                consolidated_config))
223
224
225 def reload_service():
226     global service
227     os.system("set -x ; service %s reload" % service)
228
229 ####################
230
231
232 def restart_service():
233     global service
234     print("==================== Stopping %s" % service)
235     os.system("service %s stop" % service)
236     print("==================== Starting %s" % service)
237     os.system("service %s start" % service)
238
239 ####################
240
241
242 def prompt_variable(cdef, cread, cwrite, category, variable,
243                     show_comments, support_next=False):
244
245     category_id = category
246     variable_id = variable
247
248     while True:
249         default_value = get_value(cdef, category_id, variable_id)
250         variable_type = get_type(cdef, category_id, variable_id)
251         current_value = get_current_value(
252             cread, cwrite, category_id, variable_id)
253         varname = get_varname(cread, category_id, variable_id)
254
255         if show_comments:
256             print_name_comments(cdef, category_id, variable_id)
257         prompt = "== %s : [%s] " % (varname, current_value)
258         try:
259             answer = raw_input(prompt).strip()
260         except EOFError:
261             raise Exception('BailOut')
262         except KeyboardInterrupt:
263             print "\n"
264             raise Exception('BailOut')
265
266         # no change
267         if (answer == "") or (answer == current_value):
268             return None
269         elif (answer == "."):
270             raise Exception('BailOut')
271         elif (answer == "#"):
272             print_name_comments(cread, category_id, variable_id)
273         elif (answer == "?"):
274             print variable_usage.strip()
275         elif (answer == "="):
276             print("%s defaults to %s" % (varname, default_value))
277         # revert to default : remove from cwrite (i.e. site-config)
278         elif (answer == "/"):
279             cwrite.delete(category_id, variable_id)
280             print("%s reverted to %s" % (varname, default_value))
281             return
282         elif (answer == ">"):
283             if support_next:
284                 raise Exception('NextCategory')
285             else:
286                 print "No support for next category"
287         else:
288             if cdef.validate_type(variable_type, answer):
289                 cwrite.set(category_id, variable_id, answer)
290                 return
291             else:
292                 print "Not a valid value"
293
294
295 def prompt_variables_all(cdef, cread, cwrite, show_comments):
296     try:
297         for (category_id, (category, variables)) in cread.variables().iteritems():
298             print("========== Category = %s" % category_id.upper())
299             for variable in variables.values():
300                 try:
301                     newvar = prompt_variable(cdef, cread, cwrite, category, variable,
302                                              show_comments, True)
303                 except Exception, inst:
304                     if (str(inst) == 'NextCategory'):
305                         break
306                     else:
307                         raise
308
309     except Exception, inst:
310         if (str(inst) == 'BailOut'):
311             return
312         else:
313             raise
314
315
316 def prompt_variables_category(cdef, cread, cwrite, cid, show_comments):
317     cid = cid.lower()
318     CID = cid.upper()
319     try:
320         print("========== Category = %s" % CID)
321         for vid in list_category(cdef, cid):
322             (category, variable) = cdef.locate_varname(vid.upper())
323             newvar = prompt_variable(cdef, cread, cwrite, category, variable,
324                                      show_comments, False)
325     except Exception, inst:
326         if (str(inst) == 'BailOut'):
327             return
328         else:
329             raise
330
331 ####################
332
333
334 def show_variable(cdef, cread, cwrite,
335                   category, variable, show_value, show_comments):
336     assert category.has_key('id')
337     assert variable.has_key('id')
338
339     category_id = category['id']
340     variable_id = variable['id']
341
342     default_value = get_value(cdef, category_id, variable_id)
343     current_value = get_current_value(cread, cwrite, category_id, variable_id)
344     varname = get_varname(cread, category_id, variable_id)
345     if show_comments:
346         print_name_comments(cdef, category_id, variable_id)
347     if show_value:
348         print "%s = %s" % (varname, current_value)
349     else:
350         print "%s" % (varname)
351
352
353 def show_variables_all(cdef, cread, cwrite, show_value, show_comments):
354     for (category_id, (category, variables)) in cread.variables().iteritems():
355         print("========== Category = %s" % category_id.upper())
356         for variable in variables.values():
357             show_variable(cdef, cread, cwrite,
358                           category, variable, show_value, show_comments)
359
360
361 def show_variables_category(cdef, cread, cwrite, cid, show_value, show_comments):
362     cid = cid.lower()
363     CID = cid.upper()
364     print("========== Category = %s" % CID)
365     for vid in list_category(cdef, cid):
366         (category, variable) = cdef.locate_varname(vid.upper())
367         show_variable(cdef, cread, cwrite, category, variable,
368                       show_value, show_comments)
369
370 ####################
371 re_mainloop_0arg = "^(?P<command>[uUwrRqlLsSeEcvVhH\?])[ \t]*$"
372 re_mainloop_1arg = "^(?P<command>[sSeEvV])[ \t]+(?P<arg>\w+)$"
373 matcher_mainloop_0arg = re.compile(re_mainloop_0arg)
374 matcher_mainloop_1arg = re.compile(re_mainloop_1arg)
375
376
377 def mainloop(cdef, cread, cwrite, default_config, site_config, consolidated_config):
378     global service
379     while True:
380         try:
381             answer = raw_input(
382                 "Enter command (u for usual changes, w to save, ? for help) ").strip()
383         except EOFError:
384             answer = ""
385         except KeyboardInterrupt:
386             print "\nBye"
387             sys.exit()
388
389         if (answer == "") or (answer in "?hH"):
390             print mainloop_usage
391             continue
392         groups_parse = matcher_mainloop_0arg.match(answer)
393         command = None
394         if (groups_parse):
395             command = groups_parse.group('command')
396             arg = None
397         else:
398             groups_parse = matcher_mainloop_1arg.match(answer)
399             if (groups_parse):
400                 command = groups_parse.group('command')
401                 arg = groups_parse.group('arg')
402         if not command:
403             print("Unknown command >%s< -- use h for help" % answer)
404             continue
405
406         show_comments = command.isupper()
407
408         mode = 'ALL'
409         if arg:
410             mode = None
411             arg = arg.lower()
412             variables = list_category(cdef, arg)
413             if len(variables):
414                 # category_id as the category name
415                 # variables as the list of variable names
416                 mode = 'CATEGORY'
417                 category_id = arg
418             arg = arg.upper()
419             (category, variable) = cdef.locate_varname(arg)
420             if variable:
421                 # category/variable as output by locate_varname
422                 mode = 'VARIABLE'
423             if not mode:
424                 print "%s: no such category or variable" % arg
425                 continue
426
427         if command in "qQ":
428             # todo check confirmation
429             return
430         elif command == "w":
431             try:
432                 # Confirm that various constraints are met before saving file.
433                 validate_variables = g_configuration.get(
434                     'validate_variables', {})
435                 validated_variables = cwrite.verify(
436                     cdef, cread, validate_variables)
437                 validator = g_configuration.get('validator', noop_validator)
438                 validator(validated_variables)
439                 cwrite.save(site_config)
440             except:
441                 print "Save failed due to a configuration exception:"
442                 print traceback.print_exc()
443                 print("Could not save -- fix write access on %s" % site_config)
444                 break
445             print("Wrote %s" % site_config)
446             consolidate(default_config, site_config, consolidated_config)
447             print("You might want to type 'r' (restart %s), 'R' (reload %s) or 'q' (quit)" %
448                   (service, service))
449         elif command in "uU":
450             global usual_variables
451             global flavour_xml_section_hash
452             try:
453                 for varname in usual_variables:
454                     (category, variable) = cdef.locate_varname(varname)
455                     if not (category is None and variable is None):
456                         prompt_variable(cdef, cread, cwrite,
457                                         category, variable, False)
458
459                 # set the driver variable according to the already set flavour
460                 generic_flavour = cwrite.items('sfa')[0][1]
461                 for section in cdef.sections():
462                     if generic_flavour in flavour_xml_section_hash and flavour_xml_section_hash[generic_flavour] == section:
463                         for item in cdef.items(section):
464                             category = section
465                             variable = item[0]
466                             prompt_variable(cdef, cread, cwrite,
467                                             category, variable, False)
468                         break
469
470             except Exception, inst:
471                 if (str(inst) != 'BailOut'):
472                     raise
473         elif command == "r":
474             restart_service()
475         elif command == "R":
476             reload_service()
477         elif command == "c":
478             print_categories(cread)
479         elif command in "eE":
480             if mode == 'ALL':
481                 prompt_variables_all(cdef, cread, cwrite, show_comments)
482             elif mode == 'CATEGORY':
483                 prompt_variables_category(
484                     cdef, cread, cwrite, category_id, show_comments)
485             elif mode == 'VARIABLE':
486                 try:
487                     prompt_variable(cdef, cread, cwrite, category, variable,
488                                     show_comments, False)
489                 except Exception, inst:
490                     if str(inst) != 'BailOut':
491                         raise
492         elif command in "vVsSlL":
493             show_value = (command in "sSlL")
494             (c1, c2, c3) = (cdef, cread, cwrite)
495             if command in "lL":
496                 (c1, c2, c3) = (cwrite, cwrite, cwrite)
497             if mode == 'ALL':
498                 show_variables_all(c1, c2, c3, show_value, show_comments)
499             elif mode == 'CATEGORY':
500                 show_variables_category(
501                     c1, c2, c3, category_id, show_value, show_comments)
502             elif mode == 'VARIABLE':
503                 show_variable(c1, c2, c3, category, variable,
504                               show_value, show_comments)
505         else:
506             print("Unknown command >%s< -- use h for help" % answer)
507
508
509 ####################
510 # creates directory for file if not yet existing
511 def check_dir(config_file):
512     dirname = os.path.dirname(config_file)
513     if (not os.path.exists(dirname)):
514         try:
515             os.makedirs(dirname, 0755)
516         except OSError, e:
517             print "Cannot create dir %s due to %s - exiting" % (dirname, e)
518             sys.exit(1)
519
520         if (not os.path.exists(dirname)):
521             print "Cannot create dir %s - exiting" % dirname
522             sys.exit(1)
523         else:
524             print "Created directory %s" % dirname
525
526 ####################
527
528
529 def optParserSetup(configuration):
530     parser = OptionParser(usage=usage(), version="%prog " + version_tag)
531     parser.set_defaults(config_dir=configuration['config_dir'],
532                         service=configuration['service'],
533                         usual_variables=configuration['usual_variables'])
534     parser.add_option("", "--configdir", dest="config_dir",
535                       help="specify configuration directory")
536     parser.add_option("", "--service", dest="service",
537                       help="specify /etc/init.d style service name")
538     parser.add_option("", "--usual_variable", dest="usual_variables",
539                       action="append", help="add a usual variable")
540     return parser
541
542
543 def main(command, argv, configuration):
544     global g_configuration
545     g_configuration = configuration
546
547     parser = optParserSetup(configuration)
548     (config, args) = parser.parse_args()
549     if len(args) > 3:
550         parser.error("too many arguments")
551
552     configuration['service'] = config.service
553     configuration['usual_variables'] = config.usual_variables
554     configuration['config_dir'] = config.config_dir
555     # add in new usual_variables defined on the command line
556     for usual_variable in config.usual_variables:
557         if usual_variable not in configuration['usual_variables']:
558             configuration['usual_variables'].append(usual_variable)
559
560     # intialize configuration
561     init_configuration()
562
563     default_config, site_config, consolidated_config = \
564         def_default_config, def_site_config, def_consolidated_config
565     if len(args) >= 1:
566         default_config = args[0]
567     if len(args) >= 2:
568         site_config = args[1]
569     if len(args) == 3:
570         consolidated_config = args[2]
571
572     for c in (default_config, site_config, consolidated_config):
573         check_dir(c)
574
575     try:
576         # the default settings only - read only
577         cdef = Config(default_config)
578
579         # in effect : default settings + local settings - read only
580         cread = Config(default_config)
581     except:
582         print traceback.print_exc()
583         print("default config files %s not found, is myplc installed ?" %
584               default_config)
585         return 1
586
587     # local settings only, will be modified & saved
588     config_filename = "%s/sfa_config" % config.config_dir
589     cwrite = Config(config_filename)
590     try:
591         cread.load(site_config)
592         cwrite.load(default_config)
593         cwrite.load(site_config)
594     except:
595         cwrite = Config()
596
597     mainloop(cdef, cread, cwrite, default_config,
598              site_config, consolidated_config)
599     return 0
600
601 if __name__ == '__main__':
602     command = sys.argv[0]
603     argv = sys.argv[1:]
604     main(command, argv, configuration)