boinc/tools/appmgr

572 lines
21 KiB
Python
Executable File

#! /usr/bin/env python
#
# Written by Gabor Gombas <gombasg@sztaki.hu>
# Licensed under the same terms as the rest of BOINC.
"""
Manage applications and platforms from the command line.
Run "appmgr --help" to list the available commands.
Run "appmgr <command> --help" to get the description of a command.
"""
import time, os, re, string
from optparse import OptionParser
from xml.dom.minidom import parseString
import boinc_path_config
from Boinc import database, db_mid, configxml
# The list of standard platforms
standard_platforms = {
'windows_intelx86': 'Microsoft Windows (98 or later) running on an Intel x86-compatible CPU',
'windows_x86_64': 'Microsoft Windows running on an AMD x86_64 or Intel EM64T CPU',
'i686-pc-linux-gnu': 'Linux running on an Intel x86-compatible CPU',
'x86_64-pc-linux-gnu': 'Linux running on an AMD x86_64 or Intel EM64T CPU',
'powerpc-apple-darwin': 'Mac OS X 10.3 or later running on Motorola PowerPC',
'i686-apple-darwin': 'Mac OS 10.4 or later running on Intel',
'x86_64-apple-darwin': 'Intel 64-bit Mac OS 10.5 or later',
'sparc-sun-solaris2.7': 'Solaris 2.7 running on a SPARC-compatible CPU',
'sparc-sun-solaris': 'Solaris 2.8 or later running on a SPARC-compatible CPU',
'sparc64-sun-solaris': 'Solaris 2.8 or later running on a SPARC 64-bit CPU',
'powerpc64-ps3-linux-gnu': 'Sony Playstation 3 running Linux',
'anonymous': 'anonymous'}
def remove_directory(dir):
"""Remove a directory and all files inside it."""
for file in os.listdir(dir):
os.unlink(os.path.join(dir, file))
os.rmdir(dir)
def remove_app_directory(appver, config):
"""Remove files for an app version under <projectroot>/apps"""
appdir = os.path.join(config.app_dir, appver.app.name)
for path in map(lambda file: os.path.join(appdir, file), os.listdir(appdir)):
match = re.match('^[^.]+_([0-9]+)[.]([0-9]+)_([^.]+?(?:[0-9][0-9.]*[0-9])?)(?:__([^.])+)?(?:[.][a-zA-Z0-9\_\-]*)?$', os.path.basename(path))
if not match:
continue
major, minor, platform_name, plan_class = match.groups()
version_num = int(major) * 100 + int(minor)
if plan_class is None:
plan_class = ''
if platform_name != appver.platform.name:
continue
if appver.version_num != version_num:
continue
if appver.plan_class != plan_class:
continue
if os.path.isdir(path):
remove_directory(path)
else:
os.unlink(path)
# If all versions are gone, remove the parent directory as well
try:
os.rmdir(appdir)
except:
pass
def do_delete_app_version(appver):
"""Delete both the files and the database entry for an app version."""
config = configxml.default_config().config
# Remove files/directories under app_dir
appdir = os.path.join(config.app_dir, appver.app.name)
if os.path.isdir(appdir):
remove_app_directory(appver, config)
# Remove files under download_dir
# appver.xml_doc does not have a parent node, so we have to add one
xml = parseString('<xml>' + appver.xml_doc + '</xml>')
for file in xml.getElementsByTagName('file_info'):
name = ''
for node in file.getElementsByTagName('name')[0].childNodes:
if node.nodeType == node.TEXT_NODE:
name += node.data
if not name:
continue
os.unlink(os.path.join(config.download_dir, name))
tmp = str(appver)
appver.remove()
print "Removed " + tmp
def add_platform(args):
usage = """%prog add_platform <name> <user-friendly name>
Add a new platform definition to the database."""
parser = OptionParser(usage = usage)
options, extra_args = parser.parse_args(args)
if len(extra_args) != 2:
parser.error("Wrong number of arguments")
platform = database.Platform(create_time = time.time(),
name = extra_args[0],
user_friendly_name = extra_args[1],
deprecated = 0)
platform.commit()
print "Added " + str(platform)
def del_platform(args):
usage = """%prog delete_platform <name>
Deletes a platform from the database. If the --force flag is specified, it also
deletes all application versions for this platform. Otherwise, if there is an
application version available for this platform, the command will fail."""
parser = OptionParser(usage = usage)
parser.add_option("-f", "--force",
dest = "force",
action = "store_true",
default = False,
help = "remove any app versions that still exist for this platform")
options, extra_args = parser.parse_args(args)
if len(extra_args) != 1:
parser.error("Wrong number of arguments")
platforms = database.Platforms.find(name = extra_args[0])
if len(platforms) < 1:
raise SystemExit("Unknown platform name")
platform = platforms[0]
# If there are still application versions defined for this platform, then
# either bail out, or if --force was given, blow those application versions
# first
for appver in database.AppVersions.find(platform = platform):
if not options.force:
raise SystemExit("There are still application versions for this "
"platform, bailing out")
do_delete_app_version(appver)
# The id is gone after .remove(), so we have to save the stringified form
tmp = str(platform)
platform.remove()
print "Deleted " + tmp
def update_platform(args):
usage = """%prog update_platform <name> [options]
Update platform data."""
parser = OptionParser(usage = usage)
parser.add_option("--deprecated",
action = "store_const",
const = 1,
help = "mark the platform as deprecated")
parser.add_option("--no-deprecated",
dest = "deprecated",
action = "store_const",
const = 0,
help = "remove the deprecation mark")
parser.add_option("--user_friendly_name",
metavar = "DESC",
help = "set the user-friendly name of the platform")
options, extra_args = parser.parse_args(args)
if len(extra_args) != 1:
parser.error("Wrong number of arguments")
platforms = database.Platforms.find(name = extra_args[0])
if not platforms:
raise SystemExit("Unknown platform name")
platform = platforms[0]
if options.deprecated is not None:
platform.deprecated = options.deprecated
if options.user_friendly_name:
platform.user_friendly_name = options.user_friendly_name
platform.commit()
print "Updated " + str(platform)
def list_platform(args):
usage = """%prog list_platform
List all defined platforms."""
parser = OptionParser(usage = usage)
parser.add_option("--short",
action = "store_true",
help = "show the short names only")
options, extra_args = parser.parse_args(args)
if extra_args:
parser.error("Extra arguments on the command line")
# Ensure consistent ordering
database.Platforms.select_args = 'ORDER BY name'
for platform in database.Platforms.find():
if options.short:
print platform.name
else:
desc = platform.name + ": " + platform.user_friendly_name
if platform.deprecated:
desc += " (Deprecated)"
print desc
def add_standard_platforms(args):
usage = """%prog add_standard_platforms
Add all standard platform definitions to the database. If some of them
already exist they will not be modified."""
parser = OptionParser(usage = usage)
options, extra_args = parser.parse_args(args)
if extra_args:
parser.error("Extra arguments on the command line")
for name in standard_platforms.keys():
if database.Platforms.find(name = name):
continue
platform = database.Platform(create_time = time.time(),
name = name,
user_friendly_name = standard_platforms[name],
deprecated = 0)
platform.commit()
print "Added " + str(platform)
def add_app(args):
usage = """%prog add <name> <user-friendly name> [options]
Register a new application in the database."""
hr_types = { 'no': 0, 'fine': 1, 'coarse': 2}
parser = OptionParser(usage = usage)
parser.add_option("--hr",
choices = hr_types.keys(),
default = "no",
metavar = "(no|fine|coarse)",
help = "set the homogeneous redundancy type")
parser.add_option("--beta",
action = "store_const",
default = 0,
const = 1,
help = "the application is still in beta testing, and only "
"users participating in the test program will run it")
parser.add_option("--weight",
type = "float",
default = 1,
metavar = "NUM",
help = "set the weight of the application when application "
"interleaving is enabled in the feeder")
parser.add_option("--target_nresults",
type = "int",
default = 0,
metavar = "NUM",
help = "default number of replicas for adaptive replication")
options, extra_args = parser.parse_args(args)
if len(extra_args) != 2:
parser.error("Wrong number of arguments")
app = database.App(create_time = time.time(),
name = extra_args[0],
min_version = 0,
deprecated = 0,
user_friendly_name = extra_args[1],
homogeneous_redundancy = hr_types[options.hr],
weight = options.weight,
beta = options.beta,
target_nresults = options.target_nresults)
try:
app.commit()
except Exception, e:
print "Failed to add application"
return
print "Added " + str(app)
def del_app(args):
usage = """%prog delete <name> [options]
Delete application versions. This command deletes the matching database
entries and all corresponding files under <projectroot>/apps and
<projectroot>/download. If more than one of the --version, --platform and
--plan_class options are specified, they are AND'ed together. If no
version/platform/plan class options are given, all versions of the application
are deleted and the application is removed from the 'app' table as well."""
parser = OptionParser(usage = usage)
parser.add_option("--version",
type = "float",
metavar = "VER",
help = "delete versions with the given version number")
parser.add_option("--platform",
metavar = "NAME",
help = "delete versions for the given platform")
parser.add_option("--plan_class",
metavar = "NAME",
help = "delete versions for the given plan class")
options, extra_args = parser.parse_args(args)
if len(extra_args) != 1:
parser.error("Wrong number of arguments")
apps = database.Apps.find(name = extra_args[0])
if not apps:
raise SystemExit("No such application")
app = apps[0]
kwargs = { 'app': app }
if options.version:
kwargs['version_num'] = int(options.version * 100)
if options.platform:
platforms = database.Platforms.find(name = options.platform)
if not platforms:
raise SystemExit("Unknown platform name")
kwargs['platform'] = platforms[0]
if options.plan_class:
kwargs['plan_class'] = options.plan_class
for appver in database.AppVersions.find(**kwargs):
do_delete_app_version(appver)
# If the user requested to remove all versions, then remove the entry
# from the 'app' table as well
if not options.version and not options.platform and not options.plan_class:
tmp = str(app)
app.remove()
print "Removed " + tmp
def update_app(args):
usage = """%prog update <name> [options]
Update the properties of the given application."""
hr_types = { 'no': 0, 'fine': 1, 'coarse': 2}
parser = OptionParser(usage = usage)
parser.add_option("--hr",
choices = hr_types.keys(),
default = "no",
metavar = "(no|fine|coarse)",
help = "set the homogeneous redundancy type")
parser.add_option("--beta",
action = "store_const",
const = 1,
help = "the application is still in beta testing, and only "
"users participating in the test program will run it")
parser.add_option("--no-beta",
action = "store_const",
dest = "beta",
const = 0,
help = "the application is no more in beta testing")
parser.add_option("--weight",
type = "float",
metavar = "NUM",
help = "set the weight of the application when application "
"interleaving is enabled in the feeder")
parser.add_option("--target_nresults",
type = "int",
metavar = "NUM",
help = "default number of replicas for adaptive replication")
parser.add_option("--user_friendly_name",
metavar = "DESC",
help = "set the user-friendly description of the application")
parser.add_option("--min_version",
type = "float",
metavar = "VER",
help = "set the minimum app version clients allowed to use")
parser.add_option("--deprecated",
action = "store_const",
const = 1,
help = "mark the application as deprecated")
parser.add_option("--no-deprecated",
dest = "deprecated",
action = "store_const",
const = 0,
help = "remove the deprecation mark")
options, extra_args = parser.parse_args(args)
if len(extra_args) != 1:
parser.error("Wrong number of arguments")
apps = database.Apps.find(name = extra_args[0])
if not apps:
raise SystemExit("No such application")
app = apps[0]
if options.hr is not None:
app.homogeneous_redundancy = hr_types[options.hr]
if options.beta is not None:
app.beta = options.beta
if options.weight is not None:
app.weight = options.weight
if options.target_nresults is not None:
app.target_nresults = options.target_nresults
if options.user_friendly_name:
app.user_friendly_name = options.user_friendly_name
if options.min_version is not None:
app.min_version = int(options.min_version * 100)
if options.deprecated is not None:
app.deprecated = options.deprecated
app.commit()
print "Updated " + str(app)
def list_app(args):
usage = """%prog list
List all applications and application versions."""
parser = OptionParser(usage = usage)
parser.add_option("--no-versions",
action = "store_true",
default = False,
help = "do not list application versions")
options, extra_args = parser.parse_args(args)
if extra_args:
parser.error("Extra arguments on the command line")
# Ensure consistent ordering for the output
database.Apps.select_args = 'ORDER BY name'
# It would be nice to order by platform name...
database.AppVersions.select_args = 'ORDER BY version_num, platformid, plan_class'
hr_classes = {0: 'No', 1: 'Fine-grained', 2: 'Coarse'}
for app in database.Apps.find():
title = app.name + ": " + app.user_friendly_name
print title
print "-" * len(title)
props = []
if app.deprecated:
props.append("Deprecated.")
if app.beta:
props.append("Beta.")
if app.min_version:
props.append("Min. version: %.2f." % (float(app.min_version) / 100))
props.append(hr_classes[app.homogeneous_redundancy] +
' homogeneous redundancy.')
props.append("Weight %g." % app.weight)
if app.target_nresults:
props.append(str(app.target_nresults) + " adaptive replicas.")
print string.join(props, ' ')
if options.no_versions:
print
continue
print "Versions:\n"
for appver in database.AppVersions.find(app = app):
desc = "\t%5.2f" % (float(appver.version_num) / 100)
desc += " " + appver.platform.name
props = []
if appver.plan_class:
props.append("Plan: " + appver.plan_class + ".")
if appver.deprecated:
props.append("Deprecated.")
if appver.min_core_version:
props.append("Min. core version: %g." % \
(float(appver.min_core_version) / 100))
if appver.max_core_version:
props.append("Max. core version: %g." % \
(float(appver.max_core_version) / 100))
if props:
desc += " (" + string.join(props, ' ') + ')'
print desc
print
def update_appver(args):
usage = """%prog update_appver <name> [options]
Update the properties of the given application version(s). If none
of the --version, --platform, --plan_class options are specified,
then all versions for the given application are updated."""
parser = OptionParser(usage = usage)
parser.add_option("--version",
type = "float",
metavar = "VER",
help = "update just the given version number")
parser.add_option("--platform",
metavar = "NAME",
help = "update just the given platform")
parser.add_option("--plan_class",
metavar = "NAME",
help = "update just the given plan class")
parser.add_option("--min_core_version",
type = "float",
metavar = "VER",
help = "set the minimum usable core client version")
parser.add_option("--max_core_version",
type = "float",
metavar = "VER",
help = "set the maximum usable core client version")
parser.add_option("--deprecated",
action = "store_const",
const = 1,
help = "mark this version as deprecated")
parser.add_option("--no-deprecated",
dest = "deprecated",
action = "store_const",
const = 0,
help = "remove the deprecation mark")
options, extra_args = parser.parse_args(args)
if len(extra_args) != 1:
parser.error("Wrong number of arguments")
apps = database.Apps.find(name = extra_args[0])
if not apps:
raise SystemExit("No such application")
app = apps[0]
kwargs = { 'app': app }
if options.version:
kwargs['version_num'] = int(options.version * 100)
if options.platform:
platforms = database.Platforms.find(name = options.platform)
if not platforms:
raise SystemExit("Unknown platform name")
kwargs['platform'] = platforms[0]
if options.plan_class:
kwargs['plan_class'] = options.plan_class
for appver in database.AppVersions.find(**kwargs):
if options.deprecated is not None:
appver.deprecated = options.deprecated
if options.min_core_version is not None:
appver.min_core_version = int(options.min_core_version * 100)
if options.max_core_version is not None:
appver.max_core_version = int(options.max_core_version * 100)
appver.commit()
print "Updated " + str(appver)
command_table = {
'add': add_app,
'delete': del_app,
'update': update_app,
'list': list_app,
'update_appver': update_appver,
'add_platform': add_platform,
'delete_platform': del_platform,
'update_platform': update_platform,
'list_platform': list_platform,
'add_standard_platforms': add_standard_platforms}
usage = """%%prog [global options] <command> [command arguments]
Available commands:
%s
Use "%%prog <command> --help" to get a description of the command.""" % \
string.join(map(lambda x: "\t" + x, sorted(command_table.keys())), "\n")
parser = OptionParser(usage=usage)
parser.disable_interspersed_args()
global_options, args = parser.parse_args()
if not args:
parser.error("Command is not provided")
if not args[0] in command_table:
parser.error("Unknown command " + args[0])
database.connect()
command_table[args[0]](args[1:])