6957 mDNS support for depot
11178 remove defunct policies from imageconfig
15371 repository property defaults opensolaris.org-specific
15760 Client should detect mirrors advertised by mDNS
15973 Depot should not have default for repo_dir
--- a/.hgignore Tue May 18 11:11:28 2010 +0100
+++ b/.hgignore Tue May 18 10:51:50 2010 -0700
@@ -22,16 +22,9 @@
^src/gui/data/packagemanager.desktop$
^src/gui/help/.*/package-manager.xml$
^src/gui/data/packagemanager-info.xml$
-^src/man/packagemanager.1$
-^src/man/pkg.1$
-^src/man/pkg.5$
-^src/man/pkg.depotd.1m$
-^src/man/pkgdepend.1$
-^src/man/pkgdiff.1$
-^src/man/pkgmogrify.1$
-^src/man/pkgrecv.1$
-^src/man/pkgsend.1$
-^src/man/pm-updatemanager.1$
+^src/man/.*\.1$
+^src/man/.*\.1m$
+^src/man/.*\.5$
^src/pkg$
^src/pkg.depotd$
^src/pkgdefs/pkgproto$
--- a/src/depot.py Tue May 18 11:11:28 2010 +0100
+++ b/src/depot.py Tue May 18 10:51:50 2010 -0700
@@ -19,8 +19,7 @@
#
# CDDL HEADER END
#
-# Copyright 2010 Sun Microsystems, Inc. All rights reserved.
-# Use is subject to license terms.
+# Copyright (c) 2007, 2010 Oracle and/or its affiliates. All rights reserved.
#
# pkg.depotd - package repository daemon
@@ -39,8 +38,6 @@
# client, we should probably provide a query API to do same on the server, for
# dumb clients (like a notification service).
-# The default repository path.
-REPO_PATH_DEFAULT = "/var/pkg/repo"
# The default path for static and other web content.
CONTENT_PATH_DEFAULT = "/usr/share/lib/pkg"
# cherrypy has a max_request_body_size parameter that determines whether the
@@ -71,6 +68,8 @@
REINDEX_DEFAULT = False
# Not in mirror mode by default
MIRROR_DEFAULT = False
+# Not in link-local mirror mode my default
+LL_MIRROR_DEFAULT = False
import getopt
import gettext
@@ -139,8 +138,8 @@
Usage: /usr/lib/pkg.depotd [-d repo_dir] [-p port] [-s threads]
[-t socket_timeout] [--cfg-file] [--content-root]
[--disable-ops op[/1][,...]] [--debug feature_list]
- [--log-access dest] [--log-errors dest] [--mirror] [--nasty]
- [--set-property <section.property>=<value>]
+ [--file-root dir] [--log-access dest] [--log-errors dest]
+ [--mirror] [--nasty] [--set-property <section.property>=<value>]
[--proxy-base url] [--readonly] [--rebuild] [--ssl-cert-file]
[--ssl-dialog] [--ssl-key-file] [--sort-file-max-size size]
[--writable-root dir]
@@ -165,6 +164,9 @@
--exit-ready Perform startup processing (including rebuilding
catalog or indices, if requested) and exit when
ready to start serving packages.
+ --file-root The path to the root of the file content for a given
+ repository. This is used to override the default,
+ <repo_root>/file.
--log-access The destination for any access related information
logged by the depot process. Possible values are:
stderr, stdout, none, or an absolute pathname. The
@@ -213,7 +215,6 @@
access. Used with --readonly to allow server to
create needed files, such as search indices, without
needing write access to the package information.
-
Options:
--help or -?
@@ -247,9 +248,12 @@
reindex = REINDEX_DEFAULT
proxy_base = None
mirror = MIRROR_DEFAULT
+ ll_mirror = LL_MIRROR_DEFAULT
+ file_root = None
nasty = False
nasty_value = 0
repo_config_file = None
+ repo_path = None
sort_file_max_size = indexer.SORT_FILE_MAX_SIZE
ssl_cert_file = None
ssl_key_file = None
@@ -260,8 +264,6 @@
if "PKG_REPO" in os.environ:
repo_path = os.environ["PKG_REPO"]
- else:
- repo_path = REPO_PATH_DEFAULT
try:
content_root = os.environ["PKG_DEPOT_CONTENT"]
@@ -289,10 +291,11 @@
repo_props = {}
try:
long_opts = ["add-content", "cfg-file=", "content-root=",
- "debug=", "disable-ops=", "exit-ready", "help", "mirror",
- "nasty=", "set-property=", "proxy-base=", "readonly",
- "rebuild", "refresh-index", "ssl-cert-file=", "ssl-dialog=",
- "ssl-key-file=", "sort-file-max-size=", "writable-root="]
+ "debug=", "disable-ops=", "exit-ready", "file-root=",
+ "help", "llmirror", "mirror", "nasty=", "proxy-base=",
+ "readonly", "rebuild", "refresh-index", "set-property=",
+ "ssl-cert-file=", "ssl-dialog=", "ssl-key-file=",
+ "sort-file-max-size=", "writable-root="]
for opt in log_opts:
long_opts.append("%s=" % opt.lstrip('--'))
@@ -327,6 +330,11 @@
raise OptionError, "You must specify " \
"a directory path."
content_root = arg
+ elif opt == "--file-root":
+ if arg == "":
+ raise OptionError, "You must specify " \
+ "a directory path."
+ file_root = arg
elif opt == "--debug":
if arg is None or arg == "":
raise OptionError, \
@@ -378,6 +386,10 @@
show_usage = True
elif opt == "--mirror":
mirror = True
+ elif opt == "--llmirror":
+ mirror = True
+ ll_mirror = True
+ readonly = True
elif opt == "--nasty":
value_err = None
try:
@@ -536,6 +548,10 @@
usage("--readonly can only be used with --refresh-index if "
"--writable-root is used")
+ if not repo_path and not file_root:
+ usage("At least one of PKG_REPO, -d, or --file-root"
+ " must be provided")
+
if (ssl_cert_file and not ssl_key_file) or (ssl_key_file and not
ssl_cert_file):
usage("The --ssl-cert-file and --ssl-key-file options must "
@@ -678,10 +694,11 @@
fork_allowed = not reindex and not exit_ready
try:
repo = sr.Repository(auto_create=not readonly,
- cfgpathname=repo_config_file, fork_allowed=fork_allowed,
- log_obj=cherrypy, mirror=mirror, properties=repo_props,
- read_only=readonly, refresh_index=not add_content,
- repo_root=repo_path, sort_file_max_size=sort_file_max_size,
+ cfgpathname=repo_config_file, file_root=file_root,
+ fork_allowed=fork_allowed, log_obj=cherrypy,
+ mirror=mirror, properties=repo_props, read_only=readonly,
+ refresh_index=not add_content, repo_root=repo_path,
+ sort_file_max_size=sort_file_max_size,
writable_root=writable_root)
except (RuntimeError, sr.RepositoryError), _e:
emsg("pkg.depotd: %s" % _e)
@@ -779,6 +796,9 @@
for entry in proxy_conf:
conf["/"][entry] = proxy_conf[entry]
+ if ll_mirror:
+ ds.DNSSD_Plugin(cherrypy.engine, conf, gconf).subscribe()
+
try:
root = cherrypy.Application(depot)
cherrypy.quickstart(root, config=conf)
--- a/src/man/pkg.1.txt Tue May 18 11:11:28 2010 +0100
+++ b/src/man/pkg.1.txt Tue May 18 10:51:50 2010 -0700
@@ -681,6 +681,43 @@
string is not guaranteed to be comparable in any fashion between
versions.
+IMAGE PROPERTIES
+ The following properties are part of the image and may be set using
+ the set-property subcommand. The values of these properties are
+ viewable with the property subcommand.
+
+ flush-content-cache-on-success
+ (boolean) If this is set to True, the package client will
+ remove the files in its content-cache when install or
+ image-update operations complete. For image-update
+ operations, the content is removed only from the source BE.
+ When a packaging operation next occurs in the destination BE,
+ it will flush its content cache, provided this option has not
+ been changed.
+
+ This property may be used to keep the content-cache small on
+ systems with limited disk space, but it may cause operations
+ to take longer to complete.
+
+ Default value: False
+
+ mirror-discovery
+ (boolean) Mirror-discovery tells the client to discover
+ link-local content mirrors using mDNS and DNS-SD. If this is
+ set to True, the client will attempt to download package content
+ from mirrors it dynamically discovers. To run a mirror that
+ advertises its content via mDNS, see pkg.mdnsd(1m).
+
+ Default value: False
+
+ send-uuid
+ (boolean) Send the image's Universally Unique Identifier
+ (UUID) when performing network operations. Although users may
+ disable this option, some network repositories may refuse to talk
+ to clients that do not supply a UUID.
+
+ Default value: True
+
EXAMPLES
Example 1: Create a new, full image, with publisher example.com,
stored at /aux0/example_root.
@@ -859,7 +896,7 @@
|_____________________________|_____________________________|
SEE ALSO
- pkgsend(1), pkg.depotd(1M), glob(3C), attributes(5), pkg(5)
+ pkgsend(1), pkg.depotd(1M), pkg.mdnsd(1M), glob(3C), attributes(5), pkg(5)
NOTES
The image packaging system is an under-development feature.
--- a/src/man/pkg.depotd.1m.txt Tue May 18 11:11:28 2010 +0100
+++ b/src/man/pkg.depotd.1m.txt Tue May 18 10:51:50 2010 -0700
@@ -48,6 +48,10 @@
Logs the headers of every request to
the error log.
+ pkg/file_root (astring) The root of the file content
+ for this repository. If undefined, this
+ defaults to <repo_root>/file.
+
pkg/inst_root (astring) The file system path at which
the instance should find its repository
data. The default value is
@@ -148,6 +152,26 @@
value is read-authorization
protected.
+ The pkg depot is also able to act as a mirror server for local
+ client images from pkg(5). This allows clients that share a subnet
+ on a LAN to mirror their file caches. Clients may download files
+ from one another, thereby reducing load on the package depot
+ server. This functionality is available as an alternate depot
+ service configured by smf(5). It uses mDNS and dns-sd for service
+ discovery.
+
+ The mDNS mirror is generally configured via the smf(5) properties
+ associated with its service. The following properties are recognized:
+
+ pkg/file_root (astring) The file system path at which
+ the instance should find its file
+ content. The default value is
+ /var/pkg/download.
+
+ pkg/port (count) The port number on which the
+ instance should listen for incoming
+ package requests. The default value is
+
OPTIONS
The following options alter the default behavior, if present, and
will override the settings from the service instance when managed
@@ -189,6 +213,9 @@
sure search indices are built, new content
cataloged, etc.
+ --file-root path Overrides pkg/file_root with value given
+ by path.
+
--log-access dest Overrides pkg/log_access with the value
given by dest.
@@ -249,6 +276,17 @@
# svcadm refresh application/pkg/server
# svcadm restart application/pkg/server
+ Example 3: Enabling the mirror.
+
+ # svcadm enable application/pkg/dynamic-mirror
+
+ Example 4: Changing the listening port of the server.
+
+ # svccfg -s application/pkg/server setprop pkg/port = 20000
+ # svcadm refresh application/pkg/server
+ # svcadm restart application/pkg/server
+
+
ENVIRONMENT VARIABLES
PKG_DEPOT_CONTENT Specifies the directory containing static
content served by the depot. The files
@@ -379,7 +417,7 @@
|_____________________________|_____________________________|
SEE ALSO
- pkg(1), pkgsend(1), syslogd(1M), attributes(5), smf(5)
+ dns-sd(1), mdnsd(1), pkg(1), pkgsend(1), syslogd(1M), attributes(5), smf(5)
NOTES
The image packaging system is an under-development feature.
@@ -394,9 +432,14 @@
svc:/application/pkg/server
- Because the depot server expects to be run by an smf(5) restarter,
- it does not daemonize. Error messages are generally sent to
- standard output, or to the syslogd(1M) system.
+ The mDNS mirror service is managed by the service management
+ facility, smf(5), under the service identifier:
+
+ svc:/application/pkg/dynamic-mirror
+
+ Because the depot server and mirror expect to be run by an smf(5)
+ restarter, they do not daemonize. Error messages are generally
+ sent to standard output, or to the syslogd(1M) system.
When publishing individual package files that are greater than 128MB
in size, filesystem-based publication must be used due to publication
--- a/src/modules/client/imageconfig.py Tue May 18 11:11:28 2010 +0100
+++ b/src/modules/client/imageconfig.py Tue May 18 10:51:50 2010 -0700
@@ -20,8 +20,9 @@
# CDDL HEADER END
#
-# Copyright 2009 Sun Microsystems, Inc. All rights reserved.
-# Use is subject to license terms.
+#
+# Copyright (c) 2007, 2010 Oracle and/or its affiliates. All rights reserved.
+#
import ConfigParser
import errno
@@ -44,14 +45,12 @@
# pkg(5) and their default values. Calls to the ImageConfig.get_policy method
# should use the constants defined here.
-PURSUE_LATEST = "pursue-latest"
-DISPLAY_COPYRIGHTS = "display-copyrights"
FLUSH_CONTENT_CACHE = "flush-content-cache-on-success"
+MIRROR_DISCOVERY = "mirror-discovery"
SEND_UUID = "send-uuid"
default_policies = {
- PURSUE_LATEST: True,
- DISPLAY_COPYRIGHTS: True,
FLUSH_CONTENT_CACHE: False,
+ MIRROR_DISCOVERY: False,
SEND_UUID: True
}
--- a/src/modules/client/transport/exception.py Tue May 18 11:11:28 2010 +0100
+++ b/src/modules/client/transport/exception.py Tue May 18 10:51:50 2010 -0700
@@ -340,3 +340,13 @@
if r != 0:
return r
return cmp(self.count, other.count)
+
+class mDNSException(TransportException):
+ """Used when mDNS operations fail."""
+
+ def __init__(self, errstr):
+ TransportException.__init__(self)
+ self.err = errstr
+
+ def __str__(self):
+ return self.err
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/modules/client/transport/mdetect.py Tue May 18 10:51:50 2010 -0700
@@ -0,0 +1,160 @@
+#!/usr/bin/python
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright (c) 2010 Oracle and/or its affiliates. All rights reserved.
+#
+
+import random
+
+import pkg.misc as misc
+import pkg.client.publisher as pub
+import pkg.client.transport.exception as tx
+
+try:
+ import pybonjour
+except (OSError, ImportError):
+ pass
+else:
+ import select
+
+
+class MirrorDetector(object):
+ """This class uses mDNS and DNS-SD to find link-local content
+ mirrors that may be present on the client's subnet."""
+
+ def __init__(self):
+ self._mirrors = []
+ self.__timeout = 1
+ self.__service = "_pkg5._tcp"
+
+ def __contains__(self, key):
+ return key in self._mirrors
+
+ def __getitem__(self, pos):
+ return self._mirrors[pos]
+
+ def __iter__(self):
+ """Each time iterator is invoked, randomly select up to
+ five mirrors from the list of available mirrors."""
+
+ listlen = len(self._mirrors)
+ iterlst = random.sample(xrange(listlen), min(listlen, 5))
+
+ for v in iterlst:
+ yield self._mirrors[v]
+
+ def locate(self):
+ """When invoked, this populates the MirrorDetector object with
+ URLs that name dynamically discovered content mirrors."""
+
+ # Clear the list of mirrors. It will be repopulated later.
+ self._mirrors = []
+
+ if not "pybonjour" in globals():
+ return
+
+ timedout = False
+ tval = self.__timeout
+
+ def browse_cb(sd_hdl, flags, interface_idx, error_code,
+ service_name, regtype, reply_domain):
+
+ if error_code != pybonjour.kDNSServiceErr_NoError:
+ return
+
+ if not (flags & pybonjour.kDNSServiceFlagsAdd):
+ return
+
+ self._resolve_server(interface_idx, error_code,
+ service_name, regtype, reply_domain)
+
+ try:
+ sd_hdl = pybonjour.DNSServiceBrowse(
+ regtype=self.__service, callBack=browse_cb)
+ except pybonjour.BonjourError, e:
+ errstr = "mDNS Service Browse Failed: %s\n" % e[0][1]
+ raise tx.mDNSException(errstr)
+
+ try:
+ while not timedout:
+ avail = select.select([sd_hdl], [], [], tval)
+ if sd_hdl in avail[0]:
+ pybonjour.DNSServiceProcessResult(
+ sd_hdl)
+ tval = 0
+ else:
+ timedout = True
+ except select.error, e:
+ errstr = "Select failed: %s\n" % e[1]
+ raise tx.mDNSException(errstr)
+ except pybonjour.BonjourError, e:
+ errstr = "mDNS Process Result failed: %s\n" % e[0][1]
+ raise tx.mDNSException(errstr)
+ finally:
+ sd_hdl.close()
+
+ def _resolve_server(self, if_idx, ec, service_name, regtype,
+ reply_domain):
+ """Invoked to resolve mDNS information about a service that
+ was discovered by a Browse call."""
+
+ timedout = False
+ tval = self.__timeout
+
+ def resolve_cb(sd_hdl, flags, interface_idx, error_code,
+ full_name, host_target, port, txt_record):
+
+ if error_code != pybonjour.kDNSServiceErr_NoError:
+ return
+
+ tr = pybonjour.TXTRecord.parse(txt_record)
+ if "url" in tr:
+ url = tr["url"]
+ if not misc.valid_pub_url(url):
+ return
+ self._mirrors.append(pub.RepositoryURI(url))
+
+ try:
+ sd_hdl = pybonjour.DNSServiceResolve(0, if_idx,
+ service_name, regtype, reply_domain, resolve_cb)
+ except pybonjour.BonjourError, e:
+ errstr = "mDNS Service Resolve Failed: %s\n" % e[0][1]
+ raise tx.mDNSException(errstr)
+
+ try:
+ while not timedout:
+ avail = select.select([sd_hdl], [], [], tval)
+ if sd_hdl in avail[0]:
+ pybonjour.DNSServiceProcessResult(
+ sd_hdl)
+ tval = 0
+ else:
+ timedout = True
+ except select.error, e:
+ errstr = "Select failed; %s\n" % e[1]
+ raise tx.mDNSException(errstr)
+ except pybonjour.BonjourError, e:
+ errstr = "mDNS Process Result Failed: %s\n" % e[0][1]
+ raise tx.mDNSException(errstr)
+ finally:
+ sd_hdl.close()
--- a/src/modules/client/transport/transport.py Tue May 18 11:11:28 2010 +0100
+++ b/src/modules/client/transport/transport.py Tue May 18 10:51:50 2010 -0700
@@ -39,6 +39,7 @@
import pkg.client.publisher as publisher
import pkg.client.transport.engine as engine
import pkg.client.transport.exception as tx
+import pkg.client.transport.mdetect as mdetect
import pkg.client.transport.repo as trepo
import pkg.client.transport.stats as tstats
import pkg.file_layout.file_manager as fm
@@ -70,6 +71,7 @@
self.__cadir = None
self.__portal_test_executed = False
self.__repo_cache = None
+ self.__dynamic_mirrors = []
self.__lock = nrlock.NRLock()
self._caches = None
self.stats = tstats.RepoChooser()
@@ -84,6 +86,16 @@
self.__repo_cache = trepo.RepoCache(self.__engine)
+ cc = self.__img.cfg_cache
+ if cc and cc.get_policy(imageconfig.MIRROR_DISCOVERY):
+ self.__dynamic_mirrors = mdetect.MirrorDetector()
+ try:
+ self.__dynamic_mirrors.locate()
+ except tx.mDNSException:
+ # Not fatal. Suppress.
+ pass
+
+
def _reset_caches(self):
# For now, transport write caches all publisher data in one
# place regardless of publisher source.
@@ -135,6 +147,12 @@
self.__engine.reset()
self.__repo_cache.clear_cache()
self._reset_caches()
+ if self.__dynamic_mirrors:
+ try:
+ self.__dynamic_mirrors.locate()
+ except tx.mDNSException:
+ # Not fatal. Suppress.
+ pass
finally:
self.__lock.release()
@@ -154,6 +172,7 @@
self.__repo_cache.clear_cache()
self.__repo_cache = None
self._caches = None
+ self.__dynamic_mirrors = []
finally:
self.__lock.release()
@@ -1568,6 +1587,7 @@
repo = pub.selected_repository
repolist = repo.mirrors[:]
repolist.extend(repo.origins)
+ repolist.extend(self.__dynamic_mirrors)
rslist = self.stats.get_repostats(repolist,
repo.origins)
for rs, ruri in rslist:
--- a/src/modules/depotcontroller.py Tue May 18 11:11:28 2010 +0100
+++ b/src/modules/depotcontroller.py Tue May 18 10:51:50 2010 -0700
@@ -57,6 +57,7 @@
self.__dir = None
self.__disable_ops = None
self.__exit_ready = False
+ self.__file_root = None
self.__logpath = "/tmp/depot.log"
self.__mirror = False
self.__output = None
@@ -118,6 +119,12 @@
def get_property(self, section, prop):
return self.__props.get(section, {}).get(prop)
+ def set_file_root(self, f_root):
+ self.__file_root = f_root
+
+ def get_file_root(self):
+ return self.__file_root
+
def set_repodir(self, repodir):
self.__dir = repodir
@@ -260,6 +267,8 @@
if self.__dir != None:
args.append("-d")
args.append(self.__dir)
+ if self.__file_root != None:
+ args.append("--file-root=%s" % self.__file_root)
if self.__readonly:
args.append("--readonly")
if self.__rebuild:
--- a/src/modules/server/depot.py Tue May 18 11:11:28 2010 +0100
+++ b/src/modules/server/depot.py Tue May 18 10:51:50 2010 -0700
@@ -27,6 +27,14 @@
import cherrypy
from cherrypy.lib.static import serve_file
from email.utils import formatdate
+from cherrypy.process.plugins import SimplePlugin
+
+try:
+ import pybonjour
+except (OSError, ImportError):
+ pass
+else:
+ import select
import cStringIO
import errno
@@ -39,6 +47,7 @@
import socket
import tarfile
import time
+import urlparse
# Without the below statements, tarfile will trigger calls to getpwuid and
# getgrgid for every file downloaded. This in turn leads to nscd usage which
# limits the throughput of the depot process. Setting these attributes to
@@ -132,8 +141,10 @@
# Store any possible configuration changes.
repo.write_config()
- if repo.mirror:
- self.ops_list = self.REPO_OPS_MIRROR
+ if repo.mirror or not repo.repo_root:
+ self.ops_list = self.REPO_OPS_MIRROR[:]
+ if not repo.cfg.get_property("publisher", "prefix"):
+ self.ops_list.remove("publisher")
elif repo.read_only:
self.ops_list = self.REPO_OPS_READONLY
else:
@@ -1476,3 +1487,94 @@
response.body = nfile.read(filesz)
return response.body
+
+class DNSSD_Plugin(SimplePlugin):
+ """Allow a depot to configure DNS-SD through mDNS."""
+
+ def __init__(self, bus, scfg, gconf):
+ """Bus is cherrypy engine, scfg is SvrConfig, and gconf
+ is dictionary containing the CherryPy configuration."""
+
+ SimplePlugin.__init__(self, bus)
+
+ if "pybonjour" not in globals():
+ self.start = lambda: None
+ self.exit = lambda: None
+ return
+
+ self.name = "pkg(5) mirror on %s" % socket.gethostname()
+ self.wanted_name = self.name
+ self.regtype = "_pkg5._tcp"
+ self.port = gconf["server.socket_port"]
+ self.sd_hdl = None
+ self.reg_ok = False
+
+ if gconf["server.ssl_certificate"] and \
+ gconf["server.ssl_private_key"]:
+ proto = "https"
+ else:
+ proto = "http"
+
+ netloc = "%s:%s" % (socket.getfqdn(), self.port)
+ self.url = urlparse.urlunsplit((proto, netloc, '', '', ''))
+
+ def reg_cb(self, sd_hdl, flags, error_code, name, regtype, domain):
+ """Callback invoked by service register function. Arguments
+ are determined by the pybonjour framework, and must not
+ be changed."""
+
+ if error_code != pybonjour.kDNSServiceErr_NoError:
+ self.bus.log("Error in DNS-SD registration: %s" %
+ pybonjour.BonjourError(error_code))
+
+ self.reg_ok = True
+ self.name = name
+ if self.name != self.wanted_name:
+ self.bus.log("DNS-SD service name changed to: %s" %
+ self.name)
+
+ def start(self):
+ self.bus.log("Starting DNS-SD registration.")
+
+ txt_r = pybonjour.TXTRecord()
+ txt_r["url"] = self.url
+ txt_r["type"] = "mirror"
+
+ to_val = 10
+ timedout = False
+
+ try:
+ self.sd_hdl = pybonjour.DNSServiceRegister(
+ name=self.name, regtype=self.regtype,
+ port=self.port, txtRecord=txt_r,
+ callBack=self.reg_cb)
+ except pybonjour.BonjourError, e:
+ self.bus.log("DNS-SD service registration failed: %s" %
+ e)
+ return
+
+ try:
+ while not timedout:
+ avail = select.select([self.sd_hdl], [], [],
+ to_val)
+ if self.sd_hdl in avail[0]:
+ pybonjour.DNSServiceProcessResult(
+ self.sd_hdl)
+ to_val = 0
+ else:
+ timedout = True
+ except pybonjour.BonjourError, e:
+ self.bus.log("DNS-SD service registration failed: %s" %
+ e)
+
+ if not self.reg_ok:
+ self.bus.log("DNS-SD registration timed out.")
+ return
+ self.bus.log("Finished DNS-SD registration.")
+
+ def exit(self):
+ self.bus.log("DNS-SD plugin exited")
+ if self.sd_hdl:
+ self.sd_hdl.close()
+ self.sd_hdl = None
+ self.bus.log("Service unregistration for DNS-SD complete.")
--- a/src/modules/server/feed.py Tue May 18 11:11:28 2010 +0100
+++ b/src/modules/server/feed.py Tue May 18 10:51:50 2010 -0700
@@ -73,7 +73,8 @@
for feeds to work correctly.
"""
- if not (repo.read_only and not repo.writable_root):
+ if repo.feed_cache_root and not \
+ (repo.read_only and not repo.writable_root):
# RSS/Atom feeds require a unique identifier, so
# generate one if isn't defined already. This
# needs to be a persistent value, so we only
--- a/src/modules/server/repository.py Tue May 18 11:11:28 2010 +0100
+++ b/src/modules/server/repository.py Tue May 18 10:51:50 2010 -0700
@@ -144,6 +144,13 @@
return _("Search functionality is temporarily unavailable.")
+class RepositoryUnsupportedOperationError(RepositoryError):
+ """Raised when the repository is unable to support an operation,
+ based upon its current configuration."""
+
+ def __str__(self):
+ return("Operation not supported for this configuration.")
+
class RepositoryUpgradeError(RepositoryError):
"""Used to indicate that the specified repository root cannot be used
as the catalog or format of it is an older version that needs to be
@@ -167,7 +174,7 @@
def __init__(self, auto_create=False, catalog_root=None,
cfgpathname=None, fork_allowed=False, index_root=None, log_obj=None,
mirror=False, pkg_root=None, properties=EmptyDict, read_only=False,
- repo_root=None, trans_root=None, refresh_index=True,
+ repo_root=None, trans_root=None, file_root=None, refresh_index=True,
sort_file_max_size=indexer.SORT_FILE_MAX_SIZE, writable_root=None):
"""Prepare the repository for use."""
@@ -184,8 +191,16 @@
self.read_only = read_only
self.__sort_file_max_size = sort_file_max_size
self.__tmp_root = None
+ self.__file_root = None
- # Must be set before other roots.
+ # Set before repo root, since it's possible to have
+ # the file root in an entirely different location. Repo
+ # root will govern file_root, if an argument to file_root
+ # is not supplied in __init__.
+ if file_root:
+ self.file_root = file_root
+
+ # Must be set before most other roots.
self.repo_root = repo_root
# These are all overrides for the default values that setting
@@ -202,8 +217,11 @@
self.trans_root = trans_root
# Must be set before writable_root.
- self.__required_dirs = [self.trans_root, self.pkg_root,
- self.catalog_root]
+ if self.mirror:
+ self.__required_dirs = [self.file_root]
+ else:
+ self.__required_dirs = [self.trans_root, self.pkg_root,
+ self.catalog_root, self.file_root]
# Ideally, callers would just specify overrides for the feed
# cache root, index_root, etc. But this must be set after all
@@ -314,6 +332,9 @@
"""Create a temp directory under repository directory for
various purposes."""
+ if not self.repo_root:
+ return
+
root = self.repo_root
if self.writable_root:
root = self.writable_root
@@ -344,6 +365,9 @@
raise
return datetime.datetime.utcfromtimestamp(mod_time)
+ if not self.catalog_root:
+ return
+
# To determine if an upgrade is needed, first check for a v0
# catalog attrs file.
need_upgrade = False
@@ -424,7 +448,7 @@
"take some time."))
self.__rebuild(lm=v0_lm)
- if not self.read_only:
+ if not self.read_only and self.repo_root:
v0_cat = os.path.join(self.repo_root, "catalog",
"catalog")
for f in v0_attrs, v0_cat:
@@ -466,7 +490,7 @@
"""Destroy the catalog."""
self.__catalog = None
- if os.path.exists(self.catalog_root):
+ if self.catalog_root and os.path.exists(self.catalog_root):
shutil.rmtree(self.catalog_root)
@staticmethod
@@ -535,10 +559,23 @@
self.cfg.set_property(section, prop, value)
# Verify that all required configuration information is set.
- self.cfg.validate()
+ try:
+ self.cfg.validate()
+ except rc.RequiredPropertyValueError, ex:
+ # In mirror mode, the repository may be configured
+ # with a file store that contains content from multiple
+ # publishers. In this case, eschew the requirement that
+ # the publisher prefix must be set.
+ if self.mirror and ex.section == "publisher" and \
+ ex.prop == "prefix":
+ pass
+ else:
+ raise
def __init_dirs(self):
"""Verify and instantiate repository directory structure."""
+ if not self.repo_root:
+ return
emsg = _("repository directories incomplete")
for d in self.__required_dirs + self.__optional_dirs:
if self.auto_create or (self.writable_root and
@@ -573,6 +610,10 @@
"""Load stored configuration data and configure the repository
appropriately."""
+ if not self.repo_root:
+ self.cfg = rc.RepositoryConfig(pathname=None)
+ return
+
default_cfg_path = False
# Now load our repository configuration / metadata.
@@ -602,6 +643,9 @@
# Mirrors don't permit publication.
return
+ if not self.trans_root:
+ return
+
self.__in_flight_trans = {}
for txn in os.walk(self.trans_root):
if txn[0] == self.trans_root:
@@ -626,6 +670,9 @@
"""Private version; caller responsible for repository
locking."""
+ if not self.pkg_root:
+ return
+
default_pub = self.cfg.get_property("publisher", "prefix")
if self.read_only:
@@ -683,6 +730,9 @@
"""Private version; caller responsible for repository
locking."""
+ if not self.index_root:
+ return
+
self.__searchdb_update_handle_lock.acquire()
if self.__searchdb_update_handle:
@@ -774,8 +824,8 @@
# to load it.
self.__upgrade()
- if self.mirror:
- # In mirror-mode, nothing else to do.
+ if self.mirror or not self.repo_root:
+ # In mirror-mode, or no repo_root, nothing to do.
return
# If no catalog exists on-disk yet, ensure an empty one does
@@ -893,34 +943,54 @@
self.catalog.meta_root = root
def __set_repo_root(self, root):
- assert root is not None
+ if root:
+ root = os.path.abspath(root)
+ self.__repo_root = root
+ self.__tmp_root = os.path.join(root, "tmp")
+ self.catalog_root = os.path.join(root, "catalog")
+ self.feed_cache_root = root
+ self.index_root = os.path.join(root, "index")
+ self.pkg_root = os.path.join(root, "pkg")
+ self.trans_root = os.path.join(root, "trans")
+ if not self.file_root:
+ self.file_root = os.path.join(root, "file")
+ else:
+ self.__repo_root = None
+ self.catalog_root = None
+ self.feed_cache_root = None
+ self.index_root = None
+ self.pkg_root = None
+ self.trans_root = None
+
+ def __get_file_root(self):
+ return self.__file_root
- root = os.path.abspath(root)
- self.__repo_root = root
- self.__tmp_root = os.path.join(root, "tmp")
- self.catalog_root = os.path.join(root, "catalog")
- self.feed_cache_root = root
+ def __set_file_root(self, root):
+ self.__file_root = root
try:
- self.cache_store = file_manager.FileManager(
- os.path.join(root, "file"), self.read_only)
+ self.cache_store = file_manager.FileManager(root,
+ self.read_only)
except file_manager.NeedToModifyReadOnlyFileManager:
- try:
- fs = os.lstat(self.repo_root)
- except OSError, e:
- # If the stat failed due to this, then assume
- # the repository is possibly valid but that
- # there is a permissions issue.
- if e.errno == EACCES:
- raise api_errors.PermissionsException(
- e.filename)
- raise
- # If the stat succeeded, then regardless of whether
- # repo_root is really a directory, the repository is
- # invalid.
- raise RepositoryInvalidError(self.repo_root)
- self.index_root = os.path.join(root, "index")
- self.pkg_root = os.path.join(root, "pkg")
- self.trans_root = os.path.join(root, "trans")
+ if self.repo_root:
+ try:
+ fs = os.lstat(self.repo_root)
+ except OSError, e:
+ # If the stat failed due to this, then
+ # assume the repository is possibly
+ # valid but that there is a permissions
+ # issue.
+ if e.errno == EACCES:
+ raise api_errors.\
+ PermissionsException(
+ e.filename)
+ raise
+ # If the stat succeeded, then regardless of
+ # whether repo_root is really a directory, the
+ # repository is invalid.
+ raise RepositoryInvalidError(self.repo_root)
+ # If repository root hasn't been specified yet,
+ # just raise the error with the path that is available.
+ raise RepositoryInvalidError(root)
def __set_writable_root(self, root):
if root is not None:
@@ -928,10 +998,14 @@
self.__tmp_root = os.path.join(root, "tmp")
self.feed_cache_root = root
self.index_root = os.path.join(root, "index")
- else:
+ elif self.repo_root:
self.__tmp_root = os.path.join(self.repo_root, "tmp")
self.feed_cache_root = self.repo_root
self.index_root = os.path.join(self.repo_root, "index")
+ else:
+ self.__tmp_root = None
+ self.feed_cache_root = None
+ self.index_root = None
self.__writable_root = root
def __unlock_repository(self):
@@ -993,6 +1067,8 @@
raise RepositoryMirrorError()
if self.read_only:
raise RepositoryReadOnlyError()
+ if not self.repo_root:
+ raise RepositoryUnsupportedOperationError()
self.__lock_repository()
try:
@@ -1019,6 +1095,8 @@
raise RepositoryMirrorError()
if self.read_only:
raise RepositoryReadOnlyError()
+ if not self.repo_root:
+ raise RepositoryUnsupportedOperationError()
self.__lock_repository()
try:
@@ -1042,6 +1120,8 @@
raise RepositoryMirrorError()
if self.read_only:
raise RepositoryReadOnlyError()
+ if not self.repo_root:
+ raise RepositoryUnsupportedOperationError()
self.__lock_repository()
try:
@@ -1071,6 +1151,9 @@
as the v0 updatelog does not support renames, obsoletion,
package removal, etc."""
+ if not self.catalog_root:
+ raise RepositoryUnsupportedOperationError()
+
c = self.catalog
self.inc_catalog()
@@ -1092,6 +1175,8 @@
if self.mirror:
raise RepositoryMirrorError()
+ if not self.catalog_root:
+ raise RepositoryUnsupportedOperationError()
assert name
self.inc_catalog()
@@ -1106,6 +1191,8 @@
if self.mirror:
raise RepositoryMirrorError()
+ if not self.repo_root:
+ raise RepositoryUnsupportedOperationError()
try:
t = self.__in_flight_trans[trans_id]
@@ -1163,6 +1250,8 @@
if self.mirror:
raise RepositoryMirrorError()
+ if not self.repo_root:
+ raise RepositoryUnsupportedOperationError()
self.inc_manifest()
@@ -1183,6 +1272,8 @@
raise RepositoryMirrorError()
if self.read_only:
raise RepositoryReadOnlyError()
+ if not self.repo_root:
+ raise RepositoryUnsupportedOperationError()
self.__lock_repository()
try:
@@ -1203,6 +1294,8 @@
if self.mirror:
raise RepositoryMirrorError()
+ if not self.repo_root:
+ raise RepositoryUnsupportedOperationError()
self.__lock_repository()
try:
@@ -1215,6 +1308,8 @@
in the catalog and adds them in"""
if self.mirror:
raise RepositoryMirrorError()
+ if not self.repo_root:
+ raise RepositoryUnsupportedOperationError()
self.__lock_repository()
try:
@@ -1230,6 +1325,8 @@
if self.mirror:
raise RepositoryMirrorError()
+ if not self.repo_root:
+ raise RepositoryUnsupportedOperationError()
self.__lock_repository()
try:
@@ -1261,6 +1358,8 @@
if self.mirror:
raise RepositoryMirrorError()
+ if not self.index_root:
+ raise RepositoryUnsupportedOperationError()
c = self.catalog
fmris_to_index = indexer.Indexer.check_for_updates(
@@ -1283,6 +1382,8 @@
if self.mirror:
raise RepositoryMirrorError()
+ if not self.index_root:
+ raise RepositoryUnsupportedOperationError()
def _search(q):
assert self.index_root
@@ -1322,6 +1423,8 @@
if self.mirror:
raise RepositoryMirrorError()
+ if not self.repo_root:
+ raise RepositoryUnsupportedOperationError()
if not fmri.is_valid_pkg_name(pfmri.get_name()):
return False
if not pfmri.version:
@@ -1341,6 +1444,7 @@
self.__unlock_repository()
catalog_root = property(__get_catalog_root, __set_catalog_root)
+ file_root = property(__get_file_root, __set_file_root)
repo_root = property(__get_repo_root, __set_repo_root)
writable_root = property(__get_writable_root, __set_writable_root)
--- a/src/modules/server/repositoryconfig.py Tue May 18 11:11:28 2010 +0100
+++ b/src/modules/server/repositoryconfig.py Tue May 18 10:51:50 2010 -0700
@@ -47,27 +47,52 @@
class PropertyError(Exception):
"""Base exception class for property errors."""
- def __init__(self, *args):
- Exception.__init__(self, *args)
+ def __init__(self, section, prop):
+ self.section = section
+ self.prop = prop
+ def __str__(self):
+ raise NotImplementedError()
class InvalidPropertyError(PropertyError):
"""Exception class used to indicate an invalid property."""
+ def __str__(self):
+ return "Invalid property %s.%s" % (self.section, self.prop)
+
+class InvalidSectionError(InvalidPropertyError):
+ """Exception class used to indicate an invalid section."""
+
+ def __init__(self, section):
+ InvalidPropertyError.__init__(self, section, None)
+
+ def __str__(self):
+ return "Invalid Property. Unknown section: %s." % self.section
class InvalidPropertyValueError(PropertyError):
"""Exception class used to indicate an invalid property value."""
+ def __init__(self, section, prop, value):
+ PropertyError.__init__(self, section, prop)
+ self.value = value
+
+ def __str__(self):
+ return "Invalid value '%s' for %s.%s." % (self.value,
+ self.section, self.prop)
class RequiredPropertyValueError(PropertyError):
"""Exception class used to indicate a required property value is
missing."""
+ def __str__(self):
+ return "%s.%s is required." % (self.section, self.prop)
class ReadOnlyPropertyError(PropertyError):
"""Exception class used to indicate when an attempt to set a read-only
value was made."""
+ def __str__(self):
+ return "%s.%s is read-only." % (self.section, self.prop)
class RepositoryConfig(object):
"""A RepositoryConfig object is a collection of configuration
@@ -93,18 +118,15 @@
"description": {},
"detailed_url": {
"type": PROP_TYPE_URI,
- "default": "http://www.opensolaris.com"
},
"legal_uris": {
"type": PROP_TYPE_URI_LIST
},
"maintainer": {
- "default":
- "Project Indiana <[email protected]>"
+ "type": PROP_TYPE_STR
},
"maintainer_url": {
"type": PROP_TYPE_URI,
- "default": "http://www.opensolaris.org/os/project/indiana/"
},
"mirrors": {
"type": PROP_TYPE_URI_LIST
@@ -132,7 +154,7 @@
"readonly": True,
},
"name": {
- "default": "opensolaris.org repository feed"
+ "default": "package repository feed"
},
"description": {},
"icon": {
@@ -160,7 +182,7 @@
self.__dirty = False
self.__pathname = pathname
- if os.path.exists(pathname):
+ if pathname and os.path.exists(pathname):
# If a pathname was provided, read the data in.
self.__read(overrides=properties)
else:
@@ -207,16 +229,12 @@
"""
if section not in cls._props:
if raise_error:
- raise InvalidPropertyError("Invalid "
- " property. Unknown section: %s." % \
- (section))
+ raise InvalidSectionError(section)
else:
return False
if prop not in cls._props[section]:
if raise_error:
- raise InvalidPropertyError("Invalid "
- "property %s.%s." % \
- (section, prop))
+ raise InvalidPropertyError(section, prop)
else:
return False
return True
@@ -322,11 +340,9 @@
if raise_error:
if value in (None, ""):
raise RequiredPropertyValueError(
- "%s.%s is required." % \
- (section, prop))
+ section, prop)
raise InvalidPropertyValueError(
- "Invalid value '%s' for %s.%s." % \
- (value, section, prop))
+ section, prop, value)
else:
return False
else:
@@ -395,8 +411,7 @@
if not self.is_readonly_property(section, prop):
return self._set_property(section, prop, value)
else:
- raise ReadOnlyPropertyError("%s.%s is read-only." % \
- (prop, section))
+ raise ReadOnlyPropertyError(section, prop)
def __read(self, overrides=misc.EmptyDict):
"""Reads the specified pathname and populates the configuration
@@ -404,6 +419,9 @@
expected to be in a ConfigParser-compatible format.
"""
+ if not self.__pathname:
+ return
+
# Reset to initial state to ensure we only have default values
# so that any values not overwritten by the saved configuration
# will be correct.
@@ -470,7 +488,10 @@
"""Saves the current configuration object to the specified
pathname using ConfigParser.
"""
- if os.path.exists(self.__pathname) and not self.__dirty:
+
+ if not self.__pathname:
+ return
+ elif os.path.exists(self.__pathname) and not self.__dirty:
return
cp = ConfigParser.SafeConfigParser()
--- a/src/pkgdefs/SUNWipkg/prototype Tue May 18 11:11:28 2010 +0100
+++ b/src/pkgdefs/SUNWipkg/prototype Tue May 18 10:51:50 2010 -0700
@@ -10,6 +10,7 @@
d none lib/svc 755 root bin
d none lib/svc/method 755 root bin
f none lib/svc/method/svc-pkg-depot 755 root bin
+f none lib/svc/method/svc-pkg-mdns 755 root bin
d none usr 755 root sys
d none usr/bin 755 root bin
f none usr/bin/pkg 755 root bin
@@ -122,6 +123,8 @@
f none usr/lib/python2.6/vendor-packages/pkg/client/transport/exception.pyc 444 root bin
f none usr/lib/python2.6/vendor-packages/pkg/client/transport/fileobj.py 444 root bin
f none usr/lib/python2.6/vendor-packages/pkg/client/transport/fileobj.pyc 444 root bin
+f none usr/lib/python2.6/vendor-packages/pkg/client/transport/mdetect.py 444 root bin
+f none usr/lib/python2.6/vendor-packages/pkg/client/transport/mdetect.pyc 444 root bin
f none usr/lib/python2.6/vendor-packages/pkg/client/transport/repo.py 444 root bin
f none usr/lib/python2.6/vendor-packages/pkg/client/transport/repo.pyc 444 root bin
f none usr/lib/python2.6/vendor-packages/pkg/client/transport/stats.py 444 root bin
@@ -322,3 +325,4 @@
d none var/svc/manifest 755 root sys
d none var/svc/manifest/application 755 root sys
f none var/svc/manifest/application/pkg-server.xml 444 root sys
+f none var/svc/manifest/application/pkg-mdns.xml 444 root sys
--- a/src/setup.py Tue May 18 11:11:28 2010 +0100
+++ b/src/setup.py Tue May 18 10:51:50 2010 -0700
@@ -19,8 +19,7 @@
#
# CDDL HEADER END
#
-# Copyright 2010 Sun Microsystems, Inc. All rights reserved.
-# Use is subject to license terms.
+# Copyright (c) 2008, 2010 Oracle and/or its affiliates. All rights reserved.
#
import errno
@@ -198,6 +197,7 @@
],
svc_method_dir: [
['svc/svc-pkg-depot', 'svc-pkg-depot'],
+ ['svc/svc-pkg-mdns', 'svc-pkg-mdns'],
],
}
@@ -301,6 +301,7 @@
'brand/smf_disable.conf',
]
smf_files = [
+ 'svc/pkg-mdns.xml',
'svc/pkg-server.xml',
'svc/pkg-update.xml',
]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/svc/pkg-mdns.xml Tue May 18 10:51:50 2010 -0700
@@ -0,0 +1,121 @@
+<?xml version="1.0"?>
+<!--
+ CDDL HEADER START
+
+ The contents of this file are subject to the terms of the
+ Common Development and Distribution License (the "License").
+ You may not use this file except in compliance with the License.
+
+ You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ or http://www.opensolaris.org/os/licensing.
+ See the License for the specific language governing permissions
+ and limitations under the License.
+
+ When distributing Covered Code, include this CDDL HEADER in each
+ file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ If applicable, add the following below this CDDL HEADER, with the
+ fields enclosed by brackets "[]" replaced with your own identifying
+ information: Portions Copyright [yyyy] [name of copyright owner]
+
+ CDDL HEADER END
+
+ Copyright (c) 2010 Oracle and/or its affiliates. All rights reserved.
+
+ NOTE: This service manifest is not editable; its contents will
+ be overwritten by package or patch operations, including
+ operating system upgrade. Make customizations in a different
+ file.
+-->
+
+<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1">
+
+<service_bundle type='manifest' name=':pkg-mdns'>
+
+<service
+ name='application/pkg/dynamic-mirror'
+ type='service'
+ version='1'>
+
+ <create_default_instance enabled='false' />
+
+ <dependency
+ name='fs'
+ grouping='require_all'
+ restart_on='none'
+ type='service'>
+ <service_fmri value='svc:/system/filesystem/local' />
+ </dependency>
+
+ <!--
+ If we're homed on an autofs mount point, then we should
+ delay until our path becomes available.
+ -->
+ <dependency
+ name='autofs'
+ grouping='optional_all'
+ restart_on='none'
+ type='service'>
+ <service_fmri value='svc:/system/filesystem/autofs' />
+ </dependency>
+
+ <!--
+ To ensure that the sequence IDs between two repositories are
+ sensible, we should delay startup until we can issue correct
+ timestamps.
+ -->
+ <dependency
+ name='ntp'
+ grouping='optional_all'
+ restart_on='none'
+ type='service'>
+ <service_fmri value='svc:/network/ntp' />
+ </dependency>
+
+ <dependency
+ name='network'
+ grouping='require_all'
+ restart_on='none'
+ type='service'>
+ <service_fmri value='svc:/milestone/network' />
+ </dependency>
+
+ <exec_method
+ type='method'
+ name='start'
+ exec='%{pkg/pkg_root}/lib/svc/method/svc-pkg-mdns %m'
+ timeout_seconds='0'>
+ <method_context>
+ <method_credential user='pkg5srv' group='pkg5srv' />
+ </method_context>
+ </exec_method>
+
+ <exec_method
+ type='method'
+ name='stop'
+ exec='%{pkg/pkg_root}/lib/svc/method/svc-pkg-mdns %m %{restarter/contract}'
+ timeout_seconds='30'>
+ </exec_method>
+
+ <property_group name='startd' type='framework'>
+ <propval name='duration' type='astring' value='child' />
+ </property_group>
+
+ <property_group name='pkg' type='application'>
+ <propval name='file_root' type='astring'
+ value='/var/pkg/download' />
+ <propval name='pkg_root' type='astring' value='/' />
+ <propval name='port' type='count' value='10000' />
+ </property_group>
+
+ <stability value='Unstable' />
+
+ <template>
+ <common_name>
+ <loctext xml:lang='C'>
+image packaging repository
+ </loctext>
+ </common_name>
+ </template>
+</service>
+
+</service_bundle>
--- a/src/svc/pkg-server.xml Tue May 18 11:11:28 2010 +0100
+++ b/src/svc/pkg-server.xml Tue May 18 10:51:50 2010 -0700
@@ -19,8 +19,7 @@
CDDL HEADER END
- Copyright 2010 Sun Microsystems, Inc. All rights reserved.
- Use is subject to license terms.
+ Copyright (c) 2009, 2010 Oracle and/or its affiliates. All rights reserved.
NOTE: This service manifest is not editable; its contents will
be overwritten by package or patch operations, including
@@ -120,6 +119,7 @@
<propval name='ssl_key_file' type='astring' value='none' />
<propval name='writable_root' type='astring' value=''/>
<propval name='sort_file_max_size' type='astring' value=''/>
+ <propval name='file_root' type='astring' value='' />
</property_group>
<property_group name='pkg_secure' type='application'>
--- a/src/svc/svc-pkg-depot Tue May 18 11:11:28 2010 +0100
+++ b/src/svc/svc-pkg-depot Tue May 18 10:51:50 2010 -0700
@@ -19,8 +19,8 @@
#
# CDDL HEADER END
#
-# Copyright 2009 Sun Microsystems, Inc. All rights reserved.
-# Use is subject to license terms.
+# Copyright (c) 2009, 2010 Oracle and/or its affiliates. All rights reserved.
+#
# Load SMF constants and functions
. /lib/svc/share/smf_include.sh
@@ -46,13 +46,14 @@
# short_option_props are properties which are communicated to the depot
# via a long option flag which takes an argument.
- long_option_props="cfg_file content_root debug log_access log_errors \
- proxy_base sort_file_max_size ssl_cert_file ssl_dialog ssl_key_file \
- writable_root"
+ long_option_props="cfg_file content_root debug file_root log_access \
+ log_errors proxy_base sort_file_max_size ssl_cert_file \
+ ssl_dialog ssl_key_file writable_root"
set -A long_option_cmd_line "cfg-file" "content-root" "debug" \
- "log-access" "log-errors" "proxy-base" "sort-file-max-size" \
- "ssl-cert-file" "ssl-dialog" "ssl-key-file" "writable-root"
+ "file-root" "log-access" "log-errors" "proxy-base" \
+ "sort-file-max-size" "ssl-cert-file" "ssl-dialog" "ssl-key-file" \
+ "writable-root"
bool_ops=""
option_props=""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/svc/svc-pkg-mdns Tue May 18 10:51:50 2010 -0700
@@ -0,0 +1,176 @@
+#!/usr/bin/ksh -p
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+# Copyright (c) 2010 Oracle and/or its affiliates. All rights reserved.
+#
+
+# Load SMF constants and functions
+. /lib/svc/share/smf_include.sh
+
+if [[ -z "$SMF_FMRI" ]]; then
+ echo "this script can only be invoked by smf(5)"
+ exit $SMF_EXIT_ERR_NOSMF
+fi
+
+case "$1" in
+'start')
+ # Handles mDNS depot startup
+
+ # short_option_props are properties which are communicated to the depot
+ # via a single character flag which takes an argument.
+ short_option_props="port"
+ set -A short_option_cmd_line "p"
+
+ long_option_props="file_root"
+
+ set -A long_option_cmd_line "file-root"
+
+ # retrieve the pkg_root property. If the variable is left empty
+ # pkg_root is /
+ pkg_root=$(svcprop -p pkg/pkg_root $SMF_FMRI)
+ if [[ $? -ne 0 ]]; then
+ echo "service property pkg/pkg_root not defined for" \
+ "service: $SMF_FMRI"
+ exit $SMF_EXIT_ERR_CONFIG
+ fi
+
+ # make sure pkg_root ends with a /
+ echo $pkg_root | grep /$ >/dev/null
+ if [[ $? -ne 0 ]]; then
+ pkg_root="${pkg_root}/"
+ fi
+
+ # adjust the PYTHONPATH to point to the current environment
+ # we need to make sure to adjust the PYTHONPATH accordingly
+ # to a Python 2.4 or 2.6 environment
+ python_ver=$(head -1 ${pkg_root}usr/lib/pkg.depotd 2>/dev/null |
+ awk -F/ '{print $NF}')
+ if [[ $python_ver != *python* ]]; then
+ echo "invalid python version $python_ver found in"
+ echo "${pkg_root}usr/lib/pkg.depotd"
+ exit $SMF_EXIT_ERR_FATAL
+ fi
+
+ PYTHONPATH=${pkg_root}usr/lib/${python_ver}/vendor-packages/:$PYTHONPATH
+
+ export PYTHONPATH
+
+ # Go through each property in short_option_props and, if its value is
+ # set to something other than "", add the appropriate command line
+ # flag and argument to the string.
+ cnt=0
+ for o in $short_option_props; do
+ val=$(svcprop -p pkg/$o $SMF_FMRI)
+ if [[ $? -ne 0 ]]; then
+ echo "service property pkg/$o not defined for" \
+ "service: $SMF_FMRI"
+ exit $SMF_EXIT_ERR_CONFIG
+ fi
+ # If the SMF property is set to something other than 'none', add
+ # the flag and its argument to the command.
+ if [[ $val != '""' ]]; then
+ option_ops="$option_ops -${short_option_cmd_line[$cnt]} $val"
+ fi
+ cnt=$(($cnt + 1))
+ done
+
+ # Go through each property in long_option_props and, if its value is
+ # set to something other than "", add the appropriate command line
+ # flag and argument to the string.
+ cnt=0
+ for o in $long_option_props; do
+ val=$(svcprop -p pkg/$o $SMF_FMRI)
+ if [[ $? -ne 0 ]]; then
+ echo "service property pkg/$o not defined for" \
+ "service: $SMF_FMRI"
+ exit $SMF_EXIT_ERR_CONFIG
+ fi
+ if [[ $val != '""' ]]; then
+ option_ops="$option_ops --${long_option_cmd_line[$cnt]}=$val"
+ fi
+ cnt=$(($cnt + 1))
+ done
+
+ # In order to run in mdns mode, we need to append the --llmirror
+ # flag to the list of command options. Do that last, here.
+ option_ops="$option_ops --llmirror"
+
+ # Build the command to start pkg.depotd with the specified options.
+ cmd="${pkg_root}usr/lib/pkg.depotd $option_ops"
+ # Echo the command so that the log contains the command used to start
+ # the depot.
+ echo $cmd
+
+ exec $cmd
+
+ ;;
+
+'stop')
+ #
+ # Strategy: First, try shutting down depot using polite kill. Use up
+ # as much as possible of the allotted timeout period waiting for polite
+ # kill to take effect. As time runs out, try a more aggressive kill.
+ #
+ SVC_TIMEOUT=`svcprop -p stop/timeout_seconds $SMF_FMRI`
+ if [[ $? -ne 0 ]]; then
+ echo "service property stop/timeout_seconds not defined" \
+ "for service: $SMF_FMRI"
+ exit $SMF_EXIT_ERR_CONFIG
+ fi
+
+ #
+ # Note that we're working around an oddity in smf_kill_contract: it
+ # waits in 5 second chunks and can overshoot the specified timeout
+ # by as many as 4 seconds. Example: a specified wait of 6 will result
+ # in a wait of 10 seconds in reality. Since we may potentially do a
+ # first kill and then a second, we must ensure that at least 8 seconds
+ # of slop is left in reserve. To be paranoid, we go for 10.
+ #
+ ((POLITE=$SVC_TIMEOUT - 10))
+ if [[ $POLITE -gt 0 ]]; then
+ smf_kill_contract $2 TERM 1 $POLITE
+ ret=$?
+ # '2' indicates timeout with non-empty contract.
+ if [[ $ret -eq 2 ]]; then
+ echo "Gentle contract kill timed out after"
+ "$POLITE seconds, trying SIGKILL." >&2
+ #
+ # Again, despite the specified timeout, this will
+ # take a minimum of 5 seconds to complete.
+ #
+ smf_kill_contract $2 KILL 1 1
+ if [[ $ret -ne 0 ]]; then
+ exit $SMF_EXIT_ERR_FATAL
+ fi
+ fi
+ else
+ # If the timeout is too short, we just try once, politely.
+ smf_kill_contract $2 TERM
+ fi
+ ;;
+
+*)
+ echo "Usage: $0 { start | stop }"
+ exit $SMF_EXIT_ERR_CONFIG
+ ;;
+
+esac
+exit $SMF_EXIT_OK
--- a/src/tests/api/t_api_search.py Tue May 18 11:11:28 2010 +0100
+++ b/src/tests/api/t_api_search.py Tue May 18 10:51:50 2010 -0700
@@ -1523,6 +1523,7 @@
self._search_op(api_obj, remote, 'unique_dir',
self.res_space_unique)
remote = True
+ time.sleep(1)
self._search_op(api_obj, remote, 'with', set())
self._search_op(api_obj, remote, 'with*',
self.res_space_with_star)
@@ -1762,7 +1763,7 @@
tok_file = os.path.join(ind_dir, ss.BYTE_OFFSET_FILE)
main_file = os.path.join(ind_dir, ss.MAIN_FILE)
self.pkgsend_bulk(durl, self.example_pkg10)
- time.sleep(1)
+ time.sleep(2)
fh = open(tok_file)
tok_1 = fh.readlines()
tok_len = len(tok_1)
@@ -1771,7 +1772,7 @@
main_1 = fh.readlines()
main_len = len(main_1)
self.pkgsend_bulk(durl, self.example_pkg10, optional=False)
- time.sleep(1)
+ time.sleep(2)
fh = open(tok_file)
tok_2 = fh.readlines()
new_tok_len = len(tok_2)
@@ -2117,7 +2118,7 @@
self._run_remote_empty_tests(api_obj)
os.rmdir(tmp_dir)
self.pkgsend_bulk(durl, self.example_pkg10)
- time.sleep(1)
+ time.sleep(2)
self._run_remote_tests(api_obj)
self._search_op(api_obj, True, "unique_dir",
self.res_space_unique)
--- a/src/tests/cli/t_pkg_depotd.py Tue May 18 11:11:28 2010 +0100
+++ b/src/tests/cli/t_pkg_depotd.py Tue May 18 10:51:50 2010 -0700
@@ -322,6 +322,38 @@
shutil.rmtree(dpath)
self.dc.set_repodir(opath)
+ def test_repo_file_only(self):
+ """Test that if a depot is created with only --file-root
+ supplied, it comes up in mirror mode, with only file content
+ available."""
+
+ if self.dc.is_alive():
+ self.dc.stop()
+
+ fpath = "/var/pkg/download"
+ opath = self.dc.get_repodir()
+ self.dc.set_repodir(None)
+ self.dc.set_file_root(fpath)
+ self.dc.set_readwrite()
+ self.dc.start()
+ self.assert_(self.dc.is_alive())
+
+ durl = self.dc.get_depot_url()
+ verdata = urllib2.urlopen("%s/versions/0/" % durl)
+ verlines = verdata.readlines()
+ verdict = dict(
+ s.split(None, 1)
+ for s in (l.strip() for l in verlines)
+ )
+
+ self.assert_("file" in verdict)
+ self.assert_("catalog" not in verdict)
+ self.assert_("manifest" not in verdict)
+
+ self.dc.stop()
+ self.dc.set_repodir(opath)
+ self.dc.set_file_root(None)
+
class TestDepotController(pkg5unittest.CliTestCase):
--- a/src/tests/cli/t_pkg_install.py Tue May 18 11:11:28 2010 +0100
+++ b/src/tests/cli/t_pkg_install.py Tue May 18 10:51:50 2010 -0700
@@ -298,6 +298,24 @@
self.pkg("list")
self.pkg("list [email protected] [email protected] [email protected]")
+ def test_basics_mdns(self):
+ """ Send empty package [email protected], install and uninstall """
+
+ self.pkgsend_bulk(self.rurl, self.foo11)
+ self.image_create(self.rurl)
+
+ self.pkg("set-property mirror-discovery True")
+ self.pkg("list -a")
+ self.pkg("list", exit=1)
+
+ self.pkg("install foo")
+
+ self.pkg("list")
+ self.pkg("verify")
+
+ self.pkg("uninstall foo")
+ self.pkg("verify")
+
def test_image_upgrade(self):
""" Send package [email protected], dependent on [email protected]. Install
[email protected]. List all packages. Upgrade image. """
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/util/distro-import/140/common/package:pkg Tue May 18 10:51:50 2010 -0700
@@ -0,0 +1,19 @@
+package package/pkg
+consolidation "ips"
+classification "System/Packaging"
+import SUNWipkg
+add group groupname=pkg5srv gid=97
+add user username=pkg5srv uid=97 group=pkg5srv gcos-field="pkg(5) server UID"
+depend library/python-2/[email protected]
+depend library/python-2/[email protected]
+depend library/python-2/[email protected]
+depend library/python-2/[email protected]
+depend library/python-2/[email protected]
+depend library/python-2/simplejson-26
+end package
+
+package SUNWipkg
+consolidation "ips"
+renamed 133
+depend package/pkg
+end package