depot.py revision 404
409N/A#!/usr/bin/python
290N/A#
290N/A# CDDL HEADER START
290N/A#
290N/A# The contents of this file are subject to the terms of the
290N/A# Common Development and Distribution License (the "License").
290N/A# You may not use this file except in compliance with the License.
290N/A#
290N/A# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
290N/A# or http://www.opensolaris.org/os/licensing.
290N/A# See the License for the specific language governing permissions
290N/A# and limitations under the License.
290N/A#
290N/A# When distributing Covered Code, include this CDDL HEADER in each
290N/A# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
290N/A# If applicable, add the following below this CDDL HEADER, with the
290N/A# fields enclosed by brackets "[]" replaced with your own identifying
290N/A# information: Portions Copyright [yyyy] [name of copyright owner]
290N/A#
290N/A# CDDL HEADER END
290N/A#
849N/A# Copyright 2008 Sun Microsystems, Inc. All rights reserved.
395N/A# Use is subject to license terms.
395N/A#
290N/A
883N/A# pkg.depotd - package repository daemon
454N/A
290N/A# XXX The prototype pkg.depotd combines both the version management server that
448N/A# answers to pkgsend(1) sessions and the HTTP file server that answers to the
290N/A# various GET operations that a pkg(1) client makes. This split is expected to
290N/A# be made more explicit, by constraining the pkg(1) operations such that they
290N/A# can be served as a typical HTTP/HTTPS session. Thus, pkg.depotd will reduce
383N/A# to a special purpose HTTP/HTTPS server explicitly for the version management
290N/A# operations, and must manipulate the various state files--catalogs, in
395N/A# particular--such that the pkg(1) pull client can operately accurately with
290N/A# only a basic HTTP/HTTPS server in place.
395N/A
849N/A# XXX We should support simple "last-modified" operations via HEAD queries.
1099N/A
290N/A# XXX Although we pushed the evaluation of next-version, etc. to the pull
849N/A# client, we should probably provide a query API to do same on the server, for
290N/A# dumb clients (like a notification service).
290N/A
290N/A# The default authority for the depot.
290N/AAUTH_DEFAULT = "opensolaris.org"
383N/A# The default repository path.
290N/AREPO_PATH_DEFAULT = "/var/pkg/repo"
290N/A# The default port to serve data from.
290N/APORT_DEFAULT = 80
290N/A# The minimum number of threads allowed.
290N/ATHREADS_MIN = 1
290N/A# The default number of threads to start.
290N/ATHREADS_DEFAULT = 10
290N/A# The maximum number of threads that can be started.
465N/ATHREADS_MAX = 100
465N/A# The default server socket timeout in seconds. We want this to be longer than
465N/A# the normal default of 10 seconds to accommodate clients with poor quality
801N/A# connections.
465N/ASOCKET_TIMEOUT_DEFAULT = 60
465N/A# Whether modify operations should be allowed.
465N/AREADONLY_DEFAULT = False
1099N/A# Whether the repository catalog should be rebuilt on startup.
465N/AREBUILD_DEFAULT = False
465N/A
465N/Aimport getopt
465N/Aimport os
465N/Aimport sys
465N/A
465N/Atry:
1099N/A import cherrypy
465N/A version = cherrypy.__version__.split('.')
708N/A if map(int, version) < [3, 0, 3]:
708N/A raise ImportError
708N/A elif map(int, version) >= [3, 1, 0]:
708N/A raise ImportError
708N/Aexcept ImportError:
708N/A print """cherrypy 3.0.3 or greater (but less than 3.1.0) is """ \
1099N/A """required to use this program."""
1099N/A sys.exit(2)
708N/A
1391N/Aimport pkg.server.face as face
1391N/Aimport pkg.server.config as config
1391N/Aimport pkg.server.depot as depot
1391N/Aimport pkg.server.repository as repo
1391N/Afrom pkg.misc import port_available
1391N/A
1391N/Adef usage():
1391N/A print """\
1391N/AUsage: /usr/lib/pkg.depotd [--readonly] [--rebuild] [-d repo_dir] [-p port]
1391N/A [-s threads] [-t socket_timeout]
1391N/A
742N/A --readonly Read-only operation; modifying operations disallowed
742N/A --rebuild Re-build the catalog from pkgs in depot
742N/A"""
742N/A sys.exit(2)
742N/A
742N/Aclass OptionError(Exception):
1099N/A """ Option exception. """
742N/A
941N/A def __init__(self, *args):
941N/A Exception.__init__(self, *args)
941N/A
941N/Aif __name__ == "__main__":
941N/A
941N/A port = PORT_DEFAULT
1099N/A threads = THREADS_DEFAULT
941N/A socket_timeout = SOCKET_TIMEOUT_DEFAULT
1191N/A readonly = READONLY_DEFAULT
1191N/A rebuild = REBUILD_DEFAULT
1191N/A
1191N/A if "PKG_REPO" in os.environ:
1191N/A repo_path = os.environ["PKG_REPO"]
1191N/A else:
1191N/A repo_path = REPO_PATH_DEFAULT
1191N/A
290N/A try:
290N/A parsed = set()
290N/A opts, pargs = getopt.getopt(sys.argv[1:], "d:np:s:t:",
395N/A ["readonly", "rebuild"])
395N/A for opt, arg in opts:
290N/A if opt in parsed:
395N/A raise OptionError, "Each option may only be " \
395N/A "specified once."
290N/A else:
395N/A parsed.add(opt)
395N/A
290N/A if opt == "-n":
395N/A sys.exit(0)
395N/A elif opt == "-d":
1302N/A repo_path = arg
1302N/A elif opt == "-p":
1302N/A port = int(arg)
290N/A elif opt == "-s":
448N/A threads = int(arg)
448N/A if threads < THREADS_MIN:
534N/A raise OptionError, \
534N/A "minimum value is %d" % THREADS_MIN
534N/A if threads > THREADS_MAX:
534N/A raise OptionError, \
534N/A "maximum value is %d" % THREADS_MAX
534N/A elif opt == "-t":
534N/A socket_timeout = int(arg)
290N/A elif opt == "--readonly":
290N/A readonly = True
954N/A elif opt == "--rebuild":
954N/A rebuild = True
954N/A except getopt.GetoptError, e:
954N/A print "pkg.depotd: %s" % e.msg
534N/A usage()
1099N/A except OptionError, e:
290N/A print "pkg.depotd: option: %s -- %s" % (opt, e)
1191N/A usage()
1191N/A except (ArithmeticError, ValueError):
1191N/A print "pkg.depotd: illegal option value: %s specified " \
290N/A "for option: %s" % (arg, opt)
290N/A usage()
290N/A
290N/A available, msg = port_available(None, port)
661N/A if not available:
290N/A print "pkg.depotd: unable to bind to the specified port: " \
290N/A " %d. Reason: %s" % (port, msg)
290N/A sys.exit(1)
395N/A
290N/A try:
290N/A face.set_content_root(os.environ["PKG_DEPOT_CONTENT"])
290N/A except KeyError:
1483N/A pass
290N/A
1498N/A scfg = config.SvrConfig(repo_path, AUTH_DEFAULT)
1498N/A
290N/A if rebuild:
395N/A scfg.destroy_catalog()
430N/A
395N/A if readonly:
1231N/A scfg.set_read_only()
1506N/A
395N/A try:
395N/A scfg.init_dirs()
424N/A except EnvironmentError, e:
1024N/A print "pkg.depotd: an error occurred while trying to " \
395N/A "initialize the depot repository directory " \
395N/A "structures:\n%s" % e
395N/A sys.exit(1)
578N/A
1228N/A scfg.acquire_in_flight()
1172N/A scfg.acquire_catalog()
395N/A
661N/A root = cherrypy.Application(repo.Repository(scfg))
1099N/A # We have to override cherrypy's default response_class so that we
661N/A # have access to the write() callable to stream data directly to the
395N/A # client.
849N/A root.wsgiapp.response_class = depot.DepotResponse
290N/A
395N/A cherrypy.config.update({
395N/A "environment": "production",
395N/A "checker.on": True,
395N/A "log.screen": True,
395N/A "server.socket_port": port,
395N/A "server.thread_pool": threads,
395N/A "server.socket_timeout": socket_timeout
395N/A })
395N/A
395N/A conf = {
395N/A "/robots.txt": {
395N/A "tools.staticfile.on": True,
395N/A "tools.staticfile.filename": os.path.join(face.content_root,
290N/A "robots.txt")
290N/A },
395N/A "/static": {
395N/A "tools.staticdir.on": True,
1231N/A "tools.staticdir.root": face.content_root,
1506N/A "tools.staticdir.dir": ""
395N/A }
395N/A }
395N/A
395N/A try:
395N/A cherrypy.quickstart(root, config = conf)
395N/A except:
395N/A print "pkg.depotd: unknown error starting depot, illegal " \
395N/A "option value specified?"
395N/A usage()
395N/A
395N/A