--- a/.hgignore Wed Jun 20 10:40:03 2007 -0700
+++ b/.hgignore Thu Jun 21 23:43:12 2007 -0700
@@ -1,9 +1,10 @@
# Ignore compiled byte code, lint output, object files, shared objects,
-# and the entire proto area.
+# VIM swap files, and the entire proto area.
\.pyc
\.ln
\.o
\.so
+\.sw.
^proto
# Specific build products
^src/pkg$
--- a/doc/TODO Wed Jun 20 10:40:03 2007 -0700
+++ b/doc/TODO Thu Jun 21 23:43:12 2007 -0700
@@ -5,6 +5,10 @@
Code
- need to outline ELF inspection module
+ - ali has constructed a simple C example for extracting run
+ paths
+ http://blogs.sun.com/ali/entry/changing_elf_runpaths
+
- need to start constructing placeholder classes/methods for various
pieces
- list of pieces
--- a/doc/rfes.txt Wed Jun 20 10:40:03 2007 -0700
+++ b/doc/rfes.txt Thu Jun 21 23:43:12 2007 -0700
@@ -28,3 +28,9 @@
could be treated as choking, or we could always examine intermediate
manifests.
+9. [psa] Take a snapshot [of each affected filesystem] between every
+ package update operation in a larger image transaction, as opposed
+ to at the image transaction boundaries only.
+
+10. [sch] Examine use of alternative, HTTP 1.1-friendly URL loading
+ modules. (Example: Duke's urlgrabber.)
--- a/src/Makefile Wed Jun 20 10:40:03 2007 -0700
+++ b/src/Makefile Thu Jun 21 23:43:12 2007 -0700
@@ -116,7 +116,10 @@
ln -s $(PWD)/modules /usr/lib/python2.4/vendor-packages/pkg
# Invoke all known modules with tests.
+# XXX Invoke the bundle tests.
test:
+ # $(PYTHON) $(LINKPYTHONPKG)/bundle/__init__.py a_sysv_pkg
+ # $(PYTHON) $(LINKPYTHONPKG)/bundle/__init__.py a_tarball
$(PYTHON) $(LINKPYTHONPKG)/misc.py
$(PYTHON) $(LINKPYTHONPKG)/version.py
$(PYTHON) $(LINKPYTHONPKG)/fmri.py
--- a/src/client.py Wed Jun 20 10:40:03 2007 -0700
+++ b/src/client.py Thu Jun 21 23:43:12 2007 -0700
@@ -62,6 +62,7 @@
import pkg.version as version
import pkg.client.image as image
+import pkg.client.imageplan as imageplan
def usage():
print """\
@@ -128,66 +129,16 @@
image.reload_catalogs()
image.display_catalogs()
-def pattern_install(config, image, pattern, strict):
- # check catalogs for this pattern; None is the representation of the
- # freezes
- matches = image.get_matching_pkgs(pattern)
-
- if len(matches) == 0:
- raise NameError, "pattern '%s' has no matching packages" % \
- pattern
-
- matches.sort()
-
- # If there is more than one package in the list, then we've got an
- # ambiguous outcome, and need to exit.
-
- l = matches[0]
- for p in matches:
- if p.is_same_pkg(l):
- l = p
- continue
-
- raise KeyError, "pattern %s led to multiple packages" % pattern
-
-
- # pick appropriate version, based on request and freezes
- p = max(matches)
- # XXX can we do this with the is_successor()/version() and a map?
- # warn on dependencies; exit if --strict
-
- image.retrieve_manifest(None, p)
-
- # do we have this manifest?
- # get it if not
- # request manifest
- # examine manifest for dependencies
- # if satisfied by inventory, continue
- # if satisfied by pending transaction, continue
- # if unsatisfied, then
- # if optional, then evaluate client's optional policy to either
- # treat-as-required, treat-as-required-unless-pinned, ignore
- # skip if ignoring
- # if pinned
- # ignore if treat-as-required-unless-pinned
- # else
- # **evaluation of incorporations**
- # pursue installation of this package
- # examine manifest for files
- #
- # request files
-
- return # a client operation
-
-
def install(config, image, args):
- """Attempt to take package specified to INSTALLED state."""
- strict = False
- oplist = []
+ """Attempt to take package specified to INSTALLED state. The operands
+ are interpreted as glob patterns.
+ XXX Authority-catalog issues."""
opts = None
pargs = None
+ error = 0
+
if len(args) > 0:
opts, pargs = getopt.getopt(args, "S")
@@ -197,22 +148,38 @@
image.reload_catalogs()
+ ip = imageplan.ImagePlan(image)
+
for ppat in pargs:
- ops = None
+ rpat = re.sub("\*", ".*", ppat)
+ rpat = re.sub("\?", ".", rpat)
try:
- ops = pattern_install(config, image, ppat, strict)
+ matches = image.get_regex_matching_fmris(rpat)
except KeyError:
- # okay skip this argument
- print "pkg: ambiguous package pattern '%s'" % ppat
- except NameError:
- print "pkg: unknown package pattern '%s'" % ppat
+ print """\
+pkg: no package matching '%s' could be found in current catalog
+ suggest relaxing pattern, refreshing and/or examining catalogs""" % ppat
+ error = 1
+ continue
+
+ pnames = {}
+ for m in matches:
+ pnames[m[1].get_pkg_stem()] = 1
- if ops != None:
- oplist.append(ops)
+ if len(pnames.keys()) > 1:
+ print "pkg: '%s' matches multiple packages" % ppat
+ for k in pnames.keys():
+ print "\t%s" % k
+ continue
+ ip.propose_fmri(m[1])
- # perform update transaction as given in oplist
+ if error != 0:
+ sys.exit(error)
+
+ ip.evaluate()
+ ip.execute()
return
@@ -259,12 +226,9 @@
# XXX need an Image configuration by default
icfg = image.Image()
-icfg.find_parent()
pcfg = config.ParentRepo("http://localhost:10000", ["http://localhost:10000"])
if __name__ == "__main__":
-
-
opts = None
pargs = None
try:
@@ -282,6 +246,12 @@
# Handle PKG_IMAGE and PKG_SERVER environment variables.
+ if subcommand == "image":
+ create_image(pcfg, pargs)
+ sys.exit(0)
+
+ icfg.find_parent()
+
if subcommand == "refresh":
catalog_refresh(pcfg, icfg, pargs)
elif subcommand == "catalog":
@@ -294,8 +264,6 @@
freeze(pcfg, icfg, pargs)
elif subcommand == "unfreeze":
unfreeze(pcfg, icfg, pargs)
- elif subcommand == "image":
- create_image(pcfg, pargs)
else:
print "pkg: unknown subcommand '%s'" % subcommand
usage()
--- a/src/depot.py Wed Jun 20 10:40:03 2007 -0700
+++ b/src/depot.py Thu Jun 21 23:43:12 2007 -0700
@@ -25,6 +25,22 @@
# pkg.depotd - package repository daemon
+# XXX The prototype pkg.depotd combines both the version management server that
+# answers to pkgsend(1) sessions and the HTTP file server that answers to the
+# various GET operations that a pkg(1) client makes. This split is expected to
+# be made more explicit, by constraining the pkg(1) operations such that they
+# can be served as a typical HTTP/HTTPS session. Thus, pkg.depotd will reduce
+# to a special purpose HTTP/HTTPS server explicitly for the version management
+# operations, and must manipulate the various state files--catalogs, in
+# particular--such that the pkg(1) pull client can operately accurately with
+# only a basic HTTP/HTTPS server in place.
+
+# XXX We should support simple "last-modified" operations via HEAD queries.
+
+# XXX Although we pushed the evaluation of next-version, etc. to the pull
+# client, we should probably provide a query API to do same on the server, for
+# dumb clients (like a notification service).
+
import BaseHTTPServer
import getopt
import os
@@ -50,7 +66,7 @@
Usage: /usr/lib/pkg.depotd [-n]
"""
-def catalog(scfg, request):
+def catalog_get(scfg, request):
scfg.inc_catalog()
request.send_response(200)
@@ -58,64 +74,22 @@
request.end_headers()
request.wfile.write("%s" % scfg.catalog)
-def manifest(scfg, request):
+def manifest_get(scfg, request):
"""The request is an encoded pkg FMRI. If the version is specified
- incompletely, we return the latest matching manifest. The matched
- version is returned in the headers. If an incomplete FMRI is received,
- the client is expected to have provided a build release in the request
- headers.
-
- Legitimate requests are
-
- /manifest/[URL]
- /manifest/branch/[URL]
- /manifest/release/[URL]
-
- allowing the request of the next matching version, based on client
- constraints."""
+ incompletely, we return an error, as the client is expected to form
+ correct requests, based on its interpretation of the catalog and its
+ image policies."""
scfg.inc_manifest()
- constraint = None
-
# Parse request into FMRI component and decode.
m = re.match("^/manifest/(.*)", request.path)
pfmri = urllib.unquote(m.group(1))
- # Look for exact match.
- # XXX XXX
-
- # Determine closest version.
- try:
- vs = scfg.catalog.get_matching_pkgs(pfmri, constraint)
- except KeyError, e:
- msg = "manifest request failed: '%s'" % pfmri
- request.log_message(msg)
-
- request.send_response(500)
- request.send_header('Content-type', 'text/plain')
- request.end_headers()
-
- return
-
- msg = "manifest request '%s': " % pfmri
- for v in vs:
- msg = msg + "%s " % v
- request.log_message(msg)
-
-# XXX XXX
-# if len(vs) > 1:
-# request.log_message("multiple manifest result ambiguous")
-# request.send_response(400)
-# request.send_header('Content-type', 'text/plain')
-# request.end_headers()
-#
-# return
-
- p = vs[0]
+ f = fmri.PkgFmri(pfmri, None)
# Open manifest and send.
- file = open("%s/%s" % (scfg.pkg_root, p.get_dir_path()), "r")
+ file = open("%s/%s" % (scfg.pkg_root, f.get_dir_path()), "r")
data = file.read()
request.send_response(200)
@@ -123,7 +97,7 @@
request.end_headers()
request.wfile.write(data)
-def get_file(scfg, request):
+def file_get_single(scfg, request):
"""The request is the SHA-1 hash name for the file."""
scfg.inc_file()
@@ -194,11 +168,11 @@
def do_GET(self):
# Client APIs
if re.match("^/catalog$", self.path):
- catalog(scfg, self)
+ catalog_get(scfg, self)
elif re.match("^/manifest/.*$", self.path):
- manifest(scfg, self)
+ manifest_get(scfg, self)
elif re.match("^/file/.*$", self.path):
- get_file(scfg, self)
+ file_get_single(scfg, self)
# Publisher APIs
elif re.match("^/open/(.*)$", self.path):
--- a/src/modules/bundle/SolarisPackageDatastreamBundle.py Wed Jun 20 10:40:03 2007 -0700
+++ b/src/modules/bundle/SolarisPackageDatastreamBundle.py Thu Jun 21 23:43:12 2007 -0700
@@ -43,6 +43,7 @@
}
class SolarisPackageDatastreamBundle(object):
+ """XXX Need a class comment."""
def __init__(self, filename):
self.pkg = SolarisPackage(filename)
--- a/src/modules/bundle/TarBundle.py Wed Jun 20 10:40:03 2007 -0700
+++ b/src/modules/bundle/TarBundle.py Thu Jun 21 23:43:12 2007 -0700
@@ -36,7 +36,8 @@
def __init__(self, filename):
self.tf = tarfile.open(filename)
- # XXX This could be more intelligent. Or get user input.
+ # XXX This could be more intelligent. Or get user input. Or
+ # extend API to take FMRI.
self.pkgname = os.path.basename(filename)
def __del__(self):
--- a/src/modules/catalog.py Wed Jun 20 10:40:03 2007 -0700
+++ b/src/modules/catalog.py Thu Jun 21 23:43:12 2007 -0700
@@ -42,12 +42,17 @@
incorporation relationships between packages. This latter section
allows the graph to be topologically sorted by the client.
+ S Last-Modified: [timespec]
+
XXX A authority mirror-uri ...
XXX ...
V fmri
V fmri
...
+ C fmri
+ C fmri
+ ...
I fmri fmri
I fmri fmri
...
@@ -60,14 +65,24 @@
XXX self.pkgs should be a dictionary, accessed by fmri string (or
package name). Current code is O(N_packages) O(M_versions), should be
- O(1) O(M_versions), and possibly O(1) O(1).
+ O(1) O(M_versions), and possibly O(1) O(1). Likely a similar need for
+ critical_pkgs.
+
+ XXX Initial estimates suggest that the Catalog could be composed of 1e5
+ - 1e7 lines. Catalogs across these magnitudes will need to be spread
+ out into chunks, and may require a delta-oriented update interface.
"""
def __init__(self):
self.catalog_root = ""
+ self.attrs = {}
+ self.attrs["Last-Modified"] = time.time()
+
self.authorities = {}
self.pkgs = []
+ self.critical_pkgs = []
+
self.relns = {}
def add_authority(self, authority, urls):
@@ -87,11 +102,19 @@
for entry in centries:
# each V line is an fmri
m = re.match("^V (pkg:[^ ]*)", entry)
- if m == None:
- continue
+ if m != None:
+ pname = m.group(1)
+ self.add_fmri(fmri.PkgFmri(pname, None))
- pname = m.group(1)
- self.add_fmri(fmri.PkgFmri(pname, None))
+ m = re.match("^S ([^:]*): (.*)", entry)
+ if m != None:
+ self.attrs[m.group(1)] = m.group(2)
+
+ m = re.match("^C (pkg:[^ ]*)", entry)
+ if m != None:
+ pname = m.group(1)
+ self.critical_pkgs.append(fmri.PkgFmri(pname,
+ None))
def add_fmri(self, pkgfmri):
name = pkgfmri.get_pkg_stem()
@@ -106,7 +129,7 @@
pkg.add_version(pkgfmri)
self.pkgs.append(pkg)
- def add_pkg(self, pkg):
+ def add_pkg(self, pkg, critical = False):
for opkg in self.pkgs:
if pkg.fmri == opkg.fmri:
#
@@ -118,30 +141,47 @@
return
self.pkgs.append(pkg)
-
- def add_package_fmri(self, pkg_fmri):
- return
-
- def delete_package_fmri(self, pkg_fmri):
- return
+ if critical:
+ self.critical_pkgs.append(pkg)
def get_matching_pkgs(self, pfmri, constraint):
- """Iterate through the catalog's, looking for an fmri match."""
+ """Iterate through the catalogs, looking for an exact fmri
+ match. XXX Returns a list of Package objects."""
- # XXX FMRI-based implementation doesn't do pattern matching, but
- # exact matches only.
pf = fmri.PkgFmri(pfmri, None)
-
for pkg in self.pkgs:
if pkg.fmri.is_similar(pf):
return pkg.matching_versions(pfmri, constraint)
- raise KeyError, "%s not found in catalog" % pfmri
+ raise KeyError, "FMRI '%s' not found in catalog" % pfmri
+
+ def get_regex_matching_fmris(self, regex):
+ """Iterate through the catalogs, looking for a regular
+ expression match. Returns an unsorted list of PkgFmri
+ objects."""
+
+ ret = []
+
+ for p in self.pkgs:
+ for pv in p.pversions:
+ fn = "%s@%s" % (p.fmri, pv.version)
+ if re.search(regex, fn):
+ ret.append(fmri.PkgFmri(fn, None))
+
+ if ret == []:
+ raise KeyError, "pattern '%s' not found in catalog" \
+ % regex
+
+ return ret
def __str__(self):
s = ""
+ for a in self.attrs.keys():
+ s = s + "S %s: %s\n" % (a, self.attrs[a])
for p in self.pkgs:
s = s + p.get_catalog_entry()
+ for c in self.critical_pkgs:
+ s = s + "C %s\n" % p
for r in self.relns:
s = s + "I %s\n" % r
return s
@@ -186,3 +226,11 @@
for p in c.get_matching_pkgs(tp, None):
print " ", p
+
+ for p in c.get_regex_matching_fmris("test"):
+ print p
+
+ try:
+ l = c.get_regex_matching_fmris("flob")
+ except KeyError:
+ print "correctly determined no match for 'flob'"
--- a/src/modules/client/image.py Wed Jun 20 10:40:03 2007 -0700
+++ b/src/modules/client/image.py Thu Jun 21 23:43:12 2007 -0700
@@ -28,6 +28,7 @@
import urllib
import pkg.catalog as catalog
+import pkg.manifest as manifest
IMG_ENTIRE = 0
IMG_PARTIAL = 1
@@ -86,6 +87,11 @@
self.catalogs = {}
self.authorities = {}
+ self.attrs = {}
+
+ self.attrs["Policy-Require-Optional"] = False
+ self.attrs["Policy-Pursue-Latest"] = True
+
def find_parent(self):
# Ascend from current working directory to find first
# encountered image.
@@ -150,56 +156,54 @@
for c in self.catalogs.values():
c.display()
- def get_matching_pkgs(self, pattern):
- """The pattern is a glob pattern, which we translate to an RE
- pattern.
-
- XXX This is going to need to return (catalog, fmri) pairs."""
+ def get_matching_pkgs(self, pfmri):
+ """Exact matches to the given FMRI. Returns a list of (catalog,
+ pkg) pairs."""
m = []
for c in self.catalogs.values():
- m.extend(c.get_matching_pkgs(pattern, None))
+ m.extend([(c, p) \
+ for p in c.get_matching_pkgs(pfmri, None)])
return m
- def retrieve_manifest(self, catalog, fmri):
- """Turn FMRI to Image's path to manifest. If present, return.
- If not present, calculate URI and retrieve.
+ def get_regex_matching_fmris(self, regex):
+ """FMRIs matching the given regular expression. Returns of a
+ list of (catalog, PkgFmri) pairs."""
- XXX Which catalog did we fetch this fmri from?"""
+ m = []
+ for c in self.catalogs.values():
+ m.extend([(c, p) \
+ for p in c.get_regex_matching_fmris(regex)])
- authority, pkg_name, version = fmri.tuple()
+ return m
+ def has_manifest(self, fmri):
mpath = fmri.get_dir_path()
local_mpath = "%s/pkg/%s/manifest" % (self.imgdir, mpath)
if (os.path.exists(local_mpath)):
- print "short circuit retrieve"
- return local_mpath # or return object?
+ return True
+
+ return False
- # XXX convert authority reference to server
- if authority == None:
- authority = "localhost:10000"
- url_mpath = "http://%s/manifest/%s" % (authority,
- fmri.get_url_path())
+ def get_manifest(self, fmri):
+ m = manifest.Manifest()
+ mcontent = file("%s/pkg/%s/manifest" %
+ (self.imgdir, fmri.get_dir_path())).read()
+ m.set_content(mcontent)
+ return m
- print url_mpath
- try:
- m = urllib.urlopen(url_mpath)
- except:
- raise NameError, "could not open %s" % url_mpath
+ def get_version_installed(self, fmri):
+ istate = "%s/pkg/%s/installed" % (self.imgdir, fmri.get_dir_path(True))
+ if not os.path.exists(istate):
+ raise LookupError, "no installed version of '%s'" % fmri
- data = m.read()
- print local_mpath
- try:
- mfile = file(local_mpath, "w")
- print >>mfile, data
- except IOError, e:
- os.makedirs(os.path.dirname(local_mpath))
- mfile = file(local_mpath, "w")
- print >>mfile, data
+ vs = os.readlink(istate)
+
+ return fmri.PkgFmri("%s/%s" % (fmri, vs))
- return local_mpath
-
-
+if __name__ == "__main__":
+ # XXX Need to construct a trivial image and catalog.
+ pass
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/modules/client/imageplan.py Thu Jun 21 23:43:12 2007 -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 2007 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+
+import os
+import re
+import urllib
+
+import pkg.catalog as catalog
+import pkg.fmri as fmri
+
+import pkg.client.pkgplan as pkgplan
+import pkg.client.retrieve as retrieve # XXX inventory??
+
+UNEVALUATED = 0
+EVALUATED_OK = 1
+EVALUATED_ERROR = 2
+EXECUTED_OK = 3
+EXECUTED_ERROR = 4
+
+class ImagePlan(object):
+ """An image plan takes a list of requested packages, an Image (and its
+ policy restrictions), and returns the set of package operations needed
+ to transform the Image to the list of requested packages.
+
+ Use of an ImagePlan involves the identification of the Image, the
+ Catalogs (implicitly), and a set of complete or partial package FMRIs.
+ The Image's policy, which is derived from its type and configuration
+ will cause the formulation of the plan or an exception state.
+
+ XXX In the current formulation, an ImagePlan can handle [null ->
+ PkgFmri] and [PkgFmri@Version1 -> PkgFmri@Version2], for a set of
+ PkgFmri objects. With a correct Action object definition, deletion
+ should be able to be represented as [PkgFmri@V1 -> null].
+
+ XXX Should we allow downgrades? There's an "arrow of time" associated
+ with the smf(5) configuration method, so it's better to direct
+ manipulators to snapshot-based rollback, but if people are going to do
+ "pkg delete fmri; pkg install fmri@v(n - 1)", then we'd better have a
+ plan to identify when this operation is safe or unsafe."""
+
+ def __init__(self, image):
+ self.image = image
+ self.goal_fmris = []
+ self.state = UNEVALUATED
+
+ self.target_fmris = []
+ self.pkg_plans = []
+
+ def __str__(self):
+ if self.state == UNEVALUATED:
+ s = "UNEVALUATED: "
+ for t in self.target_fmris:
+ s = s + "%s\n" % t
+ return s
+
+ for pp in self.pkg_plans:
+ s = s + "%s\n" % pp
+ return s
+
+ def set_goal_pkg_fmris(self, pflist):
+ self.goal_pkg_fmris = pflist
+
+ def propose_fmri(self, fmri):
+ # is a version of fmri.stem in the inventory?
+ # is there a freeze or incorporation statement?
+ # do any of them eliminate this fmri version?
+ # discard
+ # is a version of fmri in our target_fmris?
+ n = range(len(self.target_fmris))
+ if n == []:
+ self.target_fmris.append(fmri)
+ return
+
+ for i in n:
+ p = self.target_fmris[i]
+ if fmri.is_same_pkg(p):
+ if fmri > p:
+ self.target_fmris[i] = fmri
+ break
+
+ return
+
+ def evaluate_fmri(self, fmri):
+ # [image] do we have this manifest?
+ if not self.image.has_manifest(fmri):
+ retrieve.get_manifest(self.image, fmri)
+
+ m = self.image.get_manifest(fmri)
+
+ # [manifest] examine manifest for dependencies
+ # [image] if satisfied by inventory, continue
+ # [imageplan] if satisfied by pending transaction, continue
+ # if unsatisfied, then
+ # if optional, then evaluate client's optional policy to either
+ # treat-as-required, treat-as-required-unless-pinned, ignore
+ # skip if ignoring
+ # if pinned
+ # ignore if treat-as-required-unless-pinned
+ # else
+ # **evaluation of incorporations**
+ # [imageplan] pursue installation of this package -->
+ # backtrack or reset??
+
+ pp = pkgplan.PkgPlan(self.image)
+
+ pp.propose_destination(fmri, m)
+
+ pp.evaluate()
+
+ self.pkg_plans.append(pp)
+
+ def evaluate(self):
+ assert self.state == UNEVALUATED
+
+ for f in self.target_fmris:
+ self.evaluate_fmri(f)
+
+ self.state = EVALUATED_OK
+
+ def execute(self):
+ """Invoke the evaluated image plan, constructing each package
+ plan."""
+ assert self.state == EVALUATED_OK
+
+ # image related operations, like a snapshot
+
+ for p in self.pkg_plans:
+ p.preexecute()
+
+ for p in self.pkg_plans:
+ # per-package image operations (further snapshots)
+ p.execute()
+
+ for p in self.pkg_plans:
+ p.postexecute()
+
+ self.state = EXECUTED_OK
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/modules/client/pkgplan.py Thu Jun 21 23:43:12 2007 -0700
@@ -0,0 +1,108 @@
+#!/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 2007 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+
+import os
+import re
+import urllib
+
+import pkg.catalog as catalog
+
+class PkgPlan(object):
+ """A package plan takes two package FMRIs and an Image, and produces the
+ set of actions required to take the Image from the origin FMRI to the
+ destination FMRI."""
+
+ def __init__(self, image):
+ self.origin_fmri = None
+ self.destination_fmri = None
+ self.origin_mfst = None
+ self.destination_mfst = None
+
+ self.image = image
+
+ self.actions = []
+
+ def set_origin(self, fmri):
+ self.origin_fmri = fmri
+ self.origin_mfst = manifest.retrieve(fmri)
+
+ def propose_destination(self, fmri, manifest):
+ self.destination_fmri = fmri
+ self.destination_mfst = manifest
+
+ def is_valid(self):
+ if self.origin_fmri == None:
+ return True
+
+ if not self.origin_fmri.is_same_pkg(self.destination_fmri):
+ return False
+
+ if self.origin_fmri > self.destination_fmri:
+ return False
+
+ return True
+
+ def get_actions(self):
+ return []
+
+ def evaluate(self):
+ # if origin unset, determine if we're dealing with an previously
+ # installed version or if we're dealing with the null package
+ f = None
+ if self.origin_fmri == None:
+ try:
+ f = self.image.get_version_installed(self.destination_fmri)
+ except LookupError:
+ pass
+
+ # if null package, then our plan is the set of actions for the
+ # destination version
+ if f == None:
+ self.actions = self.destination_mfst.actions
+ else:
+ # if a previous package, then our plan is derived from the
+ # set differences between the previous manifest's actions and
+ # the union of the destination manifest's actions with the
+ # critical actions of the critical versions in the version
+ # interval between origin and destination.
+ self.actions = self.destination_mfst.difference(
+ self.origin_mfst)
+ return
+
+ def preexecute(self):
+ # retrieval step
+ return
+
+ def execute(self):
+ # record that we are in an intermediate state
+ # mv step
+ # XXX
+ for a in self.actions:
+ print a
+ return
+
+ def postexecute(self):
+ # record that package states are consistent
+ return
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/modules/client/retrieve.py Thu Jun 21 23:43:12 2007 -0700
@@ -0,0 +1,101 @@
+#!/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 2007 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+
+import getopt
+import httplib
+import os
+import re
+import sys
+import urllib
+import urllib2
+import urlparse
+
+# client/retrieve.py - collected methods for retrieval of pkg components
+# from repositories
+
+def url_catalog(config, image, args):
+ """XXX will need to show available content series for each package"""
+ croot = image.imgdir
+
+ if len(args) != 0:
+ print "pkg: catalog subcommand takes no arguments"
+ usage()
+
+ # Ensure Image directory structure is valid.
+ if not os.path.isdir("%s/catalog" % croot):
+ image.mkdirs()
+
+ # GET /catalog
+ for repo in pcfg.repo_uris:
+ # Ignore http_proxy for localhost case, by overriding default
+ # proxy behaviour of urlopen().
+ proxy_uri = None
+ netloc = urlparse.urlparse(repo)[1]
+ if urllib.splitport(netloc)[0] == "localhost":
+ proxy_uri = {}
+
+ uri = urlparse.urljoin(repo, "catalog")
+
+ c = urllib.urlopen(uri, proxies=proxy_uri)
+
+ # compare headers
+ data = c.read()
+ fname = urllib.quote(c.geturl(), "")
+
+ # Filename should be reduced to host\:port
+ cfile = file("%s/catalog/%s" % (croot, fname), "w")
+ print >>cfile, data
+
+def get_manifest(image, fmri):
+ """Calculate URI and retrieve.
+ XXX Authority-catalog issues."""
+
+ authority, pkg_name, version = fmri.tuple()
+
+ # XXX convert authority reference to server
+ if authority == None:
+ authority = "localhost:10000"
+
+ url_mpath = "http://%s/manifest/%s" % (authority,
+ fmri.get_url_path())
+
+ try:
+ m = urllib.urlopen(url_mpath)
+ except:
+ raise NameError, "could not open %s" % url_mpath
+
+ data = m.read()
+ local_mpath = "%s/pkg/%s/manifest" % (image.imgdir, fmri.get_dir_path())
+
+ try:
+ mfile = file(local_mpath, "w")
+ print >>mfile, data
+ except IOError, e:
+ os.makedirs(os.path.dirname(local_mpath))
+ mfile = file(local_mpath, "w")
+ print >>mfile, data
+
+def url_file(uri, dest):
+ return
--- a/src/modules/fmri.py Wed Jun 20 10:40:03 2007 -0700
+++ b/src/modules/fmri.py Thu Jun 21 23:43:12 2007 -0700
@@ -95,12 +95,6 @@
return
- def get_authority(self):
- return self.authority
-
- def set_authority(self, authority):
- self.authority = authority
-
def __str__(self):
"""Return as specific an FMRI representation as possible."""
if self.authority == None:
@@ -115,6 +109,18 @@
return "pkg://%s/%s@%s" % (self.authority, self.pkg_name,
self.version)
+ def get_authority(self):
+ return self.authority
+
+ def set_authority(self, authority):
+ self.authority = authority
+
+ def set_timestamp(self, new_ts):
+ self.version.set_timestamp(new_ts)
+
+ def get_timestamp(self, new_ts):
+ return self.version.get_timestamp()
+
def get_pkg_stem(self):
"""Return a string representation of the FMRI without a specific
version."""
@@ -123,9 +129,12 @@
return "pkg://%s/%s" % (self.authority, self.pkg_name)
- def get_dir_path(self):
- """Return the escaped directory path fragment for this FMRI.
- Requires a version to be defined."""
+ def get_dir_path(self, stemonly = False):
+ """Return the escaped directory path fragment for this FMRI."""
+
+ if stemonly:
+ return "%s" % (urllib.quote(self.pkg_name, ""))
+
assert self.version != None
return "%s/%s" % (urllib.quote(self.pkg_name, ""),
@@ -184,12 +193,6 @@
return True
- def set_timestamp(self, new_ts):
- self.version.set_timestamp(new_ts)
-
- def get_timestamp(self, new_ts):
- return self.version.get_timestamp()
-
if __name__ == "__main__":
n1 = PkgFmri("pkg://pion/sunos/coreutils", "5.9")
n2 = PkgFmri("sunos/coreutils", "5.10")
--- a/src/modules/manifest.py Wed Jun 20 10:40:03 2007 -0700
+++ b/src/modules/manifest.py Thu Jun 21 23:43:12 2007 -0700
@@ -306,6 +306,8 @@
"""str is the text representation of the manifest"""
for l in str.splitlines():
+ if re.match("^\s*$", l):
+ continue
if re.match("^set ", l):
self.add_attribute_line(l)
elif re.match("^file ", l):
--- a/src/modules/server/config.py Wed Jun 20 10:40:03 2007 -0700
+++ b/src/modules/server/config.py Thu Jun 21 23:43:12 2007 -0700
@@ -53,6 +53,10 @@
self.catalog = catalog.Catalog()
self.in_flight_trans = {}
+ # XXX naive: change to
+ # catalog_requests = [ (IP-addr, time), ... ]
+ # manifest_requests = { fmri : (IP-addr, time), ... }
+ # file requests = [ (IP-addr, time), ... ]
self.catalog_requests = 0
self.manifest_requests = 0
self.file_requests = 0
--- a/src/modules/server/transaction.py Wed Jun 20 10:40:03 2007 -0700
+++ b/src/modules/server/transaction.py Thu Jun 21 23:43:12 2007 -0700
@@ -46,12 +46,17 @@
self.open_time = -1
self.pkg_name = ""
self.esc_pkg_name = ""
+ self.critical = False
self.cfg = None
self.client_release = ""
self.fmri = None
self.dir = ""
return
+ def get_basename(self):
+ return "%d_%s" % (self.open_time,
+ urllib.quote("%s" % self.fmri, ""))
+
def open(self, cfg, request):
self.cfg = cfg
@@ -136,7 +141,6 @@
# XXX Build a response from our lists of unsatisfied
# dependencies.
-
try:
shutil.rmtree("%s/%s" % (self.cfg.trans_root, trans_id))
except:
@@ -187,6 +191,10 @@
rfile = request.rfile
action = pkg.actions.types[type](rfile, **attrs)
+ # XXX Once actions are labelled with critical nature.
+ # if type in critical_actions:
+ # self.critical = True
+
# XXX Need to handle multiple streams
fnames = ()
for opener in action.data.values():
@@ -214,8 +222,10 @@
tfile = file("%s/%s/manifest" %
(self.cfg.trans_root, trans_id), "a")
- print >>tfile, ("%s" + " %s" * (len(action.attrs) + len(action.data))) % \
- ((type,) + tuple(action.attrs[k] for k in action.attributes()) + fnames)
+ print >>tfile, \
+ ("%s" + " %s" * (len(action.attrs) + len(action.data))) % \
+ ((type,) + tuple(action.attrs[k]
+ for k in action.attributes()) + fnames)
request.send_response(200)
@@ -245,7 +255,9 @@
p.update(self.cfg, self)
# add entry to catalog
- self.cfg.catalog.add_pkg(p)
+ # XXX Is self.cfg.catalog a copy or a reference??
+ # XXX Could just write new catalog file...
+ self.cfg.catalog.add_pkg(p, self.critical)
return ("%s" % self.fmri, "PUBLISHED")
@@ -254,7 +266,3 @@
Make appropriate catalog entries."""
return
- def get_basename(self):
- return "%d_%s" % (self.open_time,
- urllib.quote("%s" % self.fmri, ""))
-
--- a/src/modules/sysvpkg.py Wed Jun 20 10:40:03 2007 -0700
+++ b/src/modules/sysvpkg.py Thu Jun 21 23:43:12 2007 -0700
@@ -53,7 +53,8 @@
class PkgMapLine(object):
"""A class that represents a single line of a SysV package's pkgmap.
- This class should probably disappear once pkg.Content is a bit more
+
+ XXX This class should probably disappear once pkg.Content is a bit more
fleshed out.
"""
@@ -162,7 +163,7 @@
elif ci.name.endswith("/pkgmap"):
self._pkgmap = self.datastream.extractfile(ci).readlines()
- # Here we allow for only one package. :(
+ # XXX Here we allow for only one package. :(
self.datastream = self.datastream.get_next_archive()
else: