From 38cd519a98aa5d9b48447f9e0919ff439ad2960e Mon Sep 17 00:00:00 2001 From: Alain Takoudjou <talino@tiolive.com> Date: Fri, 12 Oct 2012 10:42:58 +0200 Subject: [PATCH] Update BOINC, allows to install application with slapparameters --- slapos/recipe/boinc/__init__.py | 66 +++++++++++++++++++++----- slapos/recipe/boinc/configure.py | 80 ++++++++++++++++++++------------ software/boinc/software.cfg | 2 +- stack/boinc/buildout.cfg | 4 +- stack/boinc/instance-boinc.cfg | 3 +- 5 files changed, 109 insertions(+), 46 deletions(-) diff --git a/slapos/recipe/boinc/__init__.py b/slapos/recipe/boinc/__init__.py index 5d4ff9ff3..2c71a8ba7 100644 --- a/slapos/recipe/boinc/__init__.py +++ b/slapos/recipe/boinc/__init__.py @@ -29,6 +29,7 @@ import os import subprocess import pwd import signal +import zc.buildout class Recipe(GenericBaseRecipe): """Deploy a fully operational boinc architecture.""" @@ -208,11 +209,51 @@ class App(GenericBaseRecipe): """This recipe allow to deploy an scientific applications using boinc Note that recipe use depend on boinc-server parameter""" + def downloadFiles(self): + """This is used to download app files if necessary and update options values""" + for key in ('template-result', 'template-wu', 'input-file', 'binary'): + option = self.options[key].strip() + if option and (option.startswith('http') or option.startswith('ftp')): + #download the specified file + cache = os.path.join(self.options['home'].strip(), 'temp') + downloader = zc.buildout.download.Download(self.buildout['buildout'], + hash_name=True, cache=cache) + path, _ = downloader(option, md5sum=None) + mode = 0600 + if key == 'binary': + mode = 0700 + os.chmod(path, mode) + self.options[key] = path + + def checkOptions(self): + """Check if parameter send is valid to install or update application""" + if not self.options['app-name'].strip() or \ + not self.options['version'].strip(): + return False + self.appname = self.options['app-name'].strip() + self.version = self.options['version'].strip() + #for non exist application, check if parameter is complete + appdir = os.path.join(self.options['installroot'].strip(), 'apps', + self.options['app-name'].strip(), + self.options['version'].strip()) + if not os.path.exists(appdir): + if not self.options['template-result'].strip() or not self.options['binary'].strip() \ + or not self.options['input-file'].strip() or not self.options['template-wu'].strip() \ + or not self.options['wu-number'].strip() or not self.options['platform'].strip(): + print "Invalid argement values...operation cancelled" + return False + #write application to install + toInstall = open(os.path.join(self.options['home'].strip(), + '.install_' + self.appname + self.version), 'w') + toInstall.write('install or update') + toInstall.close() + return True def install(self): - if self.options['app-name'].strip() == '' or \ - self.options['version'].strip() == '': - #don't deploy empty application...skipped + self.appname = '' + self.version = '' + if not self.checkOptions(): + #don't deploy empty or invalid application...skipped return [] path_list = [] package = self.options['boinc'].strip() @@ -221,6 +262,7 @@ class App(GenericBaseRecipe): developegg = self.options['develop-egg'].strip() python_path = boinc_egg + ":" + os.environ['PYTHONPATH'] home = self.options['home'].strip() + user = pwd.getpwuid(os.stat(home).st_uid)[0] perl = self.options['perl-binary'].strip() svn = self.options['svn-binary'].strip() for f in os.listdir(developegg): @@ -239,26 +281,26 @@ class App(GenericBaseRecipe): self.substituteTemplate(self.getTemplateFilename('sed_update.in'), dict(dash=self.options['dash'].strip(), uldl_pid=self.options['apache-pid'].strip(), - user=self.options['user'])) + user=user)) ) path_list.append(sh_script) os.chmod(bash , 0700) + #If useful, download necessary files and update options path + self.downloadFiles() start_boinc = os.path.join(home, '.start_boinc') installroot = self.options['installroot'].strip() - version = self.options['version'].strip() platform = self.options['platform'].strip() apps_dir = os.path.join(installroot, 'apps') - appname = self.options['app-name'].strip() - bin_name = appname +"_"+ version +"_"+ \ + bin_name = self.appname +"_"+ self.version +"_"+ \ platform + self.options['extension'].strip() - application = os.path.join(apps_dir, appname, version, platform) + application = os.path.join(apps_dir, self.appname, self.version, platform) wrapperdir = self.options['wrapper-dir'].strip() project = self.options['project'].strip() parameter = dict(installroot=installroot, project=project, - appname=appname, binary_name=bin_name, - version=version, platform=platform, + appname=self.appname, binary_name=bin_name, + version=self.version, platform=platform, application=application, environment=environment, start_boinc=start_boinc, wu_number=int(self.options['wu-number'].strip()), @@ -266,10 +308,10 @@ class App(GenericBaseRecipe): t_wu=self.options['template-wu'].strip(), t_input=self.options['input-file'].strip(), binary=self.options['binary'].strip(), - bash=bash, + bash=bash, home_dir=home, ) deploy_app = self.createPythonScript( - os.path.join(wrapperdir, appname), + os.path.join(wrapperdir, self.appname), '%s.configure.deployApp' % __name__, parameter ) path_list.append(deploy_app) diff --git a/slapos/recipe/boinc/configure.py b/slapos/recipe/boinc/configure.py index a05191fdb..dbc4f82d3 100644 --- a/slapos/recipe/boinc/configure.py +++ b/slapos/recipe/boinc/configure.py @@ -53,6 +53,7 @@ def checkMysql(args): print "Could not connect to MySQL database... sleep for 2 secondes" time.sleep(2) + def checkFile(file, stime): """Loop until 'file' is created (exist)""" while True: @@ -63,6 +64,7 @@ def checkFile(file, stime): else: break + def services(args): """This function configure a new installed boinc project instance""" print "Checking if needed to install or reinstall Boinc-server..." @@ -115,6 +117,7 @@ def services(args): writeFile(args['service_status'], "started") + def restart_boinc(args): """Stop (if currently is running state) and start all Boinc service""" if args['drop_install']: @@ -130,54 +133,68 @@ def restart_boinc(args): writeFile(args['start_boinc'], "started") print "Done." + def deployApp(args): """Fully deploy or redeploy or update a BOINC application using existing BOINC instance""" print "Cheking if needed to install %s..." % args['appname'] + install_request_file = os.path.join(args['home_dir'], + '.install_' + args['appname'] + args['version']) + if not os.path.exists(install_request_file): + print "No install or update request for %s version %s..." % ( + args['appname'], args['version']) + return + os.unlink(install_request_file) token = os.path.join(args['installroot'], "." + args['appname'] + args['version']) - dropapp = False + newInstall = False if os.path.exists(token): args['previous_wu'] = int(open(token, 'r').read().strip()) - if args['previous_wu'] >= args['wu_number']: - print args['appname'] + " version " + args['version'] + " is already installed in this Boinc instance... skipped" - return - else: + if args['previous_wu'] < args['wu_number']: print args['appname'] + " Work units will be updated from %s to %s" % ( args['previous_wu'], args['wu_number']) else: args['previous_wu'] = 0 - dropapp = True + newInstall = True #Sleep until file .start_boinc exist (File indicate that BOINC has been started) checkFile(args['start_boinc'], 3) print "setup directories..." args['inputfile'] = os.path.join(args['installroot'], 'download', - args['appname'] + '_input') + args['appname'] + args['version'] + '_input') base_app = os.path.join(args['installroot'], 'apps', args['appname']) base_app_version = os.path.join(base_app, args['version']) args['templates'] = os.path.join(args['installroot'], 'templates') - t_result = os.path.join(args['templates'], args['appname']+'_result') - t_wu = os.path.join(args['templates'], args['appname']+'_wu') + t_result = os.path.join(args['templates'], + args['appname'] + args['version'] + '_result') + t_wu = os.path.join(args['templates'], + args['appname'] + args['version'] + '_wu') + binary = os.path.join(args['application'], args['binary_name']) if not os.path.exists(base_app): os.mkdir(base_app) - if dropapp: + if newInstall: if os.path.exists(base_app_version): shutil.rmtree(base_app_version) os.mkdir(base_app_version) os.mkdir(args['application']) - if not os.path.exists(args['templates']): - os.mkdir(args['templates']) - else: - if os.path.exists(t_result): - os.unlink(t_result) - if os.path.exists(t_result): - os.unlink(t_wu) + if not os.path.exists(args['templates']): + os.mkdir(args['templates']) + if args['t_result']: + if os.path.exists(t_result): + os.unlink(t_result) shutil.copy(args['t_result'], t_result) + if args['t_wu']: + if os.path.exists(t_wu): + os.unlink(t_wu) shutil.copy(args['t_wu'], t_wu) - if not os.path.exists(args['inputfile']): - os.symlink(args['t_input'], args['inputfile']) - shutil.copy(args['binary'], os.path.join(args['application'], - args['binary_name'])) + if args['t_input']: + if os.path.exists(args['inputfile']): + os.unlink(args['inputfile']) + os.symlink(args['t_input'], args['inputfile']) + if args['binary'] and args['platform']: + if os.path.exists(binary): + os.unlink(binary) + shutil.copy(args['binary'], binary) + if newInstall: print "Adding '" + args['appname'] + "' to project.xml..." print "Adding deamon for application to config.xml..." project_xml = os.path.join(args['installroot'], 'project.xml') @@ -185,19 +202,18 @@ def deployApp(args): sed_args = [args['bash'], args['appname'], args['installroot']] startProcess(sed_args) + if args['binary'] and args['platform']: print "Sign the application binary..." sign = os.path.join(args['installroot'], 'bin/sign_executable') privateKeyFile = os.path.join(args['installroot'], 'keys/code_sign_private') - output = open(os.path.join(args['application'], args['binary_name']+'.sig'), 'w') - p_sign = subprocess.Popen([sign, os.path.join(args['application'], - args['binary_name']), privateKeyFile], stdout=output, + output = open(binary + '.sig', 'w') + p_sign = subprocess.Popen([sign, binary, privateKeyFile], stdout=output, stderr=subprocess.STDOUT) result = p_sign.communicate()[0] if p_sign.returncode is None or p_sign.returncode != 0: print "Failed to execute bin/sign_executable.\nThe error was: %s" % result return output.close() - #END if drop-app HERE print "Running xadd script..." env = os.environ @@ -231,19 +247,21 @@ def deployApp(args): print "Boinc Application deployment is done... writing end signal file..." writeFile(token, str(args['wu_number'])) + def create_wu(args, env): - t_result = "templates/" + args['appname'] + '_result' - t_wu = "templates/" + args['appname'] + '_wu' + t_result = "templates/" + args['appname'] + args['version'] + '_result' + t_wu = "templates/" + args['appname'] + args['version'] + '_wu' launch_args = [os.path.join(args['installroot'], 'bin/create_work'), '--appname', args['appname'], '--wu_name', '', '--wu_template', t_wu, '--result_template', t_result, '--min_quorum', '1', '--target_nresults', '1', - args['appname']+'_input'] + args['appname'] + args['version'] + '_input'] for i in range(args['previous_wu'], args['wu_number']): print "Creating project wroker %s..." % str(i+1) launch_args[4] = args['appname'] + str(i+1) + args['version'] + '_nodelete' startProcess(launch_args, env, args['installroot']) + def startProcess(launch_args, env=None, cwd=None, stdout=subprocess.PIPE): process = subprocess.Popen(launch_args, stdout=stdout, stderr=subprocess.STDOUT, env=env, @@ -254,13 +272,14 @@ def startProcess(launch_args, env=None, cwd=None, stdout=subprocess.PIPE): return False return True + def runCmd(args): """Wait for Boinc Client started and run boinc cmd""" client_config = os.path.join(args['installdir'], 'client_state.xml') checkFile(client_config, 5) time.sleep(10) #Scan client state xml to find client ipv4 adress - host = result = re.search("<ip_addr>([\w\d\.:]+)</ip_addr>", + host = re.search("<ip_addr>([\w\d\.:]+)</ip_addr>", open(client_config, 'r').read()).group(1) args['base_cmd'][2] = host + ':' + args['base_cmd'][2] print "Run boinccmd with host at %s " % args['base_cmd'][2] @@ -271,7 +290,8 @@ def runCmd(args): #Load or reload cc_config file startProcess(args['base_cmd'] + [args['cc_cmd']], cwd=args['installdir']) + def writeFile(file, content): f = open(file, 'w') f.write(content) - f.close() \ No newline at end of file + f.close() diff --git a/software/boinc/software.cfg b/software/boinc/software.cfg index 033339902..d411ad00f 100644 --- a/software/boinc/software.cfg +++ b/software/boinc/software.cfg @@ -38,7 +38,7 @@ download-only = true filename = upper_case #Application configuration app-name = upper_case -version = 1.0 +version = 1.00 exec-extension = #Please read Boinc platform before update platform value: http://boinc.berkeley.edu/trac/wiki/BoincPlatforms platform = x86_64-pc-linux-gnu diff --git a/stack/boinc/buildout.cfg b/stack/boinc/buildout.cfg index 6c18b691e..898827a8f 100644 --- a/stack/boinc/buildout.cfg +++ b/stack/boinc/buildout.cfg @@ -41,7 +41,7 @@ eggs = recipe = slapos.recipe.template url = ${:_profile_base_location_}/instance-boinc.cfg output = ${buildout:directory}/template-boinc.cfg -md5sum = b85d32e9509e960459c2ba1e47741838 +md5sum = 003bff525faa1e63913fa5f38c18becd mode = 0644 #Template for deploying MySQL Database Server @@ -57,7 +57,7 @@ recipe = slapos.recipe.download url = ${:_profile_base_location_}/template/${:filename} mode = 0644 filename = apache.in -md5sum = 2d3efa25bf1cf6a673fb2cef13ae0529 +md5sum = 0b3825a4a0ec82e279609d1f9dc72da4 location = ${buildout:parts-directory}/${:_buildout_section_name_} # Local development diff --git a/stack/boinc/instance-boinc.cfg b/stack/boinc/instance-boinc.cfg index 7f92a8c80..a7dc582dd 100644 --- a/stack/boinc/instance-boinc.cfg +++ b/stack/boinc/instance-boinc.cfg @@ -218,7 +218,8 @@ mysql-port = $${stunnel:local-port} [slap-application] <= boinc-server recipe = slapos.cookbook:boinc.app -#appname and version is require to update wu-number +#appname and version is require to update any existing application +#otherwise, the recipe would try to install a new one app-name = $${slap-parameter:app-name} version = $${slap-parameter:version} wu-number = $${slap-parameter:wu-number} -- 2.30.9