import socket import subprocess import sys import threading import time import datetime import json import os import signal import re import shutil import tempfile import tarfile import apt_pkg import lliurex.net class LmdServer: def __init__(self): self.last_job_id=0 self.joblist=[] self.global_listeners = {} self.thread_jobs = {} # Threading Multicast process to all listeners self.multicast_thread = threading.Thread() self.logfolder = '/run/lmdserver' self.ltsp_path = '/opt/ltsp' self.locks = {} # Clean log folder and create if (os.path.exists(self.logfolder)): shutil.rmtree(self.logfolder) os.mkdir(self.logfolder) pass #def __init__ def set_default_boot(self,imgid): dbm = objects['LmdBootManager'].getDefaultBootImage() if dbm['default_boot'] == "": objects['LmdBootManager'].setDefaultBootImage(imgid) def check_chroot_exists(self, name): ''' Check if exists chroot /opt/ltsp/name and /opt/ltsp/images/name.img DATE: 03/04/2014 ''' path=os.path.join("/opt/ltsp",name) imagepath=os.path.join("/opt/ltsp/images",name+".img") #print "cheking "+name if(os.path.isdir(path)): #print "111" return {"status": True, "error": 'chroot_exists'} if (os.path.isfile(imagepath)): #print "22222" return {"status": True, "error": 'image_exists'} return {"status": False} def create_imageWS(self, imgid, name, template, description='', bgimg='', arch='i386', env='', extraopts=''): try: # creates an image from an specified template # Check if template exists path="/etc/ltsp/templates/"+template if(os.path.isfile(path)): cancelcommand = '' extraLliurexOpts="--apt-keys /usr/share/keyrings/lliurex-archive-keyring.gpg --accept-unsigned-packages --purge-chroot --lliurex-sourceslist" + extraopts # if template exists, create an image print("[LmdServer] Create_imageWS from "+path) command="ltsp-build-client --config "+path+" "+extraLliurexOpts+" --chroot "+imgid+"; systemctl restart nbd-server; ltsp-set-domain-search-ltsconf" if env != '' : command = env + " " + command if extraLliurexOpts.find('--isopath') > 0: cancelcommand = "lmd-from-iso-clean " + extraLliurexOpts command = command + "; lmd-fix-architecture " + imgid ret=objects['TaskMan'].newTask(command, cancelcommand) if ret["status"]==True: ## Task has launched ok metadata = {'id':imgid, 'name' : name, 'desc' : description , "template" : template, 'img': bgimg, 'arch': arch, 'taskid': ret["msg"], 'ltsp_fatclient': 'undefined', 'ldm_session': 'default', 'fat_ram_threshold': 'default', 'lmd_extra_params':'' } metadata_string = unicode(json.dumps(metadata,indent=4,encoding="utf-8",ensure_ascii=False)).encode("utf-8") objects['LmdImageManager'].setImage(imgid,metadata_string) self.set_default_boot(imgid) # Registering imgid for boot PXE MEnu label="ltsp_label"+str(imgid) objects['LlxBootManager'].pushToBootList(label) # set this new image as the default boot option if hdd is the current option boot_order=objects['LlxBootManager'].getBootOrder() if len(boot_order)>0 and boot_order[0]=="bootfromhd": new_boot_order=objects['LlxBootManager'].prependBootList(label) return {"status": True, "msg": ret["msg"]} else: if ret["msg"]=="SERVER_BUSY": return {'status':False, 'msg':'SERVER_BUSY'} else: return {'status':False, 'msg':'EXCEPTION'} else: return {'status':False, 'msg':'TEMPLATE_NOT:EXISTS'} pass except Exception as e: print("Except: "+str(e)) return e def create_image(self, ip, port, imgid, name, template, description='', bgimg='', srv_ip='127.0.0.1'): try: # creates an image from an specified template # Check if template exists path="/etc/ltsp/templates/"+template if(os.path.isfile(path)): extraLliurexOpts="--apt-keys /usr/share/keyrings/lliurex-archive-keyring.gpg --accept-unsigned-packages --purge-chroot" # if template exists, create an image print("[LmdServer] Create_image from "+path) command="ltsp-build-client --config "+path+" "+extraLliurexOpts+" --chroot "+imgid+"&& service nbd-server restart"; metadata = {'id':imgid, 'name' : name, 'desc' : description , "template" : template, 'img': bgimg, 'ltsp_fatclient': 'undefined', 'ldm_session': 'default', 'fat_ram_threshold': 'default', 'lmd_extra_params':'' } metadata_string = unicode(json.dumps(metadata,indent=4,encoding="utf-8",ensure_ascii=False)).encode("utf-8") objects['LmdImageManager'].setImage(imgid,metadata_string) self.set_default_boot(imgid) #command="cat "+path+"; sleep 15; cat "+path+"; echo 'command is: ltsp-build-client --config "+path+" "+extraLliurexOpts+" --chroot "+imgid+"'"; #command="/tmp/miscript" #command="tree /" result=self.do_command(ip, port, command, srv_ip,target=imgid) # Registering imgid for boot PXE MEnu if (result==0): objects['LlxBootManager'].pushToBootList("ltsp_label"+str(imgid)) else: # Image has not been built correctly metadata = {'id':imgid, 'name' : name, 'desc' : description , "template" : template, 'img': bgimg, 'ltsp_fatclient': 'undefined', 'ldm_session': 'default', 'fat_ram_threshold': 'default', 'status':'broken', 'lmd_extra_params':'' } return result else: return "{'status':'false', 'error':'template does not exists'}" pass except Exception as e: print ("Except: "+str(e)) return e def refresh_image(self, ip, username, password, port, imgid, srv_ip='127.0.0.1'): # Rebuild img file for imgid import xmlrpclib try: # umount anything path="/opt/ltsp/"+imgid server = xmlrpclib.ServerProxy("https://localhost:9779") server.umount_chroot((username,password),'LmdImageManager',path) # Let's rebuild image print ("[LmdServer] Refreshing image for "+str(imgid)) # Trying to solve the non-zenity problem # command="ltsp-chroot -p -m -a "+imgid+" dpkg-reconfigure libgl1-mesa-dri ; " command=command + "ltsp-chroot -p -m -a "+imgid+" /usr/share/ltsp/update-kernels && " command=command + "ltsp-update-kernels "+imgid+" && ltsp-update-image "+imgid+"&& service nbd-server restart"; result=self.do_command(ip, port, command, srv_ip,target=imgid) objects['LlxBootManager'].pushToBootList("ltsp_label"+str(imgid)) return {"status": True, "msg": 'Image Updated'} pass except Exception as e: print ("Except: "+str(e)) return {"False": True, "msg": str(e)} def refresh_imageWS(self, imgid, delay = ''): try: # umount anything path="/opt/ltsp/"+imgid objects['LmdImageManager'].umount_chroot(path); # read json json_file = open('/etc/ltsp/images/' + imgid + '.json','r') jsonClient=json.loads(json_file.read()) json_file.close() arch="linux32" # Ask for arch in image description if ('arch' in jsonClient): arch="linux64" if jsonClient['arch']=="i386": arch="linux32" else: # If arch does not exists, let's ask it to chroot cmd="ltsp chroot -p -m -a "+imgid+" uname -m" proc=subprocess.Popen("uname -m", stdout=subprocess.PIPE, shell=True) (out, err) = proc.communicate() machine2arch = {'AMD64': 'linux64', 'x86_64': 'linux64'} arch=machine2arch.get(out.strip("\n"), "linux32") command=arch + " ltsp-chroot -p -m -a "+imgid+" dpkg-reconfigure libgl1-mesa-dri ; " command=command + arch + " ltsp-chroot -p -m -a "+imgid+" /usr/share/ltsp/update-kernels && " if delay != '' : command = command + " lmd-update-image-delay " + imgid + " '" + delay +"'" else: command = command + arch + " ltsp-update-image " + imgid command = command + "; systemctl restart nbd-server; ltsp-set-domain-search-ltsconf" # Let's rebuild image ret=objects['TaskMan'].newTask(command) if ret["status"]==True: ## Task has launched ok print ("[LmdServer] Refreshing image for "+str(imgid)) # we does not have to push to boot list! Bug fixed! #objects['LlxBootManager'].pushToBootList("ltsp_label"+str(imgid)); # Setting new taskid to the image, so we know that it is refreshing objects['LmdImageManager'].setNewTaskIdForImage(imgid, ret["msg"]); return {"status": True, "msg": ret["msg"]} # Returns task id!! else: if ret["msg"]=="SERVER_BUSY": return {'status':False, 'msg':'SERVER_BUSY'} else: return {'status':False, 'msg':'EXCEPTION'} except Exception as e: print ("Except: "+str(e)) return {"False": True, "msg": str(e)} def export_image(self, ip, port, imgid, srv_ip='127.0.0.1'): # Deprecated: Replaced by CloneOrExportWS try: # creates an image from an specified template # Check if template exists print ("[LmdServer] Export_image from "+imgid) name_file = str(imgid) + "_" + time.strftime("%Hh%Mm%Ss_%d%m%Y") + '.tgz' command="ltsp-import-export export /tmp/"+ name_file +" "+str(imgid); result=self.do_command(ip, port, command, srv_ip,target=imgid) return {"status": True, "msg": str(result)} pass except Exception as e: print ("Except: "+str(e)) return {"status": False, "msg": str(e)} def import_image(self, ip, port, imgid, path, srv_ip='127.0.0.1'): # Deprecated: Replaced by CloneOrExportWS and extra apps... try: import xmlrpclib client=xmlrpclib.ServerProxy("https://127.0.0.1:9779") n4d_server_ip=client.get_variable("", "VariablesManager", "SRV_IP") #if(ip==srv_ip) print ("COMPARING: "+lliurex.net.get_ip_from_host(ip)+ "and "+ n4d_server_ip); if(lliurex.net.get_ip_from_host(ip)==n4d_server_ip or ip==srv_ip): print ("[LmdServer] Import_image from ",path, " to ", imgid) tar = tarfile.open(path) l = tar.getnames() folder = l[0] imgid = folder f = tar.extractfile(folder + '/' + folder + '.json') exec("json_content="+"".join(f.readlines())) try: new_name = folder count = 1 while os.path.exists(os.path.join(self.ltsp_path,new_name)): new_name = folder + "_" + str(count) count += 1 extra_opts = "" if folder != new_name: extra_opts = new_name except Exception as e: extra_opts = "" command="ltsp-import-export import "+str(path)+" "+folder+" "+extra_opts+" && service nbd-server restart"; if extra_opts != "": imgid = extra_opts json_content['id'] = imgid json_content['name'] = imgid json_file = open('/etc/ltsp/images/' + imgid + '.json','w') data = unicode(json.dumps(json_content,indent=4,encoding='utf-8',ensure_ascii=False)).encode('utf-8') json_file.write(data) json_file.close() self.set_default_boot(imgid) result=self.do_command(ip, port, command, srv_ip,target=imgid) return {"status": True, "msg": str(result)} pass else: print ("[LmdServer] ERROR. Trying to import image from out of server") return {"status": False, "msg": "Not in server"} except Exception as e: print ("Except: "+str(e)) return {"status": False, "msg": str(e)} def CreateImgJSON(self, imgid, newLabel, newid, description, path): #self.CreateImgJSON(targetid, newLabel, newid, newDesc, new_json_descriptor_file); try: # Removing previous .json into image files=os.listdir('/opt/ltsp/'+imgid ); for i in files: f,ext=os.path.splitext(i) if (ext==".json"): os.remove('/opt/ltsp/' +imgid+"/"+i); # Create and copy new json json_file = open('/etc/ltsp/images/' + imgid + '.json','r') jsonClient=json.loads(json_file.read()); json_file.close(); #print jsonClient; jsonClient['name']=newLabel; jsonClient['id']=newid; jsonClient['desc']=description; json_file = open(path,'w') metadata_string = unicode(json.dumps(jsonClient,indent=4,encoding="utf-8",ensure_ascii=False)).encode("utf-8") json_file.write(metadata_string); json_file.close(); return {"status": True, "msg": "Saved"} except Exception as e: print ("Except: "+str(e)) return {"status": False, "msg": str(e)} def CloneOrExportWS(self, targetid, newid, newLabel, newDesc, is_export): ''' Export or Clone an image via web # New Version of import/export # Last Modification: 23/09/2016 ''' new_json_descriptor_file="/tmp/"+newid+".json"; original_path_image="/opt/ltsp/"+targetid; self.CreateImgJSON(targetid, newLabel, newid, newDesc, new_json_descriptor_file); if str(is_export)=="True": #Export to a tar.gz with lmdimg extension #command="lmd-export-img.sh "+targetid+" "+newLabel+" "+newDesc; command="lmd-export-img.sh "+new_json_descriptor_file+" "+original_path_image+" "+newid+".lmdimg" else: #command="lmd-clone-img.sh "+targetid+" "+newLabel+" "+newDesc; command="echo "+targetid+" "+original_path_image+" "+newid; command="lmd-clone-img.sh "+new_json_descriptor_file+" "+original_path_image+" "+newid; try: ret=objects['TaskMan'].newTask(command); if ret["status"]==True: ## Task has launched ok # Returns true and ret.msg, that is job id print ("**********************************************"); return {"status": True, "msg": ret["msg"]} pass else: if ret["msg"]=="SERVER_BUSY": return {'status':False, 'msg':'SERVER_BUSY'} else: return {'status':False, 'msg':'EXCEPTION'} except Exception as e: print ("Except: "+str(e)) return {"status": False, "msg": str(e)} return {"status": True, "msg": "Done"} def ImportImageWS(self, filename): command="lmd-import-from-admin-center.sh "+filename; try: ret=objects['TaskMan'].newTask(command); if ret["status"]==True: ## Task has launched ok # Returns true and ret.msg, that is job id return {"status": True, "msg": ret["msg"]} pass else: if ret["msg"]=="SERVER_BUSY": return {'status':False, 'msg':'SERVER_BUSY'} else: return {'status':False, 'msg':'EXCEPTION'} except Exception as e: print ("Except: "+str(e)) return {"status": False, "msg": str(e)} return {"status": True, "msg": "Done"} pass def getExportedList(self): exported_path="/var/www/exported"; try: list=os.listdir(exported_path) return {"status":True, "msg":list} pass except Exception as e: return {"status":False, "msg": str(e)} pass def deploy_minimal_client(self, ip, port, srv_ip='127.0.0.1'): # DEPRECATED: Replaced by deploy_minimal_clientWS try: command="/usr/sbin/mini-light-client.py"; imgid="mini-light-client" lng="es_ES.UTF-8" language="es_ES" # After that, set image as available metadata = {'status':'enabled-non-editable', 'id':'mini-light-client', 'name':'Client Lleuger Minim', 'template':'Default by LMD', 'desc':'Minimal thin client -not fat- for Lliurex LTSP.', 'img':'llx-client16.png', 'ltsp_fatclient': 'false', 'ldm_session': 'plasma', 'fat_ram_threshold': 'default', 'lmd_extra_params':'XKBLAYOUT=es LDM_LANGUAGE="%s" LOCALE_DIR=%s'%(lng,language)} metadata_string = unicode(json.dumps(metadata,indent=4,encoding="utf-8",ensure_ascii=False)).encode("utf-8") objects['LmdImageManager'].setImage('mini-light-client',metadata_string) self.set_default_boot(imgid) result=self.do_command(ip, port, command, srv_ip,target=imgid) if result==0: # Step 2. Writing lts.conf for mini-light-client print ("[LMDServer] Writing lts.conf for mini-light-client") if not os.path.exists('/var/lib/tftpboot/ltsp/mini-light-client'): os.makedirs('/var/lib/tftpboot/ltsp/mini-light-client') f=open('/var/lib/tftpboot/ltsp/mini-light-client/lts.conf', 'w') f.write('[Default]\n') f.write('LDM_LANGUAGE=es_ES.UTF-8\n') f.write('LTSP_FATCLIENT=false\n') f.write('DEFAULT_DISPLAY_MANAGER="/usr/sbin/ldm"\n') f.close() # Step 3. Registering imgid for boot PXE menu objects['LlxBootManager'].pushToBootList("ltsp_label"+str(imgid)); try: f=open("/etc/default/locale") lines=f.readlines() f.close() for line in lines: if "LANG=" in line: lng=line.strip().split("=")[1].strip("'").strip('"') if "es_ES" in lng: language="es" if "ca_ES" in lng: language="ca_ES@valencia" except: return {"status": False, "msg": str(result)} pass return {"status": True, "msg": str(result)} pass else: # result is not 0 -> Image creation cancelled return {"status": True, "msg": str(result)} pass except Exception as e: print ("Except: "+str(e)) return {"status": False, "msg": str(e)} #def deploy_minimal_clientWS(self, ws): def deploy_minimal_clientWS(self): #ws=objects['TaskMan'].getWS() -> no cal?? try: command="/usr/sbin/mini-light-client.py"; #command="tasktest hola"; #result=self.do_command(ip, port, command, srv_ip,target=imgid) ret=objects['TaskMan'].newTask(command); if ret["status"]==True: ## Task has launched ok #print ret["status"] # Writing json metadata in /etc/ltsp/images/ imgid="mini-light-client" lng="es_ES.UTF-8" language="es_ES" # After that, set image as available metadata = {'status':'enabled-non-editable', 'taskid': ret["msg"], 'id':'mini-light-client', 'name':'Client Lleuger Minim', 'template':'Default by LMD', 'desc':'Minimal thin client -not fat- for Lliurex LTSP.', 'img':'llx-client16.png', 'ltsp_fatclient': 'false', 'ldm_session': 'startxfce4', 'fat_ram_threshold': 'default', 'lmd_extra_params':'XKBLAYOUT=es LDM_LANGUAGE="%s" LOCALE_DIR=%s'%(lng,language)} metadata_string = unicode(json.dumps(metadata,indent=4,encoding="utf-8",ensure_ascii=False)).encode("utf-8") objects['LmdImageManager'].setImage('mini-light-client',metadata_string) self.set_default_boot(imgid) # Writing lts.conf print ("[LMDServer] Writing lts.conf for mini-light-client") if not os.path.exists('/var/lib/tftpboot/ltsp/mini-light-client'): os.makedirs('/var/lib/tftpboot/ltsp/mini-light-client') f=open('/var/lib/tftpboot/ltsp/mini-light-client/lts.conf', 'w') f.write('[Default]\n') f.write('LDM_LANGUAGE=es_ES.UTF-8\n') f.write('LTSP_FATCLIENT=false\n') f.close() # Step 3. Registering imgid for boot PXE menu objects['LlxBootManager'].pushToBootList("ltsp_label"+str(imgid)); try: f=open("/etc/default/locale") lines=f.readlines() f.close() for line in lines: if "LANG=" in line: lng=line.strip().split("=")[1].strip("'").strip('"') if "es_ES" in lng: language="es" if "ca_ES" in lng: language="ca_ES@valencia" except: return {"status": False, "msg": ret["msg"]} pass # Returns true and ret.msg, that is job id return {"status": True, "msg": ret["msg"]} pass else: if ret["msg"]=="SERVER_BUSY": return {'status':False, 'msg':'SERVER_BUSY'} else: return {'status':False, 'msg':'EXCEPTION'} except Exception as e: print ("Except: "+str(e)) return {"status": False, "msg": str(e)} def do_command(self, ip, port, command, srv_ip='127.0.0.1',target=None): try: # Add Job '''job={ 'job_id':str(self.last_job_id), 'srv_ip': None, # Server (me) IP addr 'process': None, 'status':'started', 'msg':'', # Error code 'target':'None', 'command':command, 'started_by':None, 'listeners': [{'ip':ip, 'port':port, 'socket':None}], 'filepipe': '' }''' call_info = _n4d_get_user() job={ 'job_id':str(self.last_job_id), 'srv_ip': srv_ip, 'process': None, 'status':'started', 'msg':None, 'target': target, 'command':command, 'started_by':str(ip), 'listeners': [], 'filepipe': '', 'seek' : 0, 'method':call_info['method'], 'class': call_info['class'] } lock = threading.Lock() self.locks[job['job_id']]={} self.locks[job['job_id']]['lock'] = lock self.locks[job['job_id']]['lock'].acquire() # Exec command # proc = subprocess.Popen([command], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, preexec_fn=os.setsid) # Prepare file pipe temp = tempfile.NamedTemporaryFile(prefix='pipe_', dir=self.logfolder, delete=False) # New exec command, ignoring stderr stdin for now proc = subprocess.Popen([command], shell=True, stdout=temp, preexec_fn=os.setsid) # Add job to tasklist job['process']=proc # Assotiate process to job job['status']="running" job['filepipe'] = temp.name self.joblist.append(job) #Adding job self.add_listener_to_job(ip, port, job) # Adding the client ip:port to listeners list ## PAUSA PARA PROVOCAR UNA CONDICION DE CARRERA ## ## time.sleep(2) ## FIN DE LA PAUSA ## #self.joblist.append(job) #Adding job # Moved before add_listener_to_job # Increase last_job_id self.last_job_id=self.last_job_id+1 # Multicast process to all listeners print ("[LmdServer] WAITING ...:"+str(datetime.datetime.now())) ret=job['process'].poll() while ret is None: time.sleep(1) ret=job['process'].poll() #temp.close() if (str(ret)=='0'): job['status']="finished" elif (str(ret)=='-9'): job['status']="cancelled" else: # return code 1 when install fails job['msg']="broken" print ("[LmdServer] END WAIT AT"+str(datetime.datetime.now())) print ("[LmdServer] FINISHING!!!, return code: "+str(ret)) # Force umount (to avoid morrir destruction in mirrononnet) proc=subprocess.call(["/usr/share/lmd-scripts/umount-chroot.sh"]) # Sending last line to log for all listeners line="Finished with status:"+job['status']+" and Response: "+job['msg']+" \n" aux = open(job['filepipe'],'a') aux.writelines(line) aux.close() # Append result of job and release mutex. Now all inform_me return finish self.locks[job['job_id']]['result'] = str(ret) self.locks[job['job_id']]['lock'].release() print (str(ret)) return str(ret) except Exception as e: job['status']="Error" print ('[LmdServer]',e) if (ret is None): job['msg']="Err code None (running)" elif (ret<0): job['msg']="Interrupted by signal: "+str(ret) else: job['msg']="Aparently all went well: "+str(ret) # Append result of job and release mutex. Now all inform_me return finish self.locks[job['jobid']]['result'] = str(ret) self.locks[job['jobid']]['lock'].release() return str(e) def inform_me(self,job_id): ''' Return result of job when finish ''' self.locks[job_id]['lock'].acquire() self.locks[job_id]['lock'].release() return {'status':True,'msg':self.locks[job_id]['result']} def add_listener_to_job(self, ip, port, job): ''' Description: * Internal Function * Adds a listener identified by an ip and a port to a job object How it Works: * Creates a socket to send information to a listening socket in any client Last update: * 5/02/2014: Added Functionality * 2/04/2014: Add reference with job_id ''' try: # Create a socket for new listener srv=socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # connection to socket to send log srv.connect((ip, int(port))) # Defines a new listener listener={'ip':ip, 'port':port, 'socket':srv} # Add listener to job job['listeners'].append(listener) print ("[LmdServer] add_listener_to_job: adding "+str(listener)+"to listeners for job "+str(job['job_id'])) # Sending status of job listener['socket'].send("Start listening on "+str(listener['port'])+"") listener['socket'].send(" Task:"+str(job['command'])+"") listener['socket'].send(" Started by: "+str(job['started_by'])+"") # Add reference with job_id tuple_ip_port = (str(ip),str(port)) self.global_listeners[tuple_ip_port] = job['job_id'] #print "Len",len(self.global_listeners) #print "isAlive",self.multicast_thread.isAlive() if not self.multicast_thread.isAlive(): #print "lanzando el thread" self.multicast_thread = threading.Thread(target=self.Multicast, args=()) self.multicast_thread.daemon = True self.multicast_thread.start() pass return port except Exception as e: print("[LmdServer] add_listener_to_job Exception: "+str(e)) return -1 pass def add_listener(self, ip, port, job_id): ''' Description: * n4d method * Adds a listener identified by an ip and a port to a job object identified by its id How it Works: * Search whic job has certain job_id and adds a listener to it Last update: * 5/02/2014: Added Functionality ''' from operator import itemgetter #print ("[add_listener DEBUG] address: "+str(ip)+":"+str(port)+" job id: "+str(job_id)) try: # Get job identified by job_id current_job_index=map(itemgetter('job_id'), self.joblist).index(str(job_id)) current_job=self.joblist[current_job_index] # Add listener to current_job self.add_listener_to_job(ip, port, current_job) except Exception as e: print ("[LmdServer] add_listener Exception "+str(e)) pass def send(self, ip, job_id, info): ''' receives info to send to stdin ''' from operator import itemgetter try: current_job_index=map(itemgetter('job_id'), self.joblist).index(str(job_id)) current_job=self.joblist[current_job_index] # Get listener identified by (ip:port) #current_job['process'].stdin.writelines(str(info)) if(current_job['status']=='running'): current_job['process'].stdin.write(str(info)+"\n") #current_job['process'].write(info); except Exception as e: print ("ERROR writing to process: "+str(e)) def remove_listener(self, ip, port, job_id): ''' Description: * n4d method * Removes listener identified by an ip and a port to a job object identified by its id How it Works: * Searches which job has certain job_id (if specified) and removes it * If job_id is not specified, get it from ip:port Last update: * 6/02/2014: Added Functionality * 2/04/2014: Added checking for job_id and close socket ''' from operator import itemgetter try: # If job_id is None, search job_ib by ip and port tuple_ip_port = (str(ip),str(port)) if job_id == None: job_id = self.global_listeners[tuple_ip_port] # Get job identified by job_id print (self.joblist) print (tuple_ip_port) print (jobid) current_job_index=map(itemgetter('job_id'), self.joblist).index(job_id) current_job=self.joblist[current_job_index] # Get listener identified by (ip:port) current_listener_index=map(itemgetter('ip','port'), current_job['listeners']).index((ip,int (port, base=10))) # Close port and remove listener to current_job current_job['listeners'][current_listener_index]['socket'].close() current_job['listeners'].remove(current_job['listeners'][current_listener_index]) #remove listener reference self.global_listeners.pop(tuple_ip_port) return True except Exception as e: print ("[LmdServer] remove_listener Exception "+str(e)) return False pass def cancel_job(self, ip, port, job_id): ''' Description: * n4d method * TO DO ... Same functionality that remove_listener and in addition, kills process identified by job ''' # Remove listener from operator import itemgetter try: # If job_id is None, search job_ib by ip and port tuple_ip_port = (str(ip),str(port)) print (tuple_ip_port) if job_id == None: print (self.global_listeners) job_id = self.global_listeners[tuple_ip_port] print (str(job_id)) # Get job identified by job_id current_job_index=map(itemgetter('job_id'), self.joblist).index(job_id) print (current_job_index) current_job=self.joblist[current_job_index] print (current_job) # Get listener identified by (ip:port) try: current_listener_index=map(itemgetter('ip','port'), current_job['listeners']).index((ip,int (port, base=10))) print (current_listener_index) except: #print "*** jobid: ***" #print port print ("*** port: ***") print (port) print ("*** current_job: ***") print (current_job['listeners']) print ("*** joblist: ***") print (self.getJobList()) current_listener_index=None for listener in current_job['listeners']: self.remove_listener(str(ip),str(port), job_id); # Kill process (only addition to remove_listener) os.killpg(current_job['process'].pid,signal.SIGKILL) # Removes target print ("Removing target "+ str (current_job['target'])) r=objects['LmdImageManager'].deleteImage(current_job['target']); print (str(r)) print ("Removied target") current_job['status']='broken' if current_listener_index!=None: # Close port and remove listener to current_job current_job['listeners'][current_listener_index]['socket'].close() current_job['listeners'].remove(current_job['listeners'][current_listener_index]) #remove listener reference self.global_listeners.pop(tuple_ip_port) return True except Exception as e: print ("[LmdServer] cancel_job Exception "+str(e)) return False pass def getJobList(self): ''' Description: * N4D Method * Return JSON with the job list ''' #import urllib #ret= urllib.quote(str(self.joblist)[1:-1]) #ret= (str(self.joblist)[1:-1]).replace("'", "\""); #ret= (str(self.joblist)).replace("'", "\""); '''ret=ret.replace("<", "\""); ret=ret.replace(">", "\"");''' ret='['; count=0; for job in self.joblist: if (count>0): ret=ret+',' ret=ret+'{"job_id":"'+str(job['job_id'])+'"' ret=ret+', "srv_ip":"'+str(job['srv_ip'])+'"' ret=ret+', "status":"'+str(job['status'])+'"' ret=ret+', "msg":"'+str(job['msg'])+'"' ret=ret+', "target":"'+str(job['target'])+'"' ret=ret+', "command":"'+str(job['command'])+'"' ret=ret+', "started_by":"'+str(job['started_by'])+'"}' count=count+1 #print "*********************" #print "Local listeners" #print job['listeners'] #print "*********************" ret=ret+']' #print (ret) #print "*********************" #print "globals listeners" #print self.global_listeners #print "*********************" return str(ret) def Multicast(self): ''' Description: * Internam method * Multicast the output of all processes to its listeners How it works: * Traverses the list of jobs and write its output to all its listeners * Last Update: 13/02/2014 ''' try: while len(self.global_listeners) > 0 : counter = 0 #print "joblist",self.joblist #print "global_listeners",self.global_listeners for job in self.joblist: if True : #job['status'] != "finished": counter += 1 try: if not self.thread_jobs.has_key(job['job_id']): self.thread_jobs[job['job_id']] = threading.Thread(target=self.send_info_by_socket, args=(job,)) self.thread_jobs[job['job_id']].daemon = True self.thread_jobs[job['job_id']].start() except Exception as e: print (e) if counter == 0: break time.sleep(1) except Exception as e: print ("[LmdServer] EXCEPTION in Multicast: "+str(e)) def send_info_by_socket(self,job): try: if not os.path.exists(job['filepipe']): return False pipe = open(job['filepipe'],'r') pipe.seek(job['seek'],0) try: line = pipe.readline() while (line and len(self.global_listeners)>0): for listener in job['listeners']: if(listener['socket']!=None): try: listener['socket'].send(line) except Exception as e: print ("[LmdServer] EXCEPTION in Multicast internal loop: "+str(e)) continue line=pipe.readline() job['seek'] = pipe.tell() except Exception as e: print ("[LMDServer] Exception wghile reading pipe "+str(e)) pass if self.thread_jobs.has_key(job['job_id']): self.thread_jobs.pop(job['job_id']) except Exception as e: print ("[LMDServer] Exception wghile reading pipe "+str(e)) pass def getLastJobId(self): return self.last_job_id def getJobStatus(self, jobid): for i in self.joblist: if i['job_id']==jobid: return {"status": True, "msg": str(i['status'])} return {"status": False, "msg": 'bad luck, guy'} def check_lmd_status(self): ''' Description: * N4D Method * check status of lmd ''' import os.path if (os.path.isfile("/tmp/.lmd-editing-chroot")): return {"status": True, "lmd_status": 'lmd-editing-chroot'} else: return {"status": True, "lmd_status": 'lmd-chroot-available'} def chek_minimal_client(self): if (os.path.isfile("/etc/ltsp/images/mini-light-client.json") and os.path.isfile("/opt/ltsp/images/mini-light-client.img") ): return {"status": True} else: return {"status": False} def get_versions_13(self): ''' Checks if there is any old image un system (13.06) ''' ltsp_dir="/opt/ltsp/" nbd_dir="/etc/nbd-server/conf.d/" ret_list=[]; #OBSOLETE REMOVED METHOD #RETURN EMPTY LIST TO LMD-GUI TO AVOID LLIUREX 13 WARNING #for name in os.listdir(ltsp_dir): # dir=ltsp_dir+name # # needs_update=False # if (name!='images' and os.path.isdir(dir)): # proc = subprocess.Popen(["chroot "+dir +" lliurex-version"], stdout=subprocess.PIPE, shell=True) # (out, err) = proc.communicate() # # if ((not "16.05" in out) and out!="" ): # # check nbd... # for nbd_descriptor in os.listdir(nbd_dir): # if (self.line_in_file("["+ltsp_dir+name+"]", nbd_dir+nbd_descriptor)): # needs_update=True # if (needs_update==True): # ret_list.append(name) # return ret_list def line_in_file(self, line_to_find, filename): ''' Aux function: ask for if certain line is contained in a file ''' fd = open(filename, 'r') lines = fd.readlines() fd.close() for line in lines: if str(line_to_find) in line: return True return False def update_images(self, ip, port, imagelist, srv_ip): try: imagelist_string = " ".join(imagelist) ##print imagelist," is ", type(imagelist) ### n4d-client -c LmdServer -m update_images -u joamuran -p lliurex -a 2 3 "['pajarito', 'perro']" # Prepare and launch command command="/usr/share/lmd-scripts/lmd-upgrade-images.sh "+imagelist_string; result=self.do_command(ip, port, command, srv_ip, None) # Add image description #imagelist=imagelist_string.replace("[","").replace("]","").replace(" ","").replace("'", "").replace(","," "); #print imagelist_string; print (imagelist) #print imagelist.split(" "); for i in imagelist: metadata = {'id':i, 'name' : i, 'desc' : 'Upgraded from Lliurex 13.06', 'template' : 'none', 'ltsp_fatclient': 'undefined', 'ldm_session': 'default', 'fat_ram_threshold':'default', 'lmd_extra_params':''} metadata_string = unicode(json.dumps(metadata,indent=4,encoding="utf-8",ensure_ascii=False)).encode("utf-8") objects['LmdImageManager'].setImage(i,metadata_string) self.set_default_boot(imgid) return {"status": True} except Exception as e: print ("Exception", e) return {"status": False, 'msg':str(e)} def delete_images(self,imagelist): try: for img in imagelist: print (img) path_chroot=os.path.join("/opt/ltsp/", img) path_tftpboot=os.path.join("/var/lib/tftpboot/ltsp/", img) path_img=os.path.join("/opt/ltsp/images/", img+".img") path_nbd=os.path.join("/etc/nbd-server/conf.d/", "ltsp_"+img+".conf") path_etc_json=os.path.join("/etc/ltsp/images/"+img+".json") if (os.path.exists(path_chroot)): shutil.rmtree(path_chroot) #print "deleting: ",path_chroot if (os.path.exists(path_tftpboot)): shutil.rmtree(path_tftpboot) if (os.path.exists(path_img)): #print "deleting: ",path_img shutil.rmtree(path_img); if (os.path.exists(path_nbd)): #print "deleting: ",path_nbd shutil.rmtree(path_nbd); if (os.path.exists(path_etc_json)): shutil.rmtree(path_etc_json) return {"status": True, 'msg':'Finished'} except Exception as e: print ("Exception", e) return {"status": False, 'msg':str(e)} def LmdServerVersion(self): info=subprocess.check_output(["apt-cache","policy","lmd-server"]) lines=str(info).split('\n') version=lines[1][13:] return (version) def check_update_images(self): list_dirs = [ os.path.join(self.ltsp_path,x) for x in os.listdir(self.ltsp_path) if os.path.isdir(os.path.join(self.ltsp_path,x)) ] list_need_update = [] for chroot in list_dirs: available_file = os.path.join(chroot,'var','lib','dpkg','available') if os.path.exists(available_file): available_fd = open(available_file,'r') available_content = available_fd.readlines() try: pos = available_content.index('Package: lmd-client\n') version = None for i in range(pos + 1 ,len(available_content)): if available_content[i] == '\n': break if available_content[i].startswith('Version'): version = available_content[i][8:].strip() if version != None : apt_pkg.init() if apt_pkg.version_compare(version,'0.15') < 0 : list_need_update.append(os.path.basename(chroot)) except Exception as e: pass if len(list_need_update) > 0: return [True,list_need_update] else: return [False,[]] def is_needed_update_image(self, img_id): lliurex_up = "19.04.31" token_ltsp = "/var/lib/lmd/semi" vnc_binary = "/usr/bin/tigervncserver" if not os.path.exists(os.path.join(self.ltsp_path, img_id, "var","lib","lmd","semi")): return {"status":True,"msg":True} if not os.path.exists(os.path.join(self.ltsp_path, img_id, "usr","bin","tigervncserver")): return {"status":True,"msg":True} p = subprocess.Popen("ltsp-chroot -m -a {} dpkg-query --showformat='${{Version}}' --show lliurex-up".format(img_id),shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) result = p.communicate() if p.returncode == 1: return {"status":True,"msg":True} p = subprocess.Popen("dpkg --compare-versions {} ge {}".format(result[0], lliurex_up),shell=True) p.communicate() if p.returncode == 1: return {"status":True,"msg":True} return {"status":False,"msg":False} def update_image_to_vnc_image(self, img_id): lmd_path = os.path.join(self.ltsp_path,img_id,"var","lib","lmd") semi_path = os.path.join(lmd_path,"semi") if not os.path.exists(semi_path): if not os.path.exists(lmd_path): os.makedirs(lmd_path) x = open(semi_path,"w") x.close() shutil.copy("/usr/share/lmd-server/apt/lliurex.list",os.path.join(self.ltsp_path,img_id,"etc","apt","sources.list.d","lliurex.list")) packages = ["tigervnc-standalone-server", "lliurex-up","vnc-disable-composite"] p = subprocess.Popen("ltsp-chroot -m -a {} apt update".format(img_id),shell=True,stdout=subprocess.PIPE, stderr=subprocess.PIPE) result = p.communicate() if p.returncode != 0 : return {"status": False, "msg": result[1]} p = subprocess.Popen("ltsp-chroot -m -a {} apt install -y {}".format(img_id, " ".join(packages)),shell=True,stdout=subprocess.PIPE, stderr=subprocess.PIPE) p.communicate() if p.returncode != 0 : return {"status": False, "msg": result[1]} os.unlink(os.path.join(self.ltsp_path,img_id,"etc","apt","sources.list.d","lliurex.list")) return {"status": True, "msg": "image updated"}