source: lliurex-mirror/trunk/fuentes/n4d-lliurex-mirror.install/usr/share/n4d/python-plugins/MirrorManager.py @ 2556

Last change on this file since 2556 was 2556, checked in by hectorgh, 3 years ago

supporting more debmirror states

File size: 20.1 KB
Line 
1from jinja2 import Environment
2from jinja2.loaders import FileSystemLoader
3from jinja2 import Template
4
5import tempfile
6
7import os
8import threading
9import datetime
10import pexpect
11import re
12import json
13
14import BaseHTTPServer
15from SimpleHTTPServer import SimpleHTTPRequestHandler
16from multiprocessing import Process
17import socket
18from urllib2 import urlopen
19import string
20
21class MirrorManager:
22
23
24        def __init__(self):
25                #Default values
26                self.defaultpath = '/etc/lliurex-mirror/'
27                self.debmirrorconfpath = os.path.join(self.defaultpath,'debmirror')
28                self.configpath = os.path.join(self.defaultpath,'conf')
29                self.distro="llx16"
30                self.httpd = {}
31                self.debmirrorprocess = None
32
33                self.tpl_env = Environment(loader=FileSystemLoader('/usr/share/n4d/templates/lliurex-mirror'))
34                self.update_thread=threading.Thread()
35                self.get_mirror_thread = threading.Thread()
36                self.percentage=(0,None)
37                self.exportpercentage = 0
38                self.mirrorworking = None
39                self.webserverprocess = {}
40                self.defaultmirrorinfo = {"status_mirror":"New","last_mirror_date":None,"mirror_size":0,"progress":0}
41                self.valid_chars = "-_.%s%s" % (string.ascii_letters, string.digits)
42                self.default_mirror_config = '''
43{
44        "NAME": "",
45        "BANNER": "",
46        "ORIGS" : {"1":"lliruex.net/xenial","2":"","3":""},
47        "ARCHITECTURES": [ "amd64", "i386"],
48        "SECTIONS": ["main", "main/debian-installer", "universe", "restricted", "multiverse", "partner"],
49        "MIRROR_PATH": "/net/mirror/llx16",
50        "DISTROS": ["xenial","xenial-updates","xenial-security"],
51        "IGN_GPG":1,
52        "IGN_RELEASE":0,
53        "CHK_MD5":0,
54        "CURRENT_UPDATE_OPTION":"1"
55}'''
56               
57        #def init
58       
59        def startup(self,options):
60                self.n4d_vars=objects["VariablesManager"]
61                self.variable=objects["VariablesManager"].get_variable("LLIUREXMIRROR")
62               
63               
64                if self.variable==None:
65                        try:
66                                self.n4d_vars.add_variable("LLIUREXMIRROR",{},"","Lliurex Mirror info variable","n4d-lliurex-mirror")
67                        except Exception as e:
68                                pass
69                       
70                if type(self.variable)!=type({}):
71                        self.variable={}
72               
73                try:
74                        for repo in self.get_available_mirrors()['msg']:
75                                if self.variable.has_key(repo) and self.variable[repo].has_key("status_mirror") and self.variable[repo]["status_mirror"] == "Working":
76                                        if not self.update_thread.isAlive():
77                                                self.variable[repo]["status_mirror"] = "Error"
78                                                self.n4d_vars.set_variable("LLIUREXMIRROR",self.variable)
79                                else:
80                                        if not self.variable.has_key(repo):
81                                                self.variable[repo] = self.defaultmirrorinfo
82                except Exception as e:
83                        pass
84        #def startup
85
86        def apt(self):
87                # executed after apt operations
88                pass
89               
90        #def apt
91       
92        # service test and backup functions #
93       
94        def test(self):
95
96                pass
97               
98        #def test
99       
100        def backup(self):
101
102                pass
103               
104        #def backup
105
106        def restore(self):
107                pass
108        #def restore
109
110        def set_cname(self):
111                #Get template
112                template = self.tpl_env.get_template("cname")
113                list_variables = {}
114               
115                list_variables = self.n4d_vars.get_variable_list(['INTERNAL_DOMAIN','HOSTNAME'])
116                for x in list_variables.keys():
117                        if list_variables[x] == None:
118                                return {'status':False,'msg':'Variable ' + x + ' not defined'}
119                       
120                #Encode vars to UTF-8
121                string_template = template.render(list_variables).encode('UTF-8')
122                #Open template file
123                fd, tmpfilepath = tempfile.mkstemp()
124                new_export_file = open(tmpfilepath,'w')
125                new_export_file.write(string_template)
126                new_export_file.close()
127                os.close(fd)
128                #Write template values
129                n4d_mv(tmpfilepath,'/var/lib/dnsmasq/config/cname-mirror',True,'root','root','0644',False )
130               
131                return {'status':True,'msg':'Set mirror cname'}
132        #def set_cname
133       
134        def update(self,ip,distro=None,callback_args=None):
135
136                if distro==None:
137                        distro=self.distro
138       
139                if self.update_thread.is_alive():
140                        return {'status':False,'msg':'Lliurex-mirror (n4d instance) is running'}
141               
142                self.percentage=(0,None)
143                self.update_thread=threading.Thread(target=self._update,args=(ip,distro,callback_args,))
144                self.update_thread.daemon=True
145                self.update_thread.start()
146               
147                return {'status':True,'msg':'running'}
148
149        #def update
150       
151        def _update(self,ip,distro,callback_args):
152                if not self.variable.has_key(distro):
153                        self.variable[distro]=self.defaultmirrorinfo
154                # link config debmirror to correct path with distro name
155                self.variable[distro]['status_mirror'] = "Working"
156                self.variable[distro]['progress'] = 0
157                self.variable[distro]['exception_msg'] = ""
158                self.n4d_vars.set_variable("LLIUREXMIRROR",self.variable)
159                self.build_debmirror_config(distro)
160                if os.path.lexists('/etc/debmirror.conf'):
161                        os.remove('/etc/debmirror.conf')
162                os.symlink(os.path.join(self.debmirrorconfpath,distro),'/etc/debmirror.conf')
163                self.mirrorworking = distro
164
165                errors_found = False   
166                self.debmirrorprocess=pexpect.spawn("/usr/bin/debmirror")
167                try:
168                        objects["ZCenterVariables"].add_pulsating_color("lliurexmirror")
169                except:
170                        pass
171                while True:
172                        try:
173                                self.debmirrorprocess.expect('\n',timeout=480)
174                                line =self.debmirrorprocess.before
175                                line1=line.strip("\n")
176                                if line1.startswith("[") and line1[5] == "]":
177                                        self.percentage=(int(line1[1:4].strip()),self.debmirrorprocess.exitstatus)
178                                        self.variable[distro]['progress'] = self.percentage[0]
179                                        self.n4d_vars.set_variable("LLIUREXMIRROR",self.variable)
180                                if line1.startswith("Everything OK"):
181                                        self.percentage=(100,self.debmirrorprocess.exitstatus)
182                                        self.variable[distro]['progress'] = 100
183                                        self.n4d_vars.set_variable("LLIUREXMIRROR",self.variable)
184                               
185                                if errors_found:
186                                        e=Exception(line1)
187                                        raise e
188                                if line1.startswith("Errors"):
189                                        errors_found = True
190       
191
192                        except pexpect.EOF:
193                                        line1 = self.debmirrorprocess.before
194                                        if line1 != "" and line1.startswith("[") and line1[5] == "]":
195                                                        self.percentage=(int(line1[1:4].strip()),self.debmirrorprocess.exitstatus)
196                                        self.debmirrorprocess.close()
197                                        status = self.debmirrorprocess.exitstatus
198                                        self.percentage=(self.percentage[0],status)
199                                        self.variable[distro]['progress'] = self.percentage[0]
200                                        self.variable[distro]['status_mirror'] = "Ok" if status == 0 else "Error"
201                                        self.n4d_vars.set_variable("LLIUREXMIRROR",self.variable)
202                                        break
203                        except Exception as e:
204                                print e
205                                self.variable[distro]['status_mirror'] = "Error"
206                                self.variable[distro]["exception_msg"] = str(e)
207                                status = self.debmirrorprocess.exitstatus
208                                self.percentage=(self.percentage[0],str(e))
209                                self.n4d_vars.set_variable("LLIUREXMIRROR",self.variable)
210                                break
211
212                if type(callback_args) != type(None):
213                        if callback_args.has_key('port'):
214                                import xmlrpclib as x
215                                c = x.ServerProxy('https://' + ip + ':9779')
216                                c.stop_webserver('','MirrorManager',callback_args['port'])
217
218                self.download_time_file(distro)
219                self.set_mirror_info(distro)
220                self.mirrorworking = None
221                try:
222                        objects["ZCenterVariables"].remove_pulsating_color("lliurexmirror")
223                except:
224                        pass
225
226        #def _update
227       
228        def is_alive(self):
229
230                return {'status':self.update_thread.is_alive(),'msg':self.mirrorworking}
231        #def is_alive
232
233        def set_mirror_info(self,distro=None):
234               
235                if distro!=None:
236                        distro=distro
237                else:
238                        distro=self.distro
239               
240                configpath = os.path.join(self.configpath, distro + ".json")
241                config = json.load(open(configpath,'r'))
242
243                mirrorpath = config["MIRROR_PATH"]
244                #self.n4d_vars.set_variable("ZEROCENTERINTERNAL",self.internal_variable)
245               
246                MIRROR_DATE=datetime.date.today().strftime("%d/%m/%Y")
247                MIRROR_SIZE=self.get_size(mirrorpath)
248               
249                self.variable[distro]["last_mirror_date"]=MIRROR_DATE
250                self.variable[distro]["mirror_size"]=str(MIRROR_SIZE)
251                self.variable[distro]["progress"]=self.percentage[0]
252               
253                self.n4d_vars.set_variable("LLIUREXMIRROR",self.variable)
254               
255                #set_custom_text(self,app,text):
256                txt="Updated on: " + str(MIRROR_DATE)
257                txt+=" # Size: %.2fGB"%MIRROR_SIZE
258                try:
259                        objects["ZCenterVariables"].set_custom_text("lliurexmirror",txt)
260                        abstract=open('/var/log/lliurex/lliurex-mirror.log','w')
261                        abstract.write(txt+"\n")
262                        abstract.close()
263                except Exception as e:
264                        pass
265
266        #def set_mirror_info(self):
267
268        def get_size(self,start_path = '.'):
269       
270                total_size = 0
271                try:
272                        for dirpath, dirnames, filenames in os.walk(start_path):
273                                for f in filenames:
274                                        fp = os.path.join(dirpath, f)
275                                        total_size += os.path.getsize(fp)
276                                       
277                        total_size/=1024*1024*1024.0
278                        return total_size
279                except:
280                        return 0
281       
282        #def get_size(start_path = '.'):
283       
284        def search_field(self,filepath,fieldname):
285                try:
286                        f = open(filepath,'r')
287                        needle = None
288                        lines = f.readlines()
289                        for x in lines:
290                                        if re.match('\s*'+fieldname,x):
291                                                        needle = x.strip()
292                        return needle
293                except:
294                        return None
295        # def search_field
296       
297        def get_mirror_architecture(self,distro):
298
299                configpath = os.path.join(self.configpath,distro + ".json")
300                config = json.load(open(configpath,'r'))
301                if not os.path.lexists(configpath):
302                        return {'status':False,'msg':'not exists debmirror.conf to '+ distro }
303
304                if "ARCHITECTURES" in config.keys():
305                        return {'status':True,'msg':config["ARCHITECTURES"] }
306
307                return {'status':False,'msg':"debmirror.conf hasn't architecture variable" }
308        #def get_mirror_architecture
309       
310        def set_mirror_architecture(self,distro,archs):
311                configpath = os.path.join(self.configpath,distro + ".json")
312               
313                config = json.load(open(configpath,'r'))
314               
315                config['ARCHITECTURES'] = archs
316
317
318                f=open(configpath,"w")
319
320                data=unicode(json.dumps(config,indent=4,encoding="utf-8",ensure_ascii=False)).encode("utf-8")
321                f.write(data)
322                f.close()
323
324                self.build_debmirror_config(distro)
325                return {'status':True,'msg':'set architecture'}
326               
327        #def set_mirror_architecture
328       
329        def get_mirror_orig(self,distro,option):
330
331                configpath = os.path.join(self.configpath,distro + ".json")
332                config = json.load(open(configpath,'r'))
333                if not os.path.lexists(configpath):
334                        return {'status':False,'msg':'not exists debmirror.conf to '+ distro }
335
336                if "ORIGS" in config.keys():
337                        return {'status':True,'msg':config["ORIGS"][option] }
338                       
339                return {'status':False,'msg':"debmirror.conf hasn't orig variable" }   
340        #def get_mirror_from
341
342        def set_mirror_orig(self,distro,url,option):
343                if url == None:
344                        return {'status':False,'msg':'url is None'}
345                configpath = os.path.join(self.configpath, distro + ".json")
346                config = json.load(open(configpath,'r'))
347                config['ORIGS'][option] = url
348
349                f=open(configpath,"w")
350                data=unicode(json.dumps(config,indent=4,encoding="utf-8",ensure_ascii=False)).encode("utf-8")
351                f.write(data)
352                f.close()
353
354                self.build_debmirror_config(distro)
355                return {'status':True,'msg':'set orig'}
356        #def set_mirror_architecture
357
358        def get_option_update(self,distro):
359                configpath = os.path.join(self.configpath,distro + ".json")
360                config = json.load(open(configpath,'r'))
361                if not os.path.lexists(configpath):
362                        return {'status':False,'msg':'not exists debmirror.conf to '+ distro }
363
364                if "CURRENT_UPDATE_OPTION" in config.keys():
365                        return {'status':True,'msg':config["CURRENT_UPDATE_OPTION"] }
366                       
367                return {'status':False,'msg':"debmirror.conf hasn't option update variable" }
368        #def get_option_update
369
370        def set_option_update(self,distro,option):
371                configpath = os.path.join(self.configpath, distro + ".json")
372                config = json.load(open(configpath,'r'))
373                config['CURRENT_UPDATE_OPTION'] = str(option)
374
375                f=open(configpath,"w")
376                data=unicode(json.dumps(config,indent=4,encoding="utf-8",ensure_ascii=False)).encode("utf-8")
377                f.write(data)
378                f.close()
379
380                self.build_debmirror_config(distro)
381                return {'status':True,'msg':'set update option'}
382        #def set_option_update
383
384        def get_percentage(self,distro):
385                if self.variable.has_key(distro):
386                        return {'status':True,'msg':self.variable[distro]['progress']}
387                else:
388                        return {'status':False,'msg':'this repo nos has been configured'}
389        #def get_percentage
390
391
392        def build_debmirror_config(self,distro):
393                result = self.render_debmirror_config(distro)
394                string_template = result['msg']
395                f = open(os.path.join(self.debmirrorconfpath,distro),'w')
396                f.write(string_template)
397                f.close()
398        #def build_debmirror_config
399
400        def render_debmirror_config(self,arg):
401                if type(arg) == type(""):
402                        return self._render_debmirror_config_distro(arg)
403                if type(arg) == type({}):
404                        return self._render_debmirror_config_values(arg)
405        #def render_debmirror_config
406
407        def _render_debmirror_config_distro(self,distro):
408                template = self.tpl_env.get_template('debmirror.conf')
409                configpath = os.path.join(self.configpath,distro + ".json")
410                config = json.load(open(configpath,'r'))
411                return {'status':True,'msg':template.render(config).encode('utf-8')}
412        #def render_debmirror_config
413
414        def _render_debmirror_config_values(self,config):
415                template = self.tpl_env.get_template('debmirror.conf')
416                return {'status':True,'msg':template.render(config).encode('utf-8')}
417        #def _render_debmirror_config_values
418
419        def enable_webserver_into_folder(self,path):
420               
421                s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
422                s.bind(('localhost', 0))
423                addr, port = s.getsockname()
424                s.close()
425                self.webserverprocess[str(port)] = Process(target=self._enable_webserver_into_folder,args=(port,path,))
426                self.webserverprocess[str(port)].start()
427                return {'status':True,'msg':port}
428        #enable_webserver_into_folder
429
430        def _enable_webserver_into_folder(self,port,path):
431                try:
432                        iface = '127.0.0.1'
433                        sock = (iface,port)
434                        proto = "HTTP/1.0"
435                        os.chdir(path)
436                        handler = SimpleHTTPRequestHandler
437                        handler.protocol_version = proto
438                        self.httpd[str(port)] = BaseHTTPServer.HTTPServer(sock,handler)
439                        self.httpd[str(port)].serve_forever()
440                except Exception, e:
441                        return None
442        #_enable_webserver_into_folder
443
444        def stop_webserver(self,port):
445                if self.webserverprocess.has_key(port):
446                        self.webserverprocess[port].terminate()
447                        self.webserverprocess.pop(port)
448                        return {'status':True,'msg':'Server stopped'}
449                return {'status':False,'msg':'Server not exists'}
450        #stop_webserver
451       
452        def set_checksum_validation(self,distro,status):
453                configpath = os.path.join(self.configpath, distro + ".json")
454                config = json.load(open(configpath,'r'))
455                config['CHK_MD5'] = status
456
457                f=open(configpath,"w")
458                data=unicode(json.dumps(config,indent=4,encoding="utf-8",ensure_ascii=False)).encode("utf-8")
459                f.write(data)
460                f.close()
461
462                self.build_debmirror_config(distro)
463                return {'status':True,'msg':'set checksum validation'}
464        #set_checksum_validation
465       
466        def get_checksum_validation(self,distro):
467
468                configpath = os.path.join(self.configpath,distro + ".json")
469                config = json.load(open(configpath,'r'))
470                if not os.path.lexists(configpath):
471                        return {'status':False,'msg':'not exists debmirror.conf to '+ distro }
472                if "IGN_GPG" in config.keys():
473                        return {'status':True,'msg':config["CHK_MD5"] }
474
475                return {'status':False,'msg':"debmirror.conf hasn't orig variable" }
476        #get_checksum_validation
477       
478        def get_available_mirrors(self):
479                versions = os.listdir(self.configpath)
480                versions = [ version.replace('.json','') for version in versions if version.endswith('.json')]
481                return {'status':True,'msg':versions}
482
483        def stopupdate(self):
484                try:
485                        self.debmirrorprocess.terminate()
486                        return {'status':True,'msg':'debmirror stopped'}
487                except Exception as e:
488                        return {'status':False,'msg':str(e)}
489
490        def stopgetmirror(self):
491                try:
492                        self.get_mirror_process.terminate()
493                        return {'status':True,'msg':'debmirror stopped'}
494                except Exception as e:
495                        return {'status':False,'msg':str(e)}
496
497        def download_time_file(self,distro):
498               
499                configpath = os.path.join(self.configpath,distro + ".json")
500                config = json.load(open(configpath,'r'))
501                path=config["MIRROR_PATH"]
502                f="time-of-last-update"
503                dest=os.path.join(path,f)
504
505                orig_mirror=self.get_mirror_orig(distro,"1")
506                url_mirror="http://"+os.path.join(orig_mirror['msg'],f)
507
508                return self.get_time_file(url_mirror,dest)
509
510        # # def download_time_file                     
511
512               
513        def get_time_file(self,url,dest):
514               
515                try:
516                        r=urlopen(url)
517                        f=open(dest,"wb")
518                        f.write(r.read())
519                        f.close()
520                        r.close()
521                        return {'status':True,'msg':dest + 'successfully downloaded.'}
522               
523                except Exception as e:
524                        return {'status':False,'msg':'Error downloading' + dest + ':' + str(e)}                 
525
526        # def get_time_file             
527
528        def is_update_available(self,distro):
529
530                configpath = os.path.join(self.configpath,distro + ".json")
531                config = json.load(open(configpath,'r'))
532                path = config["MIRROR_PATH"]
533                file_time_name = "time-of-last-update"
534                file_local_mirror = os.path.join(path,file_time_name)
535
536               
537                if os.path.isfile(file_local_mirror):
538                        url_pool = "http://"+os.path.join(config["ORIGS"]['1'],file_time_name)
539                        file_pool = os.path.join("/tmp",file_time_name)
540
541                        exist_file_pool = self.get_time_file(url_pool,file_pool)
542                        if exist_file_pool['status']:
543                                file_local_mirror_content=open(file_local_mirror,"r")
544                                file_local_miror_datetime=(file_local_mirror_content.readline().strip()).split("_")
545                                file_pool_content=open(file_pool,'r')
546                                file_pool_datetime=(file_pool_content.readline().strip()).split("_")
547                                file_local_mirror_content.close()
548                                file_pool_content.close()
549
550                                date_local_mirror=datetime.datetime.strptime(file_local_miror_datetime[0],"%Y/%m/%d")
551                                date_pool=datetime.datetime.strptime(file_pool_datetime[0],"%Y/%m/%d")
552
553                                if date_local_mirror==date_pool:
554                                        time_local_mirror=datetime.datetime.strptime(file_local_miror_datetime[1],"%H:%M")     
555                                        time_pool=datetime.datetime.strptime(file_pool_datetime[1],"%H:%M")
556
557                                        if time_local_mirror<time_pool:
558                                                return {'status':False,'msg':'Mirror not updated','action':'update'}
559                                        else:
560                                                return {'status':True,'msg':'Mirror is updated','action':'nothing'}
561
562                                elif date_local_mirror<date_pool:
563                                        return {'status':False,'msg':'Mirror not updated','action':'update'}
564                                else:
565                                        return {'status':True,'msg':'Mirror is updated','action':'nothing'}     
566                        else:
567                                return {'status':False,'msg':exist_file_pool['msg'],'action':'nothing'} 
568
569                else:
570                        return {'status':False,'msg':file_local_mirror + ' does not exist.','action':'nothing'}
571
572        # def is_update_available
573
574        def new_mirror_config(self,config):
575                name = config["NAME"].lower().strip()
576                name = ''.join(c for c in name if c in self.valid_chars)
577
578                # Checks
579                if name == "":
580                        return {'status':False,'msg':"Name can't void"}
581                while True:
582                        newconfigpath = os.path.join(self.configpath,name + '.json')
583                        if not os.path.lexists(newconfigpath):
584                                break
585                        name = name + "1"
586
587                data=unicode(json.dumps(config,indent=4,encoding="utf-8",ensure_ascii=False)).encode("utf-8")
588                f = open(newconfigpath,'w')
589                f.write(data)
590                f.close()
591                self.variable[name] = self.defaultmirrorinfo
592                return {'status':True,'msg':name}
593        #def new_mirror_config
594
595        def get_all_configs(self):
596                versions = os.listdir(self.configpath)
597                allconfigs = {}
598                for version in versions:
599                        configfile = os.path.join(self.configpath,version)
600                        f = open(configfile,'r')
601                        allconfigs[version.replace('.json','')] = json.load(f)
602                        f.close()
603                return {'status':True,'msg':allconfigs}
604        #def get_all_configs
605
606        def update_mirror_config(self,mirror,config):
607                configpath = os.path.join(self.configpath,mirror + ".json")
608
609                f=open(configpath,"w")
610
611                data=unicode(json.dumps(config,indent=4,encoding="utf-8",ensure_ascii=False)).encode("utf-8")
612                f.write(data)
613                f.close()
614
615                return {'status':True,'msg':'Updated config'}
616        #def update_mirror_config
617
618        def get_client_ip(self,ip):
619                return {'status':True,'msg':ip}
620        #def get_client_ip
621
622        def is_alive_get_mirror(self):
623                return {'status':self.get_mirror_thread.is_alive(),'msg':self.exportpercentage}
624        #def is_alive_get_mirror
625
626        def get_mirror(self,config_path,callback_args):
627                self.get_mirror_thread = threading.Thread(target=self._get_mirror,args=(config_path,callback_args,))
628                self.get_mirror_thread.daemon = True
629                self.get_mirror_thread.start()
630        #def get_mirror
631
632        def _get_mirror(self,config_path,callback_args):
633                self.get_mirror_process = pexpect.spawn("/usr/bin/debmirror --config-file="+config_path)
634                while True:
635                        try:
636                                self.get_mirror_process.expect('\n')
637                                line =self.get_mirror_process.before
638                                line1=line.strip("\n")
639                                if line1.startswith("[") and line1[5] == "]":
640                                        self.exportpercentage = (int(line1[1:4].strip()),self.get_mirror_process.exitstatus)
641                        except pexpect.EOF:
642                                        line1 = self.get_mirror_process.before
643                                        if line1 != "" and line1.startswith("[") and line1[5] == "]":
644                                                        self.exportpercentage=(int(line1[1:4].strip()),self.get_mirror_process.exitstatus)
645                                        self.get_mirror_process.close()
646                                        status = self.get_mirror_process.exitstatus
647                                        self.exportpercentage=(self.exportpercentage[0],status)
648                                        break
649                        except Exception as e:
650                                break
651                if callback_args.has_key('port') and callback_args.has_key('ip'):
652                        import xmlrpclib as x
653                        c = x.ServerProxy('https://' + callback_args['ip'] + ':9779')
654                        c.stop_webserver('','MirrorManager',callback_args['port'])
655        #def _get
Note: See TracBrowser for help on using the repository browser.