the big cleanup: deprecate federica driver
[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     'nitos': 'sfa_nitos',
39     'dummy': 'sfa_dummy',
40 }
41 configuration = {
42     'name': 'sfa',
43     'service': "sfa",
44     'usual_variables': usual_variables,
45     'config_dir': "/etc/sfa",
46     'validate_variables': {},
47     'validator': validator,
48 }
49
50
51 # GLOBAL VARIABLES
52 #
53 g_configuration = None
54 usual_variables = None
55 config_dir = None
56 service = None
57
58
59 def noop_validator(validated_variables):
60     pass
61
62 # historically we could also configure the devel pkg....
63
64
65 def init_configuration():
66     global g_configuration
67     global usual_variables, config_dir, service
68
69     usual_variables = g_configuration["usual_variables"]
70     config_dir = g_configuration["config_dir"]
71     service = g_configuration["service"]
72
73     global def_default_config, def_site_config, def_consolidated_config
74     def_default_config = "%s/default_config.xml" % config_dir
75     def_site_config = "%s/configs/site_config" % config_dir
76     def_consolidated_config = "%s/%s_config" % (config_dir, service)
77
78     global mainloop_usage
79     mainloop_usage = """Available commands:
80  Uppercase versions give variables comments, when available
81  u/U\t\t\tEdit usual variables
82  w\t\t\tWrite
83  r\t\t\tRestart %(service)s service
84  R\t\t\tReload %(service)s service (rebuild config files for sh, python....)
85  q\t\t\tQuit (without saving)
86  h/?\t\t\tThis help
87 ---
88  l/L [<cat>|<var>]\tShow Locally modified variables/values
89  s/S [<cat>|<var>]\tShow variables/values (all, in category, single)
90  e/E [<cat>|<var>]\tEdit variables (all, in category, single)
91 ---
92  c\t\t\tList categories
93  v/V [<cat>|<var>]\tList Variables (all, in category, single)
94 ---
95 Typical usage involves: u, [l,] w, r, q
96 """ % globals()
97
98
99 def usage():
100     command_usage = "%prog [options] [default-xml [site-xml [consolidated-xml]]]"
101     init_configuration()
102     command_usage += """
103 \t default-xml defaults to %s
104 \t site-xml defaults to %s
105 \t consolidated-xml defaults to %s""" % (def_default_config, def_site_config, def_consolidated_config)
106     return command_usage
107
108 ####################
109 variable_usage = """Edit Commands :
110 #\tShow variable comments
111 .\tStops prompting, return to mainloop
112 /\tCleans any site-defined value, reverts to default
113 =\tShows default value
114 >\tSkips to next category
115 ?\tThis help
116 """
117
118 ####################
119
120
121 def get_value(config,  category_id, variable_id):
122     value = config.get(category_id, variable_id)
123     return value
124
125
126 def get_type(config, category_id, variable_id):
127     value = config.get(category_id, variable_id)
128     # return variable['type']
129     return str
130
131
132 def get_current_value(cread, cwrite, category_id, variable_id):
133     # the value stored in cwrite, if present, is the one we want
134     try:
135         result = get_value(cwrite, category_id, variable_id)
136     except:
137         result = get_value(cread, category_id, variable_id)
138     return result
139
140 # refrain from using plc_config's _sanitize
141
142
143 def get_varname(config,  category_id, variable_id):
144     varname = category_id + "_" + variable_id
145     config.locate_varname(varname)
146     return varname
147
148 # could not avoid using _sanitize here..
149
150
151 def get_name_comments(config, cid, vid):
152     try:
153         (category, variable) = config.get(cid, vid)
154         (id, name, value, comments) = config._sanitize_variable(cid, variable)
155         return (name, comments)
156     except:
157         return (None, [])
158
159
160 def print_name_comments(config, cid, vid):
161     name, comments = get_name_comments(config, cid, vid)
162     if name:
163         print "### %s" % name
164     if comments:
165         for line in comments:
166             print "# %s" % line
167     else:
168         print "!!! No comment associated to %s_%s" % (cid, vid)
169
170 ####################
171
172
173 def list_categories(config):
174     result = []
175     for section in config.sections():
176         result += [section]
177     return result
178
179
180 def print_categories(config):
181     print "Known categories"
182     for cid in list_categories(config):
183         print "%s" % (cid.upper())
184
185 ####################
186
187
188 def list_category(config, cid):
189     result = []
190     for section in config.sections():
191         if section == cid.lower():
192             for (name, value) in config.items(section):
193                 result += ["%s_%s" % (cid, name)]
194     return result
195
196
197 def print_category(config, cid, show_comments=True):
198     cid = cid.lower()
199     CID = cid.upper()
200     vids = list_category(config, cid)
201     if (len(vids) == 0):
202         print "%s : no such category" % CID
203     else:
204         print "Category %s contains" % (CID)
205         for vid in vids:
206             print vid.upper()
207
208 ####################
209
210
211 def consolidate(default_config, site_config, consolidated_config):
212     global service
213     try:
214         conso = Config(default_config)
215         conso.load(site_config)
216         conso.save(consolidated_config)
217     except Exception, inst:
218         print "Could not consolidate, %s" % (str(inst))
219         return
220     print("Merged\n\t%s\nand\t%s\ninto\t%s" % (default_config, site_config,
221                                                consolidated_config))
222
223
224 def reload_service():
225     reload = "sfa-setup.sh reload"
226     print("Running: {}".format(reload))
227     os.system(reload)
228
229 ####################
230
231
232 def restart_service():
233     services = ('sfa-db', 'sfa-aggregate', 'sfa-registry')
234     for service in services:
235         restart = ("systemctl -q is-active {s} && {{ echo restarting {s} ; systemctl restart {s}; }}"
236                    .format(s=service))
237         os.system(restart)
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)