X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=plc_config.py;h=9f55511e51868344210c165852f28347bf83ef6b;hb=28454f7e64f8c8bd6b01d1960e9bb771f4b00de4;hp=45805b969d4d3a8b65965d2f1d968c7ad1aaffbb;hpb=b4f6124e813805155c9f5d83eee6d20f284931bb;p=myplc.git diff --git a/plc_config.py b/plc_config.py index 45805b9..9f55511 100644 --- a/plc_config.py +++ b/plc_config.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python3 # # Merge PlanetLab Central (PLC) configuration files into a variety of # output formats. These files represent the global configuration for a @@ -7,19 +7,18 @@ # Mark Huang # Copyright (C) 2006 The Trustees of Princeton University # -# $Id$ -# -import xml.dom.minidom -from xml.parsers.expat import ExpatError -from StringIO import StringIO -import time -import re -import textwrap import codecs import os -import types - +import re +import sys +import textwrap +import time +import traceback +import xml.dom.minidom +from xml.parsers.expat import ExpatError +from io import StringIO +from optparse import OptionParser class ConfigurationException(Exception): pass @@ -40,7 +39,7 @@ class PLCConfiguration: You may also save() the configuration. If a file path or object is not specified, the configuration will be written to the file path or object that was first loaded. - + plc.save() plc.save("/etc/planetlab/plc_config.xml") """ @@ -192,10 +191,10 @@ class PLCConfiguration: try: dom = xml.dom.minidom.parse(file) - except ExpatError, e: - raise ConfigurationException, e + except ExpatError as e: + raise ConfigurationException(e) - if type(file) in types.StringTypes: + if isinstance(file, str): self._files.append(os.path.abspath(file)) # Parse section @@ -231,7 +230,7 @@ class PLCConfiguration: else: file = "/etc/planetlab/plc_config.xml" - if type(file) in types.StringTypes: + if isinstance(file, str): fileobj = open(file, 'w') else: fileobj = file @@ -249,7 +248,7 @@ class PLCConfiguration: It looks for filled-in values in the order of, local object (self), followed by cread (read values), and finally default values. - Arguments: + Arguments: default configuration site configuration @@ -263,14 +262,14 @@ class PLCConfiguration: """ validated_variables = {} - for category_id, variable_id in verify_variables.iteritems(): + for category_id, variable_id in verify_variables.items(): category_id = category_id.lower() variable_id = variable_id.lower() variable_value = None sources = (self, read, default) for source in sources: (category_value, variable_value) = source.get(category_id,variable_id) - if variable_value <> None: + if variable_value != None: entry = validated_variables.get(category_id,[]) entry.append(variable_value['value']) validated_variables["%s_%s"%(category_id.upper(),variable_id.upper())]=entry @@ -299,9 +298,9 @@ class PLCConfiguration: 'description': "Variable description" } """ - if self._variables.has_key(category_id.lower()): + if category_id.lower() in self._variables: (category, variables) = self._variables[category_id] - if variables.has_key(variable_id.lower()): + if variable_id.lower() in variables: variable = variables[variable_id] else: variable = None @@ -324,12 +323,12 @@ class PLCConfiguration: variable_id = unique variable identifier (e.g., 'port') """ - if self._variables.has_key(category_id.lower()): + if category_id.lower() in self._variables: (category, variables) = self._variables[category_id] if variable_id is None: category['element'].parentNode.removeChild(category['element']) del self._variables[category_id] - elif variables.has_key(variable_id.lower()): + elif variable_id.lower() in variables: variable = variables[variable_id] variable['element'].parentNode.removeChild(variable['element']) del variables[variable_id] @@ -356,18 +355,19 @@ class PLCConfiguration: 'description': "Variable description" } """ - if not category.has_key('id') or type(category['id']) not in types.StringTypes: + if ('id' not in category + or not isinstance(category['id'], str)): return - + category_id = category['id'].lower() - if self._variables.has_key(category_id): + if category_id in self._variables: # Existing category (old_category, variables) = self._variables[category_id] # Merge category attributes for tag in ['name', 'description']: - if category.has_key(tag): + if tag in category: old_category[tag] = category[tag] self._set_text_of_child(old_category['element'], tag, category[tag]) @@ -377,7 +377,7 @@ class PLCConfiguration: category_element = self._dom.createElement('category') category_element.setAttribute('id', category_id) for tag in ['name', 'description']: - if category.has_key(tag): + if tag in category: self._set_text_of_child(category_element, tag, category[tag]) if self._dom.documentElement.getElementsByTagName('variables'): @@ -392,22 +392,23 @@ class PLCConfiguration: variables = {} self._variables[category_id] = (category, variables) - if variable is None or not variable.has_key('id') or type(variable['id']) not in types.StringTypes: + if (variable is None or 'id' not in variable + or not isinstance(variable['id'], str)): return variable_id = variable['id'].lower() - if variables.has_key(variable_id): + if variable_id in variables: # Existing variable old_variable = variables[variable_id] # Merge variable attributes for attribute in ['type']: - if variable.has_key(attribute): + if attribute in variable: old_variable[attribute] = variable[attribute] old_variable['element'].setAttribute(attribute, variable[attribute]) for tag in ['name', 'value', 'description']: - if variable.has_key(tag): + if tag in variable: old_variable[tag] = variable[tag] self._set_text_of_child(old_variable['element'], tag, variable[tag]) else: @@ -415,12 +416,12 @@ class PLCConfiguration: variable_element = self._dom.createElement('variable') variable_element.setAttribute('id', variable_id) for attribute in ['type']: - if variable.has_key(attribute): + if attribute in variable: variable_element.setAttribute(attribute, variable[attribute]) for tag in ['name', 'value', 'description']: - if variable.has_key(tag): + if tag in variable: self._set_text_of_child(variable_element, tag, variable[tag]) - + if category_element.getElementsByTagName('variablelist'): variablelist_element = category_element.getElementsByTagName('variablelist')[0] else: @@ -441,9 +442,9 @@ class PLCConfiguration: (variable, category) when found (None, None) otherwise """ - - for (category_id, (category, variables)) in self._variables.iteritems(): - for variable in variables.values(): + + for (category_id, (category, variables)) in self._variables.items(): + for variable in list(variables.values()): (id, name, value, comments) = self._sanitize_variable(category_id, variable) if (id == varname): return (category,variable) @@ -464,9 +465,9 @@ class PLCConfiguration: 'type': "mandatory|optional" } """ - if self._packages.has_key(group_id.lower()): + if group_id.lower() in self._packages: (group, packages) = self._packages[group_id] - if packages.has_key(package_name): + if package_name in packages: package = packages[package_name] else: package = None @@ -489,12 +490,12 @@ class PLCConfiguration: package_name - unique package name (e.g., 'postgresql') """ - if self._packages.has_key(group_id): + if group_id in self._packages: (group, packages) = self._packages[group_id] if package_name is None: group['element'].parentNode.removeChild(group['element']) del self._packages[group_id] - elif packages.has_key(package_name.lower()): + elif package_name.lower() in packages: package = packages[package_name] package['element'].parentNode.removeChild(package['element']) del packages[package_name] @@ -520,18 +521,18 @@ class PLCConfiguration: 'type': "mandatory|optional" } """ - if not group.has_key('id'): + if 'id' not in group: return group_id = group['id'] - if self._packages.has_key(group_id): + if group_id in self._packages: # Existing group (old_group, packages) = self._packages[group_id] # Merge group attributes for tag in ['id', 'name', 'default', 'description', 'uservisible']: - if group.has_key(tag): + if tag in group: old_group[tag] = group[tag] self._set_text_of_child(old_group['element'], tag, group[tag]) @@ -540,7 +541,7 @@ class PLCConfiguration: # Merge into DOM group_element = self._dom.createElement('group') for tag in ['id', 'name', 'default', 'description', 'uservisible']: - if group.has_key(tag): + if tag in group: self._set_text_of_child(group_element, tag, group[tag]) if self._dom.documentElement.getElementsByTagName('comps'): @@ -555,17 +556,17 @@ class PLCConfiguration: packages = {} self._packages[group_id] = (group, packages) - if package is None or not package.has_key('name'): + if package is None or 'name' not in package: return package_name = package['name'] - if packages.has_key(package_name): + if package_name in packages: # Existing package old_package = packages[package_name] # Merge variable attributes for attribute in ['type']: - if package.has_key(attribute): + if attribute in package: old_package[attribute] = package[attribute] old_package['element'].setAttribute(attribute, package[attribute]) else: @@ -573,9 +574,9 @@ class PLCConfiguration: packagereq_element = TrimTextElement('packagereq') self._set_text(packagereq_element, package_name) for attribute in ['type']: - if package.has_key(attribute): + if attribute in package: packagereq_element.setAttribute(attribute, package[attribute]) - + if group_element.getElementsByTagName('packagelist'): packagelist_element = group_element.getElementsByTagName('packagelist')[0] else: @@ -636,23 +637,23 @@ class PLCConfiguration: def _sanitize_variable(self, category_id, variable): - assert variable.has_key('id') + assert 'id' in variable # Prepend variable name with category label id = category_id + "_" + variable['id'] # And uppercase it id = id.upper() - if variable.has_key('type'): + if 'type' in variable: type = variable['type'] else: type = None - if variable.has_key('name'): + if 'name' in variable: name = variable['name'] else: name = None - if variable.has_key('value') and variable['value'] is not None: + if 'value' in variable and variable['value'] is not None: value = variable['value'] if type == "int" or type == "double": # bash, Python, and PHP do not require that numbers be quoted @@ -669,7 +670,7 @@ class PLCConfiguration: else: value = None - if variable.has_key('description') and variable['description'] is not None: + if 'description' in variable and variable['description'] is not None: description = variable['description'] # Collapse consecutive whitespace description = re.sub(r'\s+', ' ', description) @@ -702,8 +703,8 @@ DO NOT EDIT. This file was automatically generated at buf = codecs.lookup(encoding)[3](StringIO()) buf.writelines(["# " + line + os.linesep for line in self._header()]) - for (category_id, (category, variables)) in self._variables.iteritems(): - for variable in variables.values(): + for (category_id, (category, variables)) in self._variables.items(): + for variable in list(variables.values()): (id, name, value, comments) = self._sanitize_variable(category_id, variable) if show_comments: buf.write(os.linesep) @@ -727,8 +728,8 @@ DO NOT EDIT. This file was automatically generated at buf.write("|]\tShow Locally modified variables/values + s/S [|]\tShow variables/values (all, in category, single) + e/E [|]\tEdit variables (all, in category, single) +--- + c\t\t\tList categories + v/V [|]\tList Variables (all, in category, single) +--- +Typical usage involves: u, [l,] w, r, q +""" % globals() + +def usage (): + command_usage="%prog [options] [default-xml [site-xml [consolidated-xml]]]" + init_configuration () + command_usage +=""" +\t default-xml defaults to %s +\t site-xml defaults to %s +\t consolidated-xml defaults to %s""" % (def_default_config,def_site_config, def_consolidated_config) + return command_usage + +#################### +variable_usage= """Edit Commands : +#\tShow variable comments +.\tStops prompting, return to mainloop +/\tCleans any site-defined value, reverts to default +=\tShows default value +>\tSkips to next category +?\tThis help +""" + +#################### +def get_value (config, category_id, variable_id): + (category, variable) = config.get (category_id, variable_id) + return variable['value'] + +def get_type (config, category_id, variable_id): + (category, variable) = config.get (category_id, variable_id) + return variable['type'] + +def get_current_value (cread, cwrite, category_id, variable_id): + # the value stored in cwrite, if present, is the one we want + try: + result=get_value (cwrite,category_id,variable_id) + except: + result=get_value (cread,category_id,variable_id) + return result + +# refrain from using plc_config's _sanitize +def get_varname (config, category_id, variable_id): + (category, variable) = config.get (category_id, variable_id) + return (category_id+"_"+variable['id']).upper() + +# could not avoid using _sanitize here.. +def get_name_comments (config, cid, vid): + try: + (category, variable) = config.get (cid, vid) + (id, name, value, comments) = config._sanitize_variable (cid,variable) + return (name,comments) + except: + return (None,[]) + +def print_name_comments (config, cid, vid): + (name,comments)=get_name_comments(config,cid,vid) + if name: + print("### %s" % name) + if comments: + for line in comments: + print("# %s" % line) + else: + print("!!! No comment associated to %s_%s" % (cid,vid)) + +#################### +def list_categories (config): + result=[] + for (category_id, (category, variables)) in config.variables().items(): + result += [category_id] + return result + +def print_categories (config): + print("Known categories") + for cid in list_categories(config): + print("%s" % (cid.upper())) + +#################### +def list_category (config, cid): + result=[] + for (category_id, (category, variables)) in config.variables().items(): + if (cid == category_id): + for variable in list(variables.values()): + result += ["%s_%s" %(cid,variable['id'])] + return result + +def print_category (config, cid, show_comments=True): + cid=cid.lower() + CID=cid.upper() + vids=list_category(config,cid) + if (len(vids) == 0): + print("%s : no such category"%CID) + else: + print("Category %s contains" %(CID)) + for vid in vids: + print(vid.upper()) + +#################### +def consolidate (default_config, site_config, consolidated_config): + global service + try: + conso = PLCConfiguration (default_config) + conso.load (site_config) + conso.save (consolidated_config) + except Exception as inst: + print("Could not consolidate, %s" % (str(inst))) + return + print(("Merged\n\t%s\nand\t%s\ninto\t%s"%(default_config,site_config, + consolidated_config))) + +def reload_service (): + global service + os.system("set -x ; systemctl reload %s" % service) + +#################### +def restart_service (): + global service + print(("==================== Stopping %s" % service)) + os.system("systemctl stop %s" % service) + print(("==================== Starting %s" % service)) + os.system("systemctl start %s" % service) + +#################### +def prompt_variable (cdef, cread, cwrite, category, variable, + show_comments, support_next=False): + + assert 'id' in category + assert 'id' in variable + + category_id = category ['id'] + variable_id = variable['id'] + + while True: + default_value = get_value(cdef,category_id,variable_id) + variable_type = get_type(cdef,category_id,variable_id) + current_value = get_current_value(cread,cwrite,category_id, variable_id) + varname = get_varname (cread,category_id, variable_id) + + if show_comments : + print_name_comments (cdef, category_id, variable_id) + prompt = "== %s : [%s] " % (varname,current_value) + try: + answer = input(prompt).strip() + except EOFError : + raise Exception ('BailOut') + except KeyboardInterrupt: + print("\n") + raise Exception ('BailOut') + + # no change + if (answer == "") or (answer == current_value): + return None + elif (answer == "."): + raise Exception ('BailOut') + elif (answer == "#"): + print_name_comments(cread,category_id,variable_id) + elif (answer == "?"): + print(variable_usage.strip()) + elif (answer == "="): + print(("%s defaults to %s" %(varname,default_value))) + # revert to default : remove from cwrite (i.e. site-config) + elif (answer == "/"): + cwrite.delete(category_id,variable_id) + print(("%s reverted to %s" %(varname,default_value))) + return + elif (answer == ">"): + if support_next: + raise Exception ('NextCategory') + else: + print("No support for next category") + else: + if cdef.validate_type(variable_type, answer): + variable['value'] = answer + cwrite.set(category,variable) + return + else: + print("Not a valid value") + +def prompt_variables_all (cdef, cread, cwrite, show_comments): + try: + for (category_id, (category, variables)) in cread.variables().items(): + print(("========== Category = %s" % category_id.upper())) + for variable in list(variables.values()): + try: + newvar = prompt_variable (cdef, cread, cwrite, category, variable, + show_comments, True) + except Exception as inst: + if (str(inst) == 'NextCategory'): break + else: raise + + except Exception as inst: + if (str(inst) == 'BailOut'): return + else: raise + +def prompt_variables_category (cdef, cread, cwrite, cid, show_comments): + cid=cid.lower() + CID=cid.upper() + try: + print(("========== Category = %s" % CID)) + for vid in list_category(cdef,cid): + (category,variable) = cdef.locate_varname(vid.upper()) + newvar = prompt_variable (cdef, cread, cwrite, category, variable, + show_comments, False) + except Exception as inst: + if (str(inst) == 'BailOut'): return + else: raise + +#################### +def show_variable (cdef, cread, cwrite, + category, variable,show_value,show_comments): + assert 'id' in category + assert 'id' in variable + + category_id = category ['id'] + variable_id = variable['id'] + + default_value = get_value(cdef,category_id,variable_id) + current_value = get_current_value(cread,cwrite,category_id,variable_id) + varname = get_varname (cread,category_id, variable_id) + if show_comments : + print_name_comments (cdef, category_id, variable_id) + if show_value: + print("%s = %s" % (varname,current_value)) + else: + print("%s" % (varname)) + +def show_variables_all (cdef, cread, cwrite, show_value, show_comments): + for (category_id, (category, variables)) in cread.variables().items(): + print(("========== Category = %s" % category_id.upper())) + for variable in list(variables.values()): + show_variable (cdef, cread, cwrite, + category, variable,show_value,show_comments) + +def show_variables_category (cdef, cread, cwrite, cid, show_value,show_comments): + cid=cid.lower() + CID=cid.upper() + print(("========== Category = %s" % CID)) + for vid in list_category(cdef,cid): + (category,variable) = cdef.locate_varname(vid.upper()) + show_variable (cdef, cread, cwrite, category, variable, + show_value,show_comments) + +#################### +re_mainloop_0arg="^(?P[uUwrRqlLsSeEcvVhH\?])[ \t]*$" +re_mainloop_1arg="^(?P[sSeEvV])[ \t]+(?P\w+)$" +matcher_mainloop_0arg=re.compile(re_mainloop_0arg) +matcher_mainloop_1arg=re.compile(re_mainloop_1arg) + +def mainloop (cdef, cread, cwrite, default_config, site_config, consolidated_config): + global service + while True: + try: + answer = input("Enter command (u for usual changes, w to save, ? for help) ").strip() + except EOFError: + answer ="" + except KeyboardInterrupt: + print("\nBye") + sys.exit() + + if (answer == "") or (answer in "?hH"): + print(mainloop_usage) + continue + groups_parse = matcher_mainloop_0arg.match(answer) + command=None + if (groups_parse): + command = groups_parse.group('command') + arg=None + else: + groups_parse = matcher_mainloop_1arg.match(answer) + if (groups_parse): + command = groups_parse.group('command') + arg=groups_parse.group('arg') + if not command: + print(("Unknown command >%s< -- use h for help" % answer)) + continue + + show_comments=command.isupper() + + mode='ALL' + if arg: + mode=None + arg=arg.lower() + variables=list_category (cdef,arg) + if len(variables): + # category_id as the category name + # variables as the list of variable names + mode='CATEGORY' + category_id=arg + arg=arg.upper() + (category,variable)=cdef.locate_varname(arg) + if variable: + # category/variable as output by locate_varname + mode='VARIABLE' + if not mode: + print("%s: no such category or variable" % arg) + continue + + if command in "qQ": + # todo check confirmation + return + elif command == "w": + try: + # Confirm that various constraints are met before saving file. + validate_variables = g_configuration.get('validate_variables',{}) + validated_variables = cwrite.verify(cdef, cread, validate_variables) + validator = g_configuration.get('validator',noop_validator) + validator(validated_variables) + cwrite.save(site_config) + except ConfigurationException as e: + print("Save failed due to a configuration exception: %s" % e) + break; + except: + print(traceback.print_exc()) + print(("Could not save -- fix write access on %s" % site_config)) + break + print(("Wrote %s" % site_config)) + consolidate(default_config, site_config, consolidated_config) + print(("You might want to type 'r' (restart %s), 'R' (reload %s) or 'q' (quit)" % \ + (service, service))) + elif command in "uU": + global usual_variables + try: + for varname in usual_variables: + (category,variable) = cdef.locate_varname(varname) + if not (category is None and variable is None): + prompt_variable(cdef, cread, cwrite, category, variable, False) + except Exception as inst: + if (str(inst) != 'BailOut'): + raise + elif command == "r": + restart_service() + elif command == "R": + reload_service() + elif command == "c": + print_categories(cread) + elif command in "eE": + if mode == 'ALL': + prompt_variables_all(cdef, cread, cwrite,show_comments) + elif mode == 'CATEGORY': + prompt_variables_category(cdef,cread,cwrite,category_id,show_comments) + elif mode == 'VARIABLE': + try: + prompt_variable (cdef,cread,cwrite,category,variable, + show_comments,False) + except Exception as inst: + if str(inst) != 'BailOut': + raise + elif command in "vVsSlL": + show_value=(command in "sSlL") + (c1,c2,c3) = (cdef, cread, cwrite) + if command in "lL": + (c1,c2,c3) = (cwrite,cwrite,cwrite) + if mode == 'ALL': + show_variables_all(c1,c2,c3,show_value,show_comments) + elif mode == 'CATEGORY': + show_variables_category(c1,c2,c3,category_id,show_value,show_comments) + elif mode == 'VARIABLE': + show_variable (c1,c2,c3,category,variable,show_value,show_comments) + else: + print(("Unknown command >%s< -- use h for help" % answer)) + +#################### +# creates directory for file if not yet existing +def check_dir (config_file): + dirname = os.path.dirname (config_file) + if (not os.path.exists (dirname)): + try: + os.makedirs(dirname,0o755) + except OSError as e: + print("Cannot create dir %s due to %s - exiting" % (dirname,e)) + sys.exit(1) + + if (not os.path.exists (dirname)): + print("Cannot create dir %s - exiting" % dirname) + sys.exit(1) + else: + print("Created directory %s" % dirname) + +#################### +def optParserSetup(configuration): + parser = OptionParser(usage=usage()) + parser.set_defaults(config_dir=configuration['config_dir'], + service=configuration['service'], + usual_variables=configuration['usual_variables']) + parser.add_option("","--configdir",dest="config_dir",help="specify configuration directory") + parser.add_option("","--service",dest="service",help="specify /etc/init.d style service name") + parser.add_option("","--usual_variable",dest="usual_variables",action="append", help="add a usual variable") + return parser + +def main(command,argv,configuration): + global g_configuration + g_configuration=configuration + + parser = optParserSetup(configuration) + (config,args) = parser.parse_args() + if len(args)>3: + parser.error("too many arguments") + + configuration['service']=config.service + configuration['usual_variables']=config.usual_variables + configuration['config_dir']=config.config_dir + # add in new usual_variables defined on the command line + for usual_variable in config.usual_variables: + if usual_variable not in configuration['usual_variables']: + configuration['usual_variables'].append(usual_variable) + + # intialize configuration + init_configuration() + + (default_config,site_config,consolidated_config) = (def_default_config, def_site_config, def_consolidated_config) + if len(args) >= 1: + default_config=args[0] + if len(args) >= 2: + site_config=args[1] + if len(args) == 3: + consolidated_config=args[2] + + for c in (default_config,site_config,consolidated_config): + check_dir (c) + + try: + # the default settings only - read only + cdef = PLCConfiguration(default_config) + + # in effect : default settings + local settings - read only + cread = PLCConfiguration(default_config) + + except ConfigurationException as e: + print(("Error %s in default config file %s" %(e,default_config))) + return 1 + except: + print(traceback.print_exc()) + print(("default config files %s not found, is myplc installed ?" % default_config)) + return 1 + + + # local settings only, will be modified & saved + cwrite=PLCConfiguration() + + try: + cread.load(site_config) + cwrite.load(site_config) + except: + cwrite = PLCConfiguration() + + mainloop (cdef, cread, cwrite, default_config, site_config, consolidated_config) + return 0 + if __name__ == '__main__': import sys if len(sys.argv) > 1 and sys.argv[1] in ['build', 'install', 'uninstall']: