import xml.parsers.expat,copy,base64,bindpyrame,re,ast,socket,threading,os
current_node=None
current_param=None
top_module=None
default_node=None
config_filename=None
module_dict={}
#********************* PYRAME FUNCTIONS **************************
def init():
global default_node
retcode,res=parse_file("/opt/pyrame/calxml_defaults.xml",None)
if retcode==0:
print("error in parsing default file...exiting")
submod.exit(1)
default_node=top_module
#pyrapi:reload_default_cmod:
[docs]def reload_default_cmod():
"""reload the default parameters file"""
global default_node,top_module
default_node=None
retcode,res=parse_file("/opt/pyrame/calxml_defaults.xml",None)
if retcode==0:
default_node=top_module=None
return 0,"parse error"
default_node=top_module
return 1,"ok"
#pyrapi:load_config_file_cmod:filename
[docs]def load_config_file_cmod(filename):
"""load a configuration from an xml file"""
global top_module,config_filename,default_node
checkxml=os.system("xmllint %s 2>&1 > /dev/null"%(filename))
if checkxml!=0:
return 0,"syntax error in xml file %s"%(filename)
retcode,res=parse_file(filename,default_node)
if retcode==0:
top_module=None
config_filename=None
return 0,"parse error : %s"%(res)
config_filename=filename
retcode,res=top_module.register()
if retcode==0:
return 0,"cant register modules : %s"%(res)
return 1,"ok"
#pyrapi:get_config_origin_cmod:
def get_config_origin_cmod():
global top_module,config_filename
if top_module.type=="defaultConfig":
# GUIs depend on this exact response
return 1,"None"
if config_filename==None:
return 1,"serialized config"
return 1,config_filename
#pyrapi:gener_config_cmod:
[docs]def gener_config_cmod():
"""extract the configuration from the tree and aggregate it in a XML file"""
res="<?xml version=\"1.0\" encoding=\"UTF-8\"?>"+top_module.export_xml()
return 1,res
#pyrapi:gener_configb64_cmod:
[docs]def gener_configb64_cmod():
"""extract the configuration from the tree and aggregate it in a XML file encoded in base 64"""
res="<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+top_module.export_pretty_xml(" ")
return 1,base64.b64encode(res)
#pyrapi:save_config_cmod:filename
def save_config_cmod(filename):
if not filename.endswith(".xml"):
filename+=".xml"
retcode,res=gener_configb64_cmod()
with open(filename,"w") as f:
f.write(base64.b64decode(res))
return 1,"ok"
#thread to apply a sequence of phases asynchronously
def transition_t(bg_context,node,goto,transition_name,transition_fallback,params_dict):
retcode,res=command_cmod(node.name,"false","get_seq_transition_"+node.type,transition_name,goto)
if retcode==0:
submod_sendres(bg_context,0,"error getting transition phases from top_module: %s"%(res))
return
if res!="":
phases=res.split(",")
for phase in phases:
retcode,res=node.apply_phase(phase,params_dict)
if retcode==0:
init_error=res
if transition_fallback!="undef":
retcode,res=command_cmod(node.name,"false","get_seq_transition_"+node.type,transition_fallback,"1")
if res!="":
phases=res.split(",")
for phase in phases:
retcode,res=node.apply_phase(phase,params_dict)
submod_sendres(bg_context,0,init_error)
return
submod_sendres(bg_context,1,"ok")
def apply_phase_t(bg_context,node,phase_name,params_dict):
retcode,res=node.apply_phase(phase_name,params_dict)
submod_sendres(bg_context,retcode,res)
[docs]def apply_phase_cmod(dev_name,phase_name,params=""):
"""apply a phase with name phase_name on the subtree with root dev_name. params is an empty string or a json dictionary containing values that can be passed to modules during the phase"""
try:
node=module_dict[dev_name]
except:
return 0,"unknown device %s"%(dev_name)
#convert external params string to dictionary
if params!="":
params_dict=ast.literal_eval(params)
else:
params_dict={}
#launch a thread to apply the necessary phases
retcode,res=submod_bgcontext()
t=threading.Thread(target=apply_phase_t,args=(res,node,phase_name,params_dict))
t.start()
#finish synchronous part
return KEEPOPEN,"ok"
#pyrapi:transition_cmod:goto,transition_name,transition_fallback,params
[docs]def transition_cmod(dev_name,goto,transition_name,transition_fallback,params=""):
"Use *transition_name* to a system described by the loaded configuration. If the transition fails, use *transition_fallback*. The optional JSON-encoded *params* value adds named parameters to the phases."
#check that the node exists
try:
node=module_dict[dev_name]
except:
node=None
if node==None:
return 0,"unknown device %s"%(dev_name)
#convert external params string to dictionary
if params!="":
params_dict=ast.literal_eval(params)
else:
params_dict={}
#launch a thread to apply the necessary phases
retcode,res=submod_bgcontext()
t=threading.Thread(target=transition_t,args=(res,node,goto,transition_name,transition_fallback,params_dict))
t.start()
#finish synchronous part
return KEEPOPEN,"ok"
def sendcmd_t(bg_context,ip,port,func,name,params):
retcode,res=bindpyrame.sendcmd(ip,port,func,name,*params)
submod_sendres(bg_context,retcode,res)
#pyrapi:command_cmod:dev_name,async,func,*params
[docs]def command_cmod(dev_name,async,func,*params):
"""apply a command on a device. If async is true, the command is done asynchronously."""
try:
node=module_dict[dev_name]
except:
node=None
if node==None:
return 0,"unknown device %s"%(dev_name)
if sbool(async):
retcode,res=submod_bgcontext()
t=threading.Thread(target=sendcmd_t,args=(res,node.ip,node.port,func,node.name,params))
t.start()
#finish synchronous part
return KEEPOPEN,"ok"
else:
return bindpyrame.sendcmd(node.ip,node.port,func,node.name,*params)
#pyrapi:set_param_cmod:dev_name,param_name,param_value
[docs]def set_param_cmod(dev_name,param_name,param_value):
"""add or modify a parameter for a device"""
try:
node=module_dict[dev_name]
except:
node=None
if node==None:
return 0,"unknown device %s"%(dev_name)
else:
try:
test=node.effective_params[param_name]
except:
test=None
if test==None:
return 0,"unknown parameter %s for device %s"%(param_name,dev_name)
node.effective_params[param_name]=param_value
return 1,"ok"
#pyrapi:get_param_cmod:dev_name,param_name
[docs]def get_param_cmod(dev_name,param_name):
"""get a parameter from a device"""
try:
node=module_dict[dev_name]
except:
node=None
if node==None:
return 0,"unknown device %s"%(dev_name)
else:
if param_name in node.effective_params:
return 1,node.effective_params[param_name]
else:
return 0,"unknown param %s"%(param_name)
#pyrapi:get_type_cmod:dev_name
[docs]def get_type_cmod(dev_name):
"""find the type of a device"""
try:
node=module_dict[dev_name]
except:
node=None
if node==None:
return 0,"unknown device %s"%(dev_name)
else:
return 1,node.type
#pyrapi:get_ip_cmod:dev_name
[docs]def get_ip_cmod(dev_name):
"""find the ip of a device"""
try:
node=module_dict[dev_name]
except:
node=None
if node==None:
return 0,"unknown device %s"%(dev_name)
else:
return 1,node.ip
#pyrapi:get_location_cmod:dev_name
[docs]def get_location_cmod(dev_name):
"get ip and type of a device"
try:
node=module_dict[dev_name]
except:
node=None
if node==None:
return 0,"unknown device %s"%(dev_name)
else:
return 1,"%s,%s"%(node.ip,node.type)
#pyrapi:get_name_list_cmod:dev_type
[docs]def get_name_list_cmod(dev_type):
"""get the list of all device names of a *dev_type*"""
return 1,",".join(top_module.enumerate(dev_type))
#pyrapi:get_name_sublist_cmod:dev_type,parent_name
[docs]def get_name_sublist_cmod(dev_type,parent_name):
"""get the list of device names of a *dev_type* type with *parent_name* parent"""
try:
node=module_dict[parent_name]
except:
node=None
if node==None:
return 0,"unknown device %s"%(parent_name)
return 1,",".join(node.enumerate(dev_type))
#pyrapi:get_config_list_cmod:
def get_config_list_cmod():
conf_path="/opt/pyrame/config"
lf=os.listdir(conf_path)
res=[]
for f in lf:
if f.endswith(".xml"):
res.append("%s/%s"%(conf_path,f))
return 1,",".join(res)
#pyrapi:is_enabled_cmod:dev_name
def is_enabled_cmod(dev_name):
try:
node=module_dict[dev_name]
except:
node=None
if node==None:
return 0,"unknown device %s"%(dev_name)
return 1,str(node.enabled)
#********************* PARSER FUNCTIONS **************************
def parser_opening_tag(tag,attributes):
global current_node,top_module,module_dict,current_param
if tag=="param":
current_param=attributes["name"]
current_node.legacy_params[current_param]=""
else: #creating a new node
if "name" not in attributes:
print("error : object %s with no name"%(tag))
name="undef"
else:
name=attributes["name"]
if name in module_dict:
print("error : object %s is duplicated"%(name))
enabled=1
if "disabled" in attributes:
if sbool(attributes["disabled"])==1:
enabled=0
else:
enabled=1
if "vital" in attributes:
vital=int(attributes["vital"])
else:
vital=1
print("new node %s"%(name))
newnode=node(current_node,tag,name,vital,enabled)
current_node=newnode
if top_module==None:
top_module=newnode
module_dict[name]=newnode
def parser_closing_tag(tag):
global current_node,current_param
if tag=="param":
current_param=None
else:
if current_node.parent!=None:
current_node.parent.children.append(current_node)
current_node.create_implicit()
#print("closing node %s"%(current_node.name))
current_node=current_node.parent
def parser_data(value):
global current_node
if current_param!=None:
current_node.legacy_params[current_param]=value
#print("filling param %s with %s"%(current_param,value))
else:
print("error param value outside param value=%s"%(value))
def strip_file(filename):
try:
file=open(filename,'read')
content=""
for line in file.readlines():
content+=line.strip()
file.close()
return content
except:
print("error : unknown file %s"%(filename))
return ""
def parse_file(filename,root):
#clean global variables
global current_node,current_param,top_module,default_node,module_dict
current_node=default_node
current_param=None
top_module=None
if default_node!=None:
default_node.children=[]
module_dict={}
#parse the file
parser=xml.parsers.expat.ParserCreate()
parser.StartElementHandler=parser_opening_tag
parser.EndElementHandler=parser_closing_tag
parser.CharacterDataHandler=parser_data
content=strip_file(filename)
if content=="":
return 0,"unknown file %s"%(filename)
try:
parser.Parse(content)
except Exception as e:
print("syntax error : %s"%(str(e)))
return 0,str(e)
#compute all effective or secondary parameters
top_module.calc_effective_params()
module_dict["root"]=top_module
return 1,"ok"
#********************* INTERNAL FUNCTIONS ******************
#********************* NODE CLASS **************************
class node:
def __init__(self,parent,type,name,vital,enabled):
self.parent=parent #contain the parent node
self.name=name #the name of the node (corresponding to the name attribute in xml file)
self.type=type #the type of the node (corresponding to the tag in xml file)
self.port=submod.getport(self.type) #contain the numeric port of the node
if self.name.startswith(self.type):
self.postfix=self.name[len(self.type):]
else:
self.postfix="_"+self.name
if parent!=None:
self.legacy_params=copy.deepcopy(self.parent.legacy_params)
else:
self.legacy_params={} #dictionary containing all the param from file + all params inherited from parent node
self.effective_params={} #dictionary containing the effective values of the parameters (after calculation of formulae)
self.vital=vital #flag indicating if this node is vital for the system
self.enabled=enabled # flag indicating if this node is disabled
self.children=[] #contain a list of the children nodes
self.port=-1
def register(self):
if self.type!="domain":
#print("register module %s at %s:%d"%(self.name,self.ip,self.port))
retcode,res=bindpyrame.sendcmd(self.ip,self.port,"register_module")
if retcode==0:
print("error : cant register %s : %s"%(self.name,res))
return 0,"cant register %s on port %d: %s"%(self.name,self.port,res)
for c in self.children:
retcode,res=c.register()
if retcode==0:
return 0,res
return 1,"ok"
def find_ip(self):
if self.parent==None:
return "127.0.0.1"
if self.type=="domain":
return socket.gethostbyname(self.effective_params["ip"])
return self.parent.find_ip()
def enumerate(self,type):
res=[]
for c in self.children:
res+=c.enumerate(type)
if self.type==type:
res.append(self.name)
return res
def count(self,type):
if self.type==type:
res=1
else:
res=0
for c in self.children:
res+=c.count(type)
return res
def create_implicit(self):
for p in self.legacy_params:
if "%s_nb_"%(self.type) in p:
submodule="_".join(p.split("_")[2:])
if self.count(submodule)==0 and int(self.legacy_params[p])!=0:
print("implicit creation for %d submodule %s"%(int(self.legacy_params[p]),submodule))
for i in range(1,int(self.legacy_params[p])+1):
name=submodule+self.postfix+"_%d"%(i)
newchild=node(self,submodule,name,self.vital,self.enabled)
self.children.append(newchild)
module_dict[name]=newchild
newchild.create_implicit()
def interpret(self,chain):
interpret_regex=re.compile(r"\$\{[^\}]*\}")
split_postfix=self.postfix.strip("_").split("_")
hexa=False
found=interpret_regex.findall(chain)
interpreted=chain
for f in found:
#replace count type param
if f[2:7]=="count":
param=f[8:-2]
cnt=top_module.count(param)
interpreted=interpreted.replace(f,str(cnt))
continue
#TODO add a $value(param_name) which replace the name by the value of the param
#replace eval type param
#print("replacing %s"%(f))
val=f[2:-1]
#print("split postfix=",split_postfix)
for i in range(1,len(split_postfix)+1):
#print("replacing for i=%d"%(i))
if val.find('nx')>-1 or val.find('0x')>-1:
hexa=True
val=val.replace(("nd%s"%i),split_postfix[i-1])
val=val.replace(("nx%s"%i),split_postfix[i-1])
if hexa:
interpreted=interpreted.replace(f,str(hex(eval(val)))[2:])
else:
interpreted=interpreted.replace(f,str(eval(val)))
return interpreted
def calc_effective_params(self):
#print("effective params calc for %s"%(self.type))
#interpret all relevant legacy params and insert them in effective params
for p in self.legacy_params.keys():
if p[0:len(self.type)+1]==self.type+"_" in p:
ep=p[len(self.type)+1:]
self.effective_params[ep]=self.interpret(self.legacy_params[p])
#print("effective %s=%s"%(ep,self.effective_params[ep]))
#get the ip and port of the module
self.ip=self.find_ip()
self.port=submod_getport(self.type)
#print("ip:port for %s is %s:%d"%(self.name,self.ip,self.port))
#call the procedure on children
for c in self.children:
c.calc_effective_params()
def apply_phase(self,phase_name,ext_params):
#execute init apc command
retcode,res=exec_apc(self,phase_name,ext_params,0)
if retcode==0:
if self.vital==0:
return 1,"partial : %s"%(res)
else:
return 0,"fail : %s"%(res)
#apply the phase on all children
partial=0
partialmsg=""
for c in self.children:
retcode,res=c.apply_phase(phase_name,ext_params)
if retcode==0:
if self.vital==0:
return 1,"partial : %s"%(res)
else:
return 0,"fail : %s"%(res)
else:
if res[0:7]=="partial":
partial=1
if partialmsg=="":
partialmsg=res
else:
partialmsg=partialmsg+","+msg
#execute final apc command
retcode,res=exec_apc(self,phase_name,ext_params,1)
if retcode==0:
#CAUTION : the module depend on this messages dont change their values
if self.vital==0:
return 1,"partial : %s"%(res)
else:
return 0,"fail : %s"%(res)
#return final result
if partial==1:
return 1,"partial : %s"%(partialmsg)
else:
return 1,"full"
def export_xml(self):
res="<%s name=\"%s\">"%(self.type,self.name)
for p in self.effective_params:
if "nb_" not in p:
res=res+"<param name=\"%s_%s\">%s</param>"%(self.type,p,self.effective_params[p])
for c in self.children:
subres=c.export_xml()
if subres!="":
res=res+"%s"%(subres)
res=res+"</%s>"%(self.type)
return res
def export_pretty_xml(self,indent):
res="%s<%s name=\"%s\">\n"%(indent,self.type,self.name)
for p in self.effective_params:
if "nb_" not in p:
res+="%s <param name=\"%s_%s\">%s</param>\n"%(indent,self.type,p,self.effective_params[p])
for c in self.children:
res+=c.export_pretty_xml(indent+" ")
res+="%s</%s>\n"%(indent,self.type)
return res
#********************* APC FUNCTIONS ******************
def apc_build_param_list(api,node,ext_params):
#build the params list
params=[]
for a in api:
if a=="dev_name":
params.append(node.name)
continue
if a=="%s_id"%(node.type):
params.append(node.name)
continue
if a=="parent":
params.append(node.parent.name)
continue
if a=="childs":
params.append(node.childs_name())
continue
if a=="childs_details":
params.append(node.childs_details())
continue
if a=="nb_childs":
params.append(node.nb_childs())
continue
if a in ext_params:
params.append(ext_params[a])
continue
if a in node.effective_params:
params.append(node.effective_params[a])
continue
print("unknown param : %s"%(a))
return "unknown param : %s"%(a)
return params
[docs]def exec_apc(node,phase_name,ext_params,fin):
"""this function apply a a phase to a device"""
if node.type=="domain":
return 1,"ok"
if node.enabled==0:
return 1,"ok"
if fin:
print("===> init_apc_fin for a %s with name %s on phase %s" %(node.type,node.name,phase_name))
else:
print("===> init_apc for a %s with name %s on phase %s" %(node.type,node.name,phase_name))
#extract the function
if fin==0:
func="%s_%s"%(phase_name,node.type)
else:
func="%s_fin_%s"%(phase_name,node.type)
#extract the api
retcode,res=bindpyrame.sendcmd(node.ip,node.port,"getfuncapi_module",func)
if retcode==0:
print("no function with name %s in module %s"%(func,node.type))
return 1,"no function with that name"
print("obtained api=%s for func %s"%(res,func))
api=res.split(",")
#build the params list
params=apc_build_param_list(api,node,ext_params)
if type(params)==str:
return 0,"error while executing %s <- %s"%(func,params)
#send the command
retcode,res=bindpyrame.sendcmd(node.ip,node.port,func,*params)
#print("-->result %d,%s"%(retcode,res))
if retcode==0:
return 0,"error while executing %s <- %s"%(func,res)
else:
return 1,"ok"