# This file is part of BOINC. # http://boinc.berkeley.edu # Copyright (C) 2016 University of California # # BOINC is free software; you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License # as published by the Free Software Foundation, # either version 3 of the License, or (at your option) any later version. # # BOINC is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with BOINC. If not, see . # Python bindings of the remote job submission and file management APIs import urllib import copy import xml.etree.ElementTree as ET import requests # you'll need to "yip install requests" # describes an input file # class FILE_DESC: def __init__(self): return def to_xml(self): xml = ('\n' '%s\n' ) %(self.mode) if self.mode == 'remote': xml += ('%s\n' '%f\n' '%s\n' ) %(self.url, self.nbytes, self.md5) else: xml += '%s\n' %(self.source) xml += '\n' return xml # describes a job # class JOB_DESC: def __init__(self): return def to_xml(self): xml = '\n' if hasattr(self, 'rsc_fpops_est'): xml += '%f\n'%self.rsc_fpops_est if hasattr(self, 'command_line'): xml += '%s\n'%self.command_line if hasattr(self, 'wu_template'): xml += '\n%s\n\n'%self.wu_template if hasattr(self, 'result_template'): xml += '\n%s\n\n'%self.result_template for file in self.files: xml += file.to_xml() xml += '\n' return xml # describes a batch for submit() or estimate() # class BATCH_DESC: def __init__(self): return def to_xml(self, op): xml = ('<%s>\n' '%s\n' '\n' '%s\n' ) %(op, self.authenticator, self.app_name) if hasattr(self, 'batch_id'): xml += '%s\n'%(self.batch_id) elif hasattr(self, 'batch_name'): xml += '%s\n'%(self.batch_name) for job in self.jobs: xml += job.to_xml() xml += '\n\n' %(op) return xml class CREATE_BATCH_REQ: def __init__(self): return def to_xml(self): xml = ('\n' '%s\n' '%s\n' '%s\n' '%f\n' '\n') %(self.authenticator, self.app_name, self.batch_name, self.expire_time) return xml # a generic request # class REQUEST: def __init__(self): return def do_http_post(req, project_url, handler='submit_rpc_handler.php'): print req url = project_url + handler params = urllib.urlencode({'request': req}) f = urllib.urlopen(url, params) reply = f.read() print "REPLY:", reply return ET.fromstring(reply) ########### API FUNCTIONS START HERE ############### def abort_batch(req): req_xml = ('\n' '%s\n' '%s\n' '\n' ) %(req.authenticator, req.batch_id) return do_http_post(req_xml, req.project) def abort_jobs(req): req_xml = ('\n' '%s\n' ) %(req.authenticator) for job in req.jobs: req_xml += '%s\n'%(job) req_xml += '\n' return do_http_post(req_xml, req.project) # req is a CREATE_BATCH_REQ # def create_batch(req): return do_http_post(req.to_xml(), req.project) def estimate_batch(req): return do_http_post(req.to_xml('estimate_batch'), req.project) def query_batch(req): req_xml = ('\n' '%s\n' '%s\n' '%d\n' '\n' ) %(req.authenticator, req.batch_id, 1 if req.get_cpu_time else 0) return do_http_post(req_xml, req.project) def query_batches(req): req_xml = ('\n' '%s\n' '%d\n' '\n' ) %(req.authenticator, 1 if req.get_cpu_time else 0) return do_http_post(req_xml, req.project) def query_completed_job(req): req_xml = ('\n' '%s\n' '%s\n' '\n' ) %(req.authenticator, req.job_name) return do_http_post(req_xml, req.project) def query_job(req): req_xml = ('\n' '%s\n' '%s\n' '\n' ) %(req.authenticator, req.job_id) return do_http_post(req_xml, req.project) def get_output_file(req): auth_str = md5.new(req.authenticator+req.instance_name).hexdigest() name = req.instance_name file_num = req.file_num return req.project+"/get_output.php?cmd=result_file&result_name=%s&file_num=%s&auth_str=%s"%(name, file_num, auth_str) def get_output_files(req): auth_str = md5.new(req.authenticator+req.batch_id).hexdigest() return req.project+"/get_output.php?cmd=batch_files&batch_id=%s&auth_str=%s"%(req.batch_id, auth_str) def retire_batch(req): req_xml = ('\n' '%s\n' '%s\n' '\n' ) %(req.authenticator, req.batch_id) return do_http_post(req_xml, req.project) def submit_batch(req): return do_http_post(req.to_xml('submit_batch'), req.project) # see if reply is error. # if so print the message and return True # def check_error(response): if response.find('error') is not None: print 'error: ', response.find('error').find('error_msg').text return True ############ FILE MANAGEMENT API ############## class QUERY_FILES_REQ: def __init__(self): return def to_xml(self): xml = ('\n' '%s\n' '%d\n') %(self.authenticator, self.batch_id) for name in self.boinc_names: xml += '%s\n' %(name) xml += '\n' return xml class UPLOAD_FILES_REQ: def __init__(self): return def to_xml(self): xml = ('\n' '%s\n' '%d\n') %(self.authenticator, self.batch_id) for name in self.boinc_names: xml += '%s\n' %(name) xml += '\n' return xml def query_files(query_req): reply = do_http_post(query_req.to_xml(), query_req.project, 'job_file.php') return reply # This actually does two RPCs: # query_files() to find what files aren't already on server # upload_files() to upload them # def upload_files(upload_files_req): query_req = QUERY_FILES_REQ() query_req.authenticator = upload_files_req.authenticator query_req.batch_id = upload_files_req.batch_id query_req.boinc_names = upload_files_req.boinc_names query_req_xml = query_req.to_xml() reply = do_http_post(query_req_xml, upload_files_req.project, 'job_file.php') if reply[0].tag == 'error': return reply absent = reply.find('absent_files').findall('file') #print 'query files succeeded; ',len(absent), ' files need upload' boinc_names = [] local_names = [] for n in absent: ind = int(n.text) boinc_names.append(upload_files_req.boinc_names[ind]) local_names.append(upload_files_req.local_names[ind]) upload_files_req.boinc_names = boinc_names upload_files_req.local_names = local_names # make a description of upoad files for "requests" # files = [] for i in range(len(boinc_names)): bn = boinc_names[i] ln = local_names[i] upload_name = 'file_%d'%(i) files.append((upload_name, (bn, open(ln, 'rb'), 'application/octet-stream'))) url = upload_files_req.project + '/job_file.php' req_xml = upload_files_req.to_xml() #print req_xml req = {'request': req_xml} reply = requests.post(url, data=req, files=files) #print "reply text: ", reply.text return ET.fromstring(reply.text)