make_project, upgrade, other tools

svn path=/trunk/boinc/; revision=2399
This commit is contained in:
Karl Chen 2003-10-03 05:53:28 +00:00
parent bd07042ab8
commit d2ed1c768f
11 changed files with 406 additions and 566 deletions

View File

@ -6480,3 +6480,30 @@ Eric Oct 2 2003
api/graphics_api.h
lib/xml_util.[Ch]
Karl 2003/10/02
- rewrote make_project and related; a lot of restructuring:
- make_project only creates the project and database but doesn't add
platform, app, app_version, or core_version. Use `add' for these.
- database actions are through MySQLdb, removing need for mysql client
binary.
tools/
make_project
add
update_versions
upgrade (new)
py/Boinc/
configxml.py
database.py
setup_project.py
tools.py
test/
testbase.py
test_uc.py
sched/
boinc_config.py (removed)
db/
constraints.sql
doc/
single_host_server.php

View File

@ -631,39 +631,42 @@ def _connectp(dbname, user, passwd, host='localhost'):
boincdb = MySQLdb.connect(db=dbname,host=host,user=user,passwd=passwd,
cursorclass=MySQLdb.cursors.DictCursor)
# def _connectm(module):
# _connectp(module.database, module.username, module.password)
# def connect(readonly = False):
# """Connect if not already connected or if we're adding write permissions"""
# global boincdb
# if boincdb:
# if not readonly and boincdb.readonly:
# # re-open with write access
# boincdb.close()
# boincdb = None
# else:
# return 0
# if readonly:
# import password_settings_r
# _connectm(password_settings_r)
# else:
# import password_settings
# _connectm(password_settings)
# boincdb.readonly = readonly
# return 1
def connect(config = None):
def connect(config = None, nodb = False):
"""Connect if not already connected, using config values."""
global boincdb
if boincdb:
return 0
config = config or configxml.default_config().config
_connectp(config.db_name,
if nodb:
db = ''
else:
db = config.db_name
_connectp(db,
config.__dict__.get('db_user',''),
config.__dict__.get('db_passwd', ''))
return 1
def _execute_sql_script(cursor, filename):
for query in open(filename).read().split(';'):
query = query.strip()
if not query: continue
cursor.execute(query)
def create_database(config = None, drop_first = False):
''' creates a new database. '''
global boincdb
config = config or configxml.default_config().config
connect(config, nodb=True)
cursor = boincdb.cursor()
if drop_first:
cursor.execute("drop database if exists %s"%config.db_name)
cursor.execute("create database %s"%config.db_name)
cursor.execute("use %s"%config.db_name)
schema_path = os.path.join(boinc_path_config.TOP_SOURCE_DIR, 'db')
for file in ['schema.sql', 'constraints.sql']:
_execute_sql_script(cursor, os.path.join(schema_path, file))
cursor.close()
# alias
connect_default_config = connect

View File

@ -1,20 +1,14 @@
## $Id$
# module for setting up a new project (either a real project or a test project
# - see testbase.py).
#
# (This used to be boinc/py/boinc.py.)
# see tools/makeproject, test/testbase.py).
# TODO: make things work if build_dir != src_dir
# TODO: use database.py
# TODO: make sure things work if build_dir != src_dir
import boinc_path_config
import version
from Boinc import database, db_mid, configxml, tools
from Boinc.boinc_db import *
import os, sys, glob, time, shutil, re, random
# import MySQLdb
class Options:
pass
@ -27,7 +21,6 @@ options.have_init = False
options.install_method = None
options.echo_verbose = 1
options.is_test = False
options.client_bin_filename = version.CLIENT_BIN_FILENAME
options.drop_db_first = False
def init():
@ -164,8 +157,11 @@ def force_symlink(src, dest):
os.unlink(dest)
my_symlink(src, dest)
def rmtree(dir):
if os.path.exists(dir):
shutil.rmtree(dir)
# if os.path.exists(dir):
# shutil.rmtree(dir)
if not dir or dir == '/' or dir == '.' or ' ' in dir:
raise Exception
os.system("rm -rf %s"%dir)
def _remove_trail(s, suffix):
if s.endswith(suffix):
@ -240,122 +236,91 @@ def _check_vars(dict, **names):
# result = db.use_result()
# return result and result.fetch_row(0,1)
def num_results(db):
def num_results():
return database.Results.count()
def num_results_unsent(db):
def num_results_unsent():
return database.Results.count(server_state = RESULT_SERVER_STATE_UNSENT)
def num_results_in_progress(db):
def num_results_in_progress():
return database.Results.count(server_state = RESULT_SERVER_STATE_IN_PROGRESS)
def num_results_over(db):
def num_results_over():
return database.Results.count(server_state = RESULT_SERVER_STATE_OVER)
def num_wus(db):
def num_wus():
return database.Workunits.count()
def num_wus_assimilated(db):
def num_wus_assimilated():
return database.Workunits.count(assimilate_state = ASSIMILATE_DONE)
def num_wus_to_transition(db):
def num_wus_to_transition():
return database.Workunits.count(_extra_params = 'transition_time<%d'%(time.time()+30*86400))
def query_yesno(str):
'''Query user; default Yes'''
verbose_echo(0,'')
print str, "[Y/n] ",
return not raw_input().strip().lower().startswith('n')
def query_noyes(str):
'''Query user; default No'''
verbose_echo(0,'')
print str, "[y/N] ",
return raw_input().strip().lower().startswith('y')
def build_command_line(cmd, **kwargs):
for (key, value) in kwargs.items():
cmd += " -%s '%s'" %(key,value)
return cmd
# class Platform:
# def __init__(self, name, user_friendly_name=None):
# self.name = name
# self.user_friendly_name = user_friendly_name or name
def install_boinc_files(dest_dir):
def dir(*dirs):
return apply(os.path.join,(dest_dir,)+dirs)
# class CoreVersion:
# def __init__(self):
# self.version = 1
# self.platform = Platform(version.PLATFORM)
# self.exec_dir = builddir('client')
# self.exec_name = options.client_bin_filename
install_glob(srcdir('html_user/*.php'), dir('html_user/'))
install_glob(srcdir('html_user/*.inc'), dir('html_user/'))
install_glob(srcdir('html_user/class/*.inc'), dir('html_user/class/'))
install_glob(srcdir('html_user/include/*.inc'), dir('html_user/include/'))
install_glob(srcdir('html_ops/*.php'), dir('html_ops/'))
install_glob(srcdir('html_ops/*.inc'), dir('html_ops/'))
install(builddir('tools/country_select'), dir('html_user/'))
# class App:
# def __init__(self, name):
# assert(name)
# self.name = name
# copy all the backend programs
map(lambda (s): install(builddir('sched',s), dir('cgi-bin',s)),
[ 'cgi', 'file_upload_handler'])
map(lambda (s): install(builddir('sched',s), dir('bin',s)),
[ 'make_work', 'feeder', 'transitioner', 'validate_test',
'file_deleter', 'assimilator' ])
map(lambda (s): install(srcdir('sched',s), dir('bin',s)),
[ 'start', 'stop', 'status',
'grep_logs' ])
map(lambda (s): install(srcdir('tools',s), dir('bin',s)),
[ 'boinc_path_config.py', 'add', 'dbcheck_files_exist',
'upgrade' ])
# class AppVersion:
# def __init__(self, app, appversion = 1, exec_names=None):
# self.exec_names = []
# self.exec_dir = builddir('apps')
# self.exec_names = exec_names or [app.name]
# self.app = app
# self.version = appversion
# self.platform = Platform(version.PLATFORM)
class Project:
def __init__(self,
short_name, long_name, appname=None,
project_dir=None, master_url=None, cgi_url=None,
core_versions=None, key_dir=None,
apps=None, app_versions=None,
resource_share=None):
short_name, long_name,
project_dir=None,key_dir=None,
master_url=None, cgi_url=None,
db_name=None):
init()
self.config_options = []
self.config_daemons = []
self.short_name = short_name or 'test_'+appname
self.long_name = long_name or 'Project ' + self.short_name.replace('_',' ').capitalize()
self.db_passwd = ''
self.shmem_key = generate_shmem_key()
self.resource_share = resource_share or 1
self.output_level = 3
self.master_url = master_url or os.path.join(options.html_url , self.short_name , '')
self.download_url = os.path.join(self.master_url, 'download')
self.cgi_url = cgi_url or os.path.join(options.cgi_url, self.short_name)
self.upload_url = os.path.join(self.cgi_url , 'file_upload_handler')
self.scheduler_url = os.path.join(self.cgi_url , 'cgi')
self.project_dir = project_dir or os.path.join(options.projects_dir , self.short_name)
self.download_dir = os.path.join(self.project_dir , 'download')
self.upload_dir = os.path.join(self.project_dir , 'upload')
self.key_dir = key_dir or os.path.join(self.project_dir , 'keys')
self.user_name = options.user_name
self.db_name = self.user_name + '_' + self.short_name
self.short_name = short_name
self.long_name = long_name or 'Project ' + self.short_name.replace('_',' ').capitalize()
self.project_dir = project_dir or os.path.join(options.projects_dir, self.short_name)
self.config = configxml.ConfigFile(self.dir('config.xml')).init_empty()
config = self.config.config
config.user_name = options.user_name
config.db_name = db_name or config.user_name + '_' + self.short_name
config.db_passwd = ''
config.shmem_key = generate_shmem_key()
config.output_level = 3
config.master_url = master_url or os.path.join(options.html_url , self.short_name , '')
config.download_url = os.path.join(config.master_url, 'download')
config.cgi_url = cgi_url or os.path.join(options.cgi_url, self.short_name)
config.upload_url = os.path.join(config.cgi_url , 'file_upload_handler')
self.scheduler_url = os.path.join(config.cgi_url , 'cgi')
config.download_dir = os.path.join(self.project_dir , 'download')
config.upload_dir = os.path.join(self.project_dir , 'upload')
config.key_dir = key_dir or os.path.join(self.project_dir , 'keys')
self.project_php_file = srcdir('html_user/project.inc.sample')
self.project_specific_prefs_php_file = srcdir('html_user/project_specific_prefs.inc.sample')
self.core_versions = core_versions or [CoreVersion()]
self.app_versions = app_versions or [AppVersion(App(appname))]
self.apps = apps or unique(map(lambda av: av.app, self.app_versions))
self.platforms = [Platform(version.PLATFORM)]
# convenience vars:
self.app_version = self.app_versions[0]
self.app = self.apps[0]
def dir(self, *dirs):
return apply(os.path.join,(self.project_dir,)+dirs)
def keydir(self, *dirs):
return apply(os.path.join,(self.key_dir,)+dirs)
def run_db_script(self, script):
shell_call('mysql %s < %s' % (self.db_name,srcdir('db', script)))
def drop_db_if_exists(self):
shell_call('echo "drop database if exists %s" | mysql' % self.db_name)
def create_db(self):
if options.drop_db_first:
self.drop_db_if_exists()
shell_call('echo "create database %s" | mysql' % self.db_name)
def db_open(self):
return MySQLdb.connect(db=self.db_name)
return apply(os.path.join,(self.config.config.key_dir,)+dirs)
def create_keys(self):
if not os.path.exists(self.keydir()):
@ -364,7 +329,7 @@ class Project:
_gen_key(self.keydir('code_sign'))
def query_create_keys(self):
return query_yesno("Keys don't exist in %s; generate them?"%self.key_dir)
return query_yesno("Keys don't exist in %s; generate them?"%self.keydir())
def keys_exist(self):
keys = ['upload_private', 'upload_public',
@ -395,28 +360,23 @@ class Project:
self.create_keys()
# copy the user and administrative PHP files to the project dir,
verbose_echo(1, "Setting up server files: copying html directories")
verbose_echo(1, "Setting up server files: copying files")
install_boinc_files(self.dir())
install_glob(srcdir('html_user/*.php'), self.dir('html_user/'))
install_glob(srcdir('html_user/*.inc'), self.dir('html_user/'))
install_glob(srcdir('html_user/class/*.inc'), self.dir('html_user/class/'))
install_glob(srcdir('html_user/include/*.inc'), self.dir('html_user/include/'))
install_glob(srcdir('html_user/*.txt'), self.dir('html_user/'))
install_glob(srcdir('html_ops/*.php'), self.dir('html_ops/'))
install_glob(srcdir('html_ops/*.inc'), self.dir('html_ops/'))
install(builddir('tools/country_select'), self.dir('html_user/'))
install(self.project_php_file,
self.dir('html_user', 'project_specific', 'project.inc'))
install(self.project_specific_prefs_php_file,
self.dir('html_user', 'project_specific', 'project_specific_prefs.inc'))
my_symlink(self.download_dir, self.dir('html_user', 'download'))
my_symlink(self.config.config.download_dir, self.dir('html_user', 'download'))
# Copy the sched server in the cgi directory with the cgi names given
# source_dir/html_usr/schedulers.txt
#
verbose_echo(1, "Setting up server files: copying cgi programs");
if scheduler_file:
r = re.compile('<scheduler>([^<]+)</scheduler>', re.IGNORECASE)
f = open(self.dir('html_user', scheduler_file))
@ -432,91 +392,20 @@ class Project:
else:
scheduler_file = 'schedulers.txt'
f = open(self.dir('html_user', scheduler_file), 'w')
print >>f, "<scheduler>" + self.scheduler_url, "</scheduler>"
print >>f, "<scheduler>" + self.scheduler_url.strip(), "</scheduler>"
f.close()
# copy all the backend programs
map(lambda (s): install(builddir('sched',s), self.dir('cgi-bin',s)),
[ 'cgi', 'file_upload_handler'])
map(lambda (s): install(builddir('sched',s), self.dir('bin',s)),
[ 'make_work', 'feeder', 'transitioner', 'validate_test',
'file_deleter', 'assimilator' ])
map(lambda (s): install(srcdir('sched',s), self.dir('bin',s)),
[ 'start', 'stop', 'status',
'boinc_config.py', 'grep_logs' ])
verbose_echo(1, "Setting up database")
self.create_db()
map(self.run_db_script, [ 'schema.sql' ])
database.connect()
database.create_database(config = self.config.config,
drop_first = options.drop_db_first)
self.project = database.Project(short_name = self.short_name,
long_name = self.long_name)
self.project.commit()
verbose_echo(1, "Setting up database: adding %d apps(s)" % len(self.apps))
for app in self.apps:
db.query("insert into app(name, create_time) values ('%s', %d)" %(
app.name, time.time()))
verbose_echo(1, "Setting up server files: writing config files")
self.platforms = unique(map(lambda a: a.platform, self.app_versions))
verbose_echo(1, "Setting up database: adding %d platform(s)" % len(self.platforms))
db.close()
for platform in self.platforms:
cmd = build_command_line("old_add platform",
db_name = self.db_name,
platform_name = platform.name,
user_friendly_name = platform.user_friendly_name)
run_tool(cmd)
verbose_echo(1, "Setting up database: adding %d core version(s)" % len(self.core_versions))
for core_version in self.core_versions:
cmd = build_command_line("old_add core_version",
db_name = self.db_name,
platform_name = core_version.platform.name,
version = core_version.version,
download_dir = self.download_dir,
download_url = self.download_url,
exec_dir = core_version.exec_dir,
exec_files = core_version.exec_name)
run_tool(cmd)
verbose_echo(1, "Setting up database: adding %d app version(s)" % len(self.app_versions))
for app_version in self.app_versions:
app = app_version.app
cmd = ("old_add app_version -db_name %s -app_name '%s'" +
" -platform_name %s -version %s -download_dir %s -download_url %s" +
" -code_sign_keyfile %s -exec_dir %s -exec_files") % (
self.db_name, app.name, app_version.platform.name,
app_version.version,
self.download_dir,
self.download_url,
os.path.join(self.key_dir, 'code_sign_private'),
app_version.exec_dir)
for exec_name in app_version.exec_names:
check_app_executable(exec_name)
cmd += ' ' + exec_name
run_tool(cmd)
verbose_echo(1, "Setting up server files: writing config files");
config = map_xml(self,
[ 'db_name', 'db_passwd', 'shmem_key',
'key_dir', 'download_url', 'download_dir',
'upload_url', 'upload_dir', 'project_dir', 'user_name',
'cgi_url',
'output_level' ])
self.config_options = config.split('\n')
self.write_config()
# edit "index.php" in the user HTML directory to have the right file
# as the source for scheduler_urls; default is schedulers.txt
macro_substitute_inplace('FILE_NAME', scheduler_file,
self.dir('html_user', 'index.php'))
self.config.write()
# create symbolic links to the CGI and HTML directories
verbose_echo(1, "Setting up server files: linking cgi programs")
@ -526,12 +415,6 @@ class Project:
force_symlink(self.dir('html_user'), os.path.join(options.html_dir, self.short_name))
force_symlink(self.dir('html_ops'), os.path.join(options.html_dir, self.short_name+'_admin'))
# show the URLs for user and admin sites
# admin_url = os.path.join("html_user", self.short_name+'_admin/')
# verbose_echo(2, "Master URL: " + self.master_url)
# verbose_echo(2, "Admin URL: " + admin_url)
def http_password(self, user, password):
'Adds http password protection to the html_ops directory'
passwd_file = self.dir('html_ops', '.htpassword')
@ -585,11 +468,11 @@ class Project:
self._run_sched_prog(prog, '-d 3 -one_pass '+cmdline)
def sched_install(self, prog, **kwargs):
for cmdline in self._build_sched_commandlines(prog, kwargs):
self.config_daemons.append("%s -d 3 %s" %(prog, cmdline))
self.write_config()
def sched_uninstall(self, prog):
self.config_daemons = filter(lambda l: l.find(prog)==-1, self.config_daemons)
self.write_config()
self.config.daemons.make_node_and_append("daemon").cmd = "%s -d 3 %s" %(prog, cmdline)
self.config.write()
# def sched_uninstall(self, prog):
# self.config_daemons = XXX filter(lambda l: l.find(prog)==-1, self.config_daemons)
# self.config.write()
def start_stripcharts(self):
map(lambda l: self.copy(os.path.join('stripchart', l), 'cgi-bin/'),
@ -613,15 +496,10 @@ class Project:
def maybe_stop(self):
if self.started: self.stop()
def write_config(self):
f = open(self.dir('config.xml'), 'w')
print >>f, '<boinc>'
print >>f, ' <config>'
for line in self.config_options:
print >>f, " ", line
print >>f, ' </config>'
print >>f, ' <daemons>'
for daemon in self.config_daemons:
print >>f, " <daemon><cmd>%s</cmd></daemon>"%daemon
print >>f, ' </daemons>'
print >>f, '</boinc>'
def query_noyes(str):
verbose_echo(0,'')
return tools.query_noyes(str)
def query_yesno(str):
verbose_echo(0,'')
return tools.query_yesno(str)

View File

@ -14,10 +14,10 @@ def file_size(path):
f.seek(0,2)
return f.tell()
def sign_executable(executable_path):
def sign_executable(executable_path, quiet=False):
'''Returns signed text for executable'''
config = configxml.default_config()
print 'Signing', executable_path
if not quiet: print 'Signing', executable_path
code_sign_key = os.path.join(config.config.key_dir, 'code_sign_private')
sign_executable_path = os.path.join(boinc_path_config.TOP_BUILD_DIR,
'tools','sign_executable')
@ -29,7 +29,7 @@ def sign_executable(executable_path):
raise SystemExit("Couldn't sign executable %s"%executable_path)
return signature_text
def process_executable_file(file, signature_text=None):
def process_executable_file(file, signature_text=None, quiet=False):
'''Handle a new executable file to be added to the database.
1. Copy file to download_dir if necessary.
@ -42,7 +42,7 @@ def process_executable_file(file, signature_text=None):
file_dir, file_base = os.path.split(file)
target_path = os.path.join(config.config.download_dir, file_base)
if file_dir != config.config.download_dir:
print "Copying %s to %s"%(file_base, config.config.download_dir)
if not quiet: print "Copying %s to %s"%(file_base, config.config.download_dir)
shutil.copy(file, target_path)
xml = '''<file_info>
@ -60,7 +60,7 @@ def process_executable_file(file, signature_text=None):
xml += ' <nbytes>%f</nbytes>\n</file_info>\n' % file_size(target_path)
return xml
def process_app_version(app, version_num, exec_files, signature_files={}):
def process_app_version(app, version_num, exec_files, signature_files={}, quiet=False):
"""Return xml for application version
app is an instance of database.App
@ -80,8 +80,8 @@ def process_app_version(app, version_num, exec_files, signature_files={}):
if signature_file:
signature_text = open(signature_file).read()
else:
signature_text = sign_executable(exec_file)
xml_doc += process_executable_file(exec_file, signature_text)
signature_text = sign_executable(exec_file, quiet=quiet)
xml_doc += process_executable_file(exec_file, signature_text, quiet=quiet)
xml_doc += ('<app_version>\n'+
' <app_name>%s</app_name>\n'+
@ -101,3 +101,13 @@ def process_app_version(app, version_num, exec_files, signature_files={}):
xml_doc += '</app_version>\n'
return xml_doc
def query_yesno(str):
'''Query user; default Yes'''
print str, "[Y/n] ",
return not raw_input().strip().lower().startswith('n')
def query_noyes(str):
'''Query user; default No'''
print str, "[y/N] ",
return raw_input().strip().lower().startswith('y')

View File

@ -1,200 +0,0 @@
#!/usr/bin/env python
# $Id$
# boinc_config.py - module to read and parse config.xml, run_state.xml
'''
SYNOPSIS: parses and writes config.xml and run_state.xml
USAGE: from boinc_config import *
config = BoincConfig('confing.xml').read()
run_state = BoincRunState('run_state.xml').read()
print config.config.db_name
print config.tasks[4].cmd
run_state.enabled = True
new_task = newConfigDict()
new_task.cmd = "echo hi | mail quarl"
config.tasks.append(new_task)
config.write()
run_state.write()
TODO: create a Boinc package and remove these Boinc prefix qualifiers
'''
import sys
import xml.dom.minidom
CONFIG_FILE = '../config.xml'
RUN_STATE_FILE = '../run_state.xml'
def _append_new_element(parent_node, name):
new_element = xml.dom.minidom.Element(name)
if isinstance(parent_node,xml.dom.minidom.Document):
new_element.ownerDocument = parent_node
else:
assert(parent_node.ownerDocument)
new_element.ownerDocument = parent_node.ownerDocument
parent_node.appendChild(new_element)
return new_element
def _get_elements(node, name):
return node.getElementsByTagName(name)
def _get_element(node, name, optional=True):
try:
return _get_elements(node,name)[0]
except IndexError:
if optional:
return _append_new_element(node, name)
raise SystemExit("ERROR: Couldn't find xml node <%s>"% name)
def _None2Str(object):
if object == None:
return ''
else:
return object
def _get_element_data(node):
return node and _None2Str(node.firstChild and node.firstChild.data)
def _get_element_int(node, default=0):
try:
return int(_get_element_data(node))
except:
return default
def _get_child_elements(node):
return filter(lambda node: node.nodeType == node.ELEMENT_NODE, node.childNodes)
def _set_element(node, new_data):
if node.firstChild and node.firstChild.data:
node.firstChild.data = str(new_data)
else:
new_data_node = xml.dom.minidom.Text()
new_data_node.data = str(new_data)
new_data_node.ownerDocument = node.ownerDocument
node.appendChild(new_data_node)
class ConfigDict:
def __init__(self, dom_node):
self._node = dom_node
self._name = self._node.nodeName
for node in _get_child_elements(self._node):
self.__dict__[node.nodeName] = _get_element_data(node)
def save(self):
for key in self.__dict__:
if key.startswith('_'): continue
_set_element( _get_element(self._node,key,1), str(self.__dict__[key]) )
def debug_print(self):
for key in self.__dict__.keys():
print key.rjust(15), '=', self.__dict__[key]
class ConfigDictList(list):
def __init__(self, dom_node, item_class=ConfigDict):
self._node = dom_node
list.__init__(self, map(item_class, _get_child_elements(self._node)))
def save(self):
map(ConfigDict.save, self)
def make_node_and_append(self, name):
'''Make a new ConfigDict and append it. Returns new ConfigDict.'''
new_element = _append_new_element(self._node, name)
new_cd = ConfigDict(new_element)
self.append(new_cd)
return new_cd
# base class for xml config files
class XMLConfig:
def __init__(self, filename):
self.filename = filename
def read(self, failopen_ok=False):
try:
self.xml = xml.dom.minidom.parse(self.filename)
except IOError, e:
if not failopen_ok:
raise
print >>sys.stderr, "Warning:", e
# self.xml = xml.dom.minidom.Document()
self.xml = xml.dom.minidom.parseString(self.default_xml)
self._get_elements()
return self
def _get_elements(self):
pass
def write(self, output=None):
self._set_elements()
if not output:
output = open(self.filename,'w')
self.xml.writexml(output)
print >>output
return self
def _set_elements(self):
pass
# normal config file
class BoincConfig(XMLConfig):
'''
embodies config.xml
Public attributes:
config - ConfigDict
tasks - list of ConfigDict elements
'''
def _get_elements(self):
self.xml_boinc = _get_element(self.xml, 'boinc', optional=False)
self.xml_config = _get_element(self.xml_boinc, 'config', optional=False)
self.xml_tasks = _get_element(self.xml_boinc, 'tasks')
self.xml_daemons = _get_element(self.xml_boinc, 'daemons')
self.config = ConfigDict(self.xml_config)
self.daemons = ConfigDictList(self.xml_daemons)
self.tasks = ConfigDictList(self.xml_tasks)
def _set_elements(self):
self.config.save()
self.daemons.save()
self.tasks.save()
def debug_print_all(self):
'''print everything to stdout.'''
print 'Debug dump of', self.filename
print '-- parsed xml -------------------------------------------------------'
self.xml.writexml(sys.stdout)
print
print '-- Config -----------------------------------------------------------'
self.config.debug_print()
print
print '-- Daemons ------------------------------------------------------------'
for daemon in self.daemons:
daemon.debug_print()
print
print
print '-- Tasks ------------------------------------------------------------'
for task in self.tasks:
task.debug_print()
print
default_xml = '<boinc><config></config></boinc>'
# keeps BoincCron's timestamp status file
class BoincRunState(XMLConfig):
'''
embodies run_state.xml
Public attributes:
tasks - list of ConfigDict elements
enabled - boolean
'''
def _get_elements(self):
self.xml_boinc = _get_element(self.xml, 'boinc', optional=False)
self.xml_tasks = _get_element(self.xml_boinc, 'tasks')
self.xml_enabled = _get_element(self.xml_boinc, 'enabled')
self.tasks = ConfigDictList(self.xml_tasks)
self.enabled = _get_element_int(self.xml_enabled)
def _set_elements(self):
_set_element( self.xml_enabled, self.enabled )
self.tasks.save()
default_xml = '<boinc></boinc>'
if __name__ == '__main__':
config = BoincConfig('config.xml')
config.read()
# print "setting config.enabled = True"
# config.enabled = True
config.debug_print_all()
print " -- saving xml and rewriting -----------------------------------------------"
config.write(sys.stdout)

View File

@ -9,16 +9,24 @@
from testbase import *
class UserUC(User):
def __init__(self):
User.__init__(self)
self.project_prefs = "<project_specific>\nfoobar\n</project_specific>"
self.global_prefs = """<venue name=\"home\">
def init(self):
self.User.__init__()
self.project_prefs = """<project_preferences>
<project_specific>
foobar
</project_specific>
</project_preferences>
"""
self.global_prefs = """<global_preferences>
<venue name=\"home\">
<work_buf_min_days>0</work_buf_min_days>
<work_buf_max_days>2</work_buf_max_days>
<disk_interval>1</disk_interval>
<run_on_batteries/>
<max_bytes_sec_down>400000</max_bytes_sec_down>
</venue>"""
</venue>
</global_preferences>
"""
class WorkUC(Work):
def __init__(self, redundancy, **kwargs):
@ -28,7 +36,7 @@ class WorkUC(Work):
self.input_files = ['input']
self.__dict__.update(kwargs)
class ResultUC(Result):
class ResultUC(ExpectedResult):
def __init__(self):
self.stderr_out = MATCH_REGEXPS([
"<stderr_txt>",
@ -36,7 +44,7 @@ class ResultUC(Result):
"APP: upper_case: argv[[]0[]] is upper_case",
"APP: upper_case ending, wrote \\d+ chars"])
class ResultComputeErrorUC(ResultComputeError):
class ResultComputeErrorUC(ExpectedResultComputeError):
def __init__(self):
self.stderr_out = MATCH_REGEXPS([ """<stderr_txt>
APP: upper_case: starting, argc \\d+"""])

View File

@ -19,15 +19,16 @@ import cgiserver
options.have_init_t = False
options.echo_overwrite = False
options.client_bin_filename = version.CLIENT_BIN_FILENAME
def test_init():
if options.have_init_t: return
options.have_init_t = True
if not os.path.exists('test_uc.py'):
if not os.path.exists('testbase.py'):
os.chdir(os.path.join(boinc_path_config.TOP_SOURCE_DIR,'test'))
if not os.path.exists('test_uc.py'):
raise SystemExit('Could not find boinc_db.py anywhere')
if not os.path.exists('testbase.py'):
raise SystemExit('Could not find testbase.py anywhere')
#options.program_path = os.path.realpath(os.path.dirname(sys.argv[0]))
options.program_path = os.getcwd()
@ -170,13 +171,13 @@ def dict_match(dic, resultdic):
format = "result %s: unexpected %s '%s' (expected '%s')"
error( format % (id, key, found, expected))
class Result:
class ExpectedResult:
def __init__(self):
self.server_state = RESULT_SERVER_STATE_OVER
self.client_state = RESULT_FILES_UPLOADED
self.outcome = RESULT_OUTCOME_SUCCESS
class ResultComputeError:
class ExpectedResultComputeError:
def __init__(self):
self.server_state = RESULT_SERVER_STATE_OVER
self.client_state = RESULT_COMPUTE_DONE
@ -228,24 +229,38 @@ def get_redundancy_args(num_wu = None, redundancy = None):
return (num_wu, redundancy)
class TestProject(Project):
def __init__(self, works, expected_result,
def __init__(self, works, expected_result, appname=None,
num_wu=None, redundancy=None,
users=None, hosts=None,
add_to_list=True,
apps=None, app_versions=None, core_versions=None,
resource_share=None,
**kwargs):
test_init()
if add_to_list:
all_projects.append(self)
kwargs['short_name'] = kwargs.get('short_name') or 'test_'+kwargs['appname']
kwargs['short_name'] = kwargs.get('short_name') or 'test_'+appname
kwargs['long_name'] = kwargs.get('long_name') or 'Project ' + kwargs['short_name'].replace('_',' ').capitalize()
(num_wu, redundancy) = get_redundancy_args(num_wu, redundancy)
self.resource_share = resource_share or 1
self.num_wu = num_wu
self.redundancy = redundancy
self.expected_result = expected_result
self.works = works
self.users = users or [User()]
self.hosts = hosts or [Host()]
self.platforms = [Platform()]
self.core_versions = core_versions or [CoreVersion(self.platforms[0])]
self.app_versions = app_versions or [AppVersion(App(appname),
self.platforms[0],
appname)]
self.apps = apps or unique(map(lambda av: av.app, self.app_versions))
# convenience vars:
self.app_version = self.app_versions[0]
self.app = self.apps[0]
# convenience vars:
self.work = self.works[0]
self.user = self.users[0]
@ -263,28 +278,6 @@ class TestProject(Project):
'''Overrides Project::query_create_keys() to always return true'''
return True
def install_project_users(self):
db = self.db_open()
verbose_echo(1, "Setting up database: adding %d user(s)" % len(self.users))
for user in self.users:
if user.project_prefs:
pp = "<project_preferences>\n%s\n</project_preferences>\n" % user.project_prefs
else:
pp = ''
if user.global_prefs:
gp = "<global_preferences>\n%s\n</global_preferences>\n" % user.global_prefs
else:
gp = ''
db.query(("insert into user values (0, %d, '%s', '%s', '%s', " +
"'Peru', '12345', 0, 0, 0, '%s', '%s', 0, 'home', '', 0, 1, 0)") % (
time.time(),
user.email_addr,
user.name,
user.authenticator,
gp,
pp))
def install_works(self):
for work in self.works:
work.install(self)
@ -295,10 +288,30 @@ class TestProject(Project):
host.add_user(user, self)
host.install()
def install_platforms_versions(self):
def commit(list):
for item in list: item.commit()
self.platforms = unique(map(lambda a: a.platform, self.app_versions))
verbose_echo(1, "Setting up database: adding %d platform(s)" % len(self.platforms))
commit(self.platforms)
verbose_echo(1, "Setting up database: adding %d core version(s)" % len(self.core_versions))
commit(self.core_versions)
verbose_echo(1, "Setting up database: adding %d apps(s)" % len(self.apps))
commit(self.apps)
verbose_echo(1, "Setting up database: adding %d app version(s)" % len(self.app_versions))
commit(self.app_versions)
verbose_echo(1, "Setting up database: adding %d user(s)" % len(self.users))
commit(self.users)
def install(self):
self.init_install()
self.install_project()
self.install_project_users()
self.install_platforms_versions()
self.install_works()
self.install_hosts()
@ -318,14 +331,12 @@ class TestProject(Project):
If more than X seconds have passed than just assume something is broken and return.'''
db = self.db_open()
timeout = time.time() + 3*60
while (num_wus_assimilated(db) < self.num_wu) or num_wus_to_transition(db):
while (num_wus_assimilated() < self.num_wu) or num_wus_to_transition():
time.sleep(.5)
if time.time() > timeout:
error("run_finish_wait(): timed out waiting for workunits to assimilate/transition")
break
db.close()
def check(self):
# verbose_sleep("Sleeping to allow server daemons to finish", 5)
@ -337,17 +348,16 @@ class TestProject(Project):
# self.check_deleted("upload/uc_wu_%d_0", count=self.num_wu)
def progress_meter_ctor(self):
self.db = self.db_open()
pass
def progress_meter_status(self):
return "WUs: [%dassim/%dtotal/%dtarget] Results: [%dunsent,%dinProg,%dover/%dtotal]" % (
num_wus_assimilated(self.db), num_wus(self.db), self.num_wu,
num_results_unsent(self.db),
num_results_in_progress(self.db),
num_results_over(self.db),
num_results(self.db))
num_wus_assimilated(), num_wus(), self.num_wu,
num_results_unsent(),
num_results_in_progress(),
num_results_over(),
num_results())
def progress_meter_dtor(self):
self.db.close()
self.db = None
pass
def _disable(self, *path):
'''Temporarily disable a file to test exponential backoff'''
@ -382,13 +392,11 @@ class TestProject(Project):
exit_status
'''
expected_count = expected_count or self.redundancy
db = self.db_open()
rows = db_query(db,"select * from result")
for row in rows:
dict_match(matchresult, row)
db.close()
if len(rows) != expected_count:
error("expected %d results, but found %d" % (expected_count, len(rows)))
results = database.Results.find()
for result in results:
dict_match(matchresult, result.__dict__)
if len(results) != expected_count:
error("expected %d results, but found %d" % (expected_count, len(results)))
def check_files_match(self, result, correct, count=None):
'''if COUNT is specified then [0,COUNT) is mapped onto the %d in RESULT'''
@ -414,16 +422,47 @@ class TestProject(Project):
return errs
return check_exists(self.dir(file))
class Platform(database.Platform):
def __init__(self, name=None, user_friendly_name=None):
database.Platform.__init__(self)
self.name = name or version.PLATFORM
self.user_friendly_name = user_friendly_name or name
class CoreVersion(database.CoreVersion):
def __init__(self, platform):
database.CoreVersion.__init__(self)
self.version_num = 1
self.platform = platform
self.xml_doc = tools.process_executable_file(
os.path.join(boinc_path_config.TOP_BUILD_DIR,'client',
options.client_bin_filename),
quiet=True)
class User:
'''represents an account on a particular project'''
class User(database.User):
def __init__(self):
database.User.__init__(self,id=None)
self.name = 'John'
self.email_addr = 'john@boinc.org'
self.authenticator = "3f7b90793a0175ad0bda68684e8bd136"
self.project_prefs = None
self.global_prefs = None
class App(database.App):
def __init__(self, name):
database.App.__init__(self,id=None)
self.name = name
self.min_version = 1
class AppVersion(database.AppVersion):
def __init__(self, app, platform, exec_file):
database.AppVersion.__init__(self,id=None)
self.app = app
self.version_num = 1
self.platform = platform
self.xml_doc = tools.process_app_version(
app, self.version_num,
[os.path.join(boinc_path_config.TOP_BUILD_DIR,'apps',exec_file)],
quiet=True)
self.min_core_version = 1
self.max_core_version = 999
class HostList(list):
def run(self, asynch=False): map(lambda i: i.run(asynch=asynch), self)
@ -457,12 +496,12 @@ class Host:
verbose_echo(1, "Setting up host '%s': creating account files" % self.name);
for (user,project) in map(None,self.users,self.projects):
filename = self.dir(account_file_name(project.master_url))
filename = self.dir(account_file_name(project.config.config.master_url))
verbose_echo(2, "Setting up host '%s': writing %s" % (self.name, filename))
f = open(filename, "w")
print >>f, "<account>"
print >>f, map_xml(project, ['master_url'])
print >>f, map_xml(project.config.config, ['master_url'])
print >>f, map_xml(user, ['authenticator'])
if user.project_prefs:
print >>f, user.project_prefs
@ -509,6 +548,7 @@ class Host:
_url_to_filename(project.master_url),
filename))
# TODO: do this in Python
class Work:
def __init__(self, redundancy, **kwargs):
self.input_files = []
@ -534,7 +574,8 @@ class Work:
self.app = project.app_versions[0].app
for input_file in unique(self.input_files):
install(os.path.realpath(input_file),
os.path.join(project.download_dir,os.path.basename(input_file)))
os.path.join(project.config.config.download_dir,
os.path.basename(input_file)))
# simulate multiple data servers by making symbolic links to the
# download directory
@ -558,11 +599,11 @@ class Work:
os.symlink(handler, newhandler)
cmd = build_command_line("create_work",
db_name = project.db_name,
download_dir = project.download_dir,
upload_url = project.upload_url,
download_url = project.download_url,
keyfile = os.path.join(project.key_dir,'upload_private'),
db_name = project.config.config.db_name,
download_dir = project.config.config.download_dir,
upload_url = project.config.config.upload_url,
download_url = project.config.config.download_url,
keyfile = os.path.join(project.config.config.key_dir,'upload_private'),
appname = self.app.name,
rsc_fpops_est = self.rsc_fpops_est,
rsc_fpops_bound = self.rsc_fpops_bound,

View File

@ -2,6 +2,9 @@
# $Id$
#XXX TODO: add app should modify config.xml to add application-specific daemons
'''
add items to the BOINC database.

View File

@ -8,17 +8,27 @@
# TODO: create 'apps' subdirectory and set it in config.xml
# TODO: use configxml module
import sys, os, getopt, re
sys.path.append('../py')
from version import *
from boinc import *
import boinc_path_config
from Boinc.setup_project import *
from Boinc import database, db_mid, configxml, tools
import sys, os, getopt, re, socket
def gethostname():
try:
return socket.gethostbyaddr(socket.gethostname())[0]
except:
return 'localhost'
def isurl(s):
return s.startswith('http://') or s.startswith('https://')
argv0 = sys.argv[0]
HOME = os.path.expanduser('~')
USER = os.environ['USER']
NODENAME = gethostname()
HELP = """
syntax: %(argv0)s [options] project-dir-name 'Project Long Name' app-exe 'AppName'
syntax: %(argv0)s [options] project ['Project Long Name']
Creates a new project with given name with everything running on a single
server.
@ -35,15 +45,17 @@ Dir-options:
--base default: $HOME (%(HOME)s)
--key_dir default: BASE/keys
--project_root default: BASE/projects/PROJECT
--url_base REQUIRED; e.g.: http://maggie.ssl.berkeley.edu/
--url_base default: http://$NODENAME/ (http://%(NODENAME)s/)
--html_user_url default: URL_BASE/PROJECT/
--html_ops_url default: URL_BASE/PROJECT_ops/
--cgi_url default: URL_BASE/PROJECT_cgi/
Other:
--db_name default: PROJECT
Example command line:
./make_project --base $HOME/boinc --url_base http://boink/ yah 'YETI @ Home' \\
upper_case 'UpperCase'
./make_project --base $HOME/boinc --url_base http://boink/ yah 'YETI @ Home'
Then upload_dir = $HOME/boinc/projects/yah/upload
and cgi_url = http://boink/yah_cgi/
@ -104,7 +116,7 @@ options.delete_prev_inst = False
for o,a in opts:
if o == '-h' or o == '--help': usage()
elif o == '-v': options.echo_verbose = 2
elif o == '-v': options.echo_verbose = 2
elif o == '--verbose': options.echo_verbose = int(a)
elif o == '--no_query': options.no_query = True
elif o == '--user_name': options.user_name = a
@ -117,6 +129,7 @@ for o,a in opts:
elif o == '--html_user_url': options.html_user_url = a
elif o == '--html_ops_url': options.html_ops_url = a
elif o == '--cgi_url': options.cgi_url = a
elif o == '--db_name': options.db_name = a
# elif o == '--bin_dir': options.bin_dir = a
# elif o == '--cgi_bin_dir': options.cgi_bin_dir = a
# elif o == '--html_user_dir': options.html_user_dir = a
@ -128,43 +141,45 @@ for o,a in opts:
else:
raise SystemExit('internal error o=%s'%o)
if len(args) != 4:
syntax_error('Need four arguments')
(project_shortname, project_longname, app_exe, app_name) = args
if not options.url_base:
syntax_error('Need --url_base')
if not options.url_base.startswith('http://'):
syntax_error('url_base needs to be an URL')
options.url_base = os.path.join(options.url_base, '')
if len(args) == 2:
(project_shortname, project_longname) = args
elif len(args) == 1:
(project_shortname, project_longname) = args[0], args[0].capitalize()
else:
syntax_error('Need one or two arguments')
opt_repls = {'PROJECT':project_shortname,
'PROJECT_ops':project_shortname+'_ops',
'PROJECT_cgi':project_shortname+'_cgi',
'URL_BASE':options.url_base}
def replopt(str):
for key in opt_repls:
str = re.compile('\\b'+key+'\\b').sub(os.path.join(opt_repls[key],'')[:-1], str)
return str
'PROJECT_cgi':project_shortname+'_cgi'}
def delete_slash(str):
return os.path.join(str,'')[:-1]
def add_slash(str, action=True):
if action:
return os.path.join(str,'')
else:
return str
def replopt(str):
for key in opt_repls:
str = str.replace(key, delete_slash(opt_repls[key]))
return str
def defopt(name, v, isdir=True):
options.__dict__[name] = opt_repls[name.upper()] = add_slash(replopt(options.__dict__.get(name) or v), isdir)
defopt('user_name', USER, isdir=False)
defopt('base', HOME)
defopt('key_dir', 'BASE/keys')
defopt('project_root', 'BASE/projects/PROJECT')
defopt('url_base' , 'http://%s/'%NODENAME)
defopt('html_user_url', 'URL_BASE/PROJECT')
defopt('html_ops_url', 'URL_BASE/PROJECT_ops')
defopt('cgi_url', 'URL_BASE/PROJECT_cgi')
if not isurl(options.url_base):
syntax_error('url_base needs to be an URL')
defopt('html_user_url' , 'URL_BASE/PROJECT')
defopt('html_ops_url' , 'URL_BASE/PROJECT_ops')
defopt('cgi_url' , 'URL_BASE/PROJECT_cgi')
defopt('user_name' , USER, isdir=False)
defopt('base' , HOME)
defopt('key_dir' , 'BASE/keys')
defopt('project_root' , 'BASE/projects/PROJECT')
defopt('db_name' , 'PROJECT', isdir=False)
print "Creating project '%s' (short name '%s'):" %(project_longname, project_shortname)
for k in ['base',
@ -185,6 +200,7 @@ if os.path.exists(options.project_root):
if not options.no_query:
if not query_noyes('Delete %s?'%options.project_root):
raise SystemExit('Aborted')
print "Deleting", options.project_root
rmtree(options.project_root)
else:
raise SystemExit('Project root already exists! Specify --delete_prev_inst --drop_db_first to clobber')
@ -193,34 +209,38 @@ if not options.no_query:
if not query_yesno("Continue?"):
raise SystemExit('Aborted')
app = App(app_name)
app_version = AppVersion(app, exec_names=[app_exe])
options.install_method = 'copy'
init()
project = Project(project_shortname, project_longname,
project_dir = options.project_root,
master_url = options.url_base,
master_url = options.html_user_url,
cgi_url = options.cgi_url,
key_dir = options.key_dir,
apps=[app], app_versions=[app_version],
db_name = options.db_name
)
project.install_project()
project.sched_install('feeder')
project.sched_install('transitioner')
project.sched_install('validate_test')
project.sched_install('assimilator')
project.sched_install('file_deleter')
# project.sched_install('feeder')
# project.sched_install('transitioner')
# project.sched_install('validate_test')
# project.sched_install('assimilator')
# project.sched_install('file_deleter')
print '''Done installing files.
httpd_conf_template_filename = os.path.join(options.project_root,
project_shortname+'.httpd.conf')
You need to manually edit your Apache httpd.conf to add these lines:
proot = delete_slash(options.project_root)
html_user_url = options.html_user_url
html_ops_url = options.html_ops_url
Alias /%(project)s %(proot)s/html_user
Alias /%(project)s_ops %(proot)s/html_ops
ScriptAlias /%(project)s_cgi %(proot)s/cgi-bin
print >>open(httpd_conf_template_filename,'w'), '''
## Settings for BOINC project %(project_longname)s
Alias /%(project_shortname)s %(proot)s/html_user
Alias /%(project_shortname)s_ops %(proot)s/html_ops
ScriptAlias /%(project_shortname)s_cgi %(proot)s/cgi-bin
# Note: projects/*/keys/ should NOT be readable!
@ -243,19 +263,47 @@ You need to manually edit your Apache httpd.conf to add these lines:
Order allow,deny
Allow from all
</Directory>
''' %locals()
Install this in crontab:
print '''Done installing files.
Steps to complete installation:
1. Set permissions for Apache:
cat %(httpd_conf_template_filename)s >> /etc/apache/httpd.conf && apachectl restart
# (path to httpd.conf varies)
2. Add to crontab (as %(USER)s)
(If cron cannot run "start", try using a helper script to set PATH and
PYTHONPATH)
0,5,10,15,20,25,30,35,40,45,50,55 * * * * %(proot)s/bin/start --cron
(You may need to set PATH and/or PYTHONPATH since cron runs with manilla environment)
To start, show status, stop BOINC daemons run:
%(proot)s/bin/start
%(proot)s/bin/status
%(proot)s/bin/stop
'''%{'project':project_shortname, 'proot':os.path.join(options.project_root,'')[:-1]}
Master URL: %(html_user_url)s
Administration URL: %(html_ops_url)s
## TODO: save settings to a file.
Tasks to do:
1. Add platform(s)
%(proot)s/bin/add platform --name=c64 --user_f="Commodore 64"
%(proot)s/bin/add platform --name=i686-pc-linux-gnu --user_f="Linux x86"
2. Add application(s)
%(proot)s/bin/add app --name=SpaghettiAtHome
3. Add core client and application binaries
3a. Place compiled clients in %(proot)s/apps/boinc/ and %(proot)s/apps/APP/
3b. Run %(proot)s/bin/update_versions
4. Generate work : read documentation at http://boinc.berkeley.edu/
'''%locals()

View File

@ -36,11 +36,6 @@ assert(config.app_dir
config.download_url
)
def query_yesno(msg):
'''Query y/n; default Y'''
print msg, "[Y/n]? ",
return not raw_input().lower().startswith('n')
objects_to_commit = []
def xsort(list):
@ -131,7 +126,7 @@ print "Commit %d items:" %len(objects_to_commit)
for object in objects_to_commit:
print " ", object
if not query_yesno("Continue"):
if not tools.query_yesno("Continue"):
raise SystemExit
for object in objects_to_commit:

27
tools/upgrade Executable file
View File

@ -0,0 +1,27 @@
#!/usr/bin/env python
# $Id$
'''
This program upgrades the project (created using make_project) with current
source/build files. Useful if you are a BOINC developer or closely following
BOINC development.
'''
import boinc_path_config
from Boinc import boinc_project_path
from Boinc.setup_project import *
import os
if os.system(os.path.join(boinc_project_path.PROGRAM_PARENT_DIR,'bin/stop')):
raise SystemExit("Couldn't stop BOINC!")
print "Upgrading files... "
options.install_method = 'copy'
init()
install_boinc_files(boinc_project_path.PROGRAM_PARENT_DIR)
print "Upgrading files... done"
print
print "Run `start' to resume BOINC."