start pkg graph management, annotate pkgsend with server states
author"Stephen Hahn <sch@sun.com>"
Wed, 04 Apr 2007 18:26:56 -0700
changeset 14 969c85e852af
parent 13 2a4c0dd21262
child 15 fba016a465cc
start pkg graph management, annotate pkgsend with server states add reminders on damaged and diskless install states remove pkgsend pieces from pkg add catalog, dependency, content (incomplete) change versions to use timestamps (incomplete)
doc/pkg-states.txt
src/client.py
src/depot.py
src/pkg-modules/catalog.py
src/pkg-modules/content.py
src/pkg-modules/dependency.py
src/pkg-modules/package.py
src/pkg-modules/version.py
--- a/doc/pkg-states.txt	Tue Apr 03 14:46:40 2007 -0700
+++ b/doc/pkg-states.txt	Wed Apr 04 18:26:56 2007 -0700
@@ -152,3 +152,9 @@
     operation) get represented in the state?  Is there an image state
     machine model as well?
 
+    XXX Need a substate of INSTALLED for damaged packages.
+
+    XXX Need a substate of INSTALLED for packages where the global zone
+    portion is available, but local installation has not finished.  Can
+    we generalize this state for all diskless installs?
+
--- a/src/client.py	Tue Apr 03 14:46:40 2007 -0700
+++ b/src/client.py	Wed Apr 04 18:26:56 2007 -0700
@@ -28,6 +28,15 @@
 # We use urllib2 for GET and POST operations, but httplib for PUT and DELETE
 # operations.
 
+# The client is going to maintain an on-disk cache of its state, so that startup
+# assembly of the graph is reduced.
+
+# Client graph is of the entire local catalog.  As operations progress, package
+# states will change.
+
+# Deduction operation allows the compilation of the local component of the
+# catalog, only if an authoritative repository can identify critical files.
+
 import getopt
 import httplib
 import os
@@ -36,7 +45,12 @@
 import urllib2
 import urlparse
 
+import pkg.catalog
 import pkg.config
+import pkg.dependency
+import pkg.fmri
+import pkg.package
+import pkg.version
 
 def usage():
         print """\
@@ -45,17 +59,10 @@
 
 Install subcommands:
         pkg catalog
-        pkg install pkg_name
-        pkg uninstall pkg_name
-
-Packager subcommands:
-        pkg open [-e] pkg_name
-        pkg add file|link|device path file
-        pkg delete path
-        pkg meta add require|exclude pkg_name
-        pkg meta delete pkg_name
-        pkg summary
-        pkg close
+        pkg install pkg_fmri
+        pkg uninstall pkg_fmri
+        pkg freeze [--version version_spec] [--release] [--branch] pkg_fmri
+        pkg unfreeze pkg_fmri
 
 Options:
         --repo, -s
@@ -81,80 +88,24 @@
 
                 # compare headers
 
-def trans_open(config, args):
-        opts = None
-        pargs = None
-        try:
-                opts, pargs = getopt.getopt(args, "e")
-        except:
-                print "pkg: illegal open option(s)"
-                usage()
-
-        eval_form = False
-        for opt, arg in opts:
-                if opt == "-e":
-                        eval_form = True
+def install(config, args):
+        """Attempt to take package specified to INSTALLED state."""
+        return
 
-        if len(pargs) != 1:
-                print "pkg: open requires one package name"
-                usage()
-
-        # POST /open/pkg_name
-        repo = config.install_uri
-        uri = urlparse.urljoin(repo, "open/%s" % pargs[0])
-
-        c = urllib2.urlopen(uri)
-
-        lines = c.readlines()
-        for line in lines:
-                if re.match("^Transaction-ID:", line):
-                        m = re.match("^Transaction-ID: (.*)", line)
-                        if eval_form:
-                                print "export PKG_TRANS_ID=%s" % m.group(1)
-                        else:
-                                print m.group(1)
-
+def uninstall(config, args):
+        """Attempt to take package specified to DELETED state."""
         return
 
-def trans_close(config, args):
-        # XXX alternately args contains -t trans
-        trans_id = os.environ["PKG_TRANS_ID"]
-        repo = config.install_uri
-        uri = urlparse.urljoin(repo, "close/%s" % trans_id)
-        try:
-                c = urllib2.urlopen(uri)
-        except urllib2.HTTPError:
-                print "pkg: transaction close failed"
-                sys.exit(1)
-
-def trans_add(config, args):
-        """POST the file contents to the transaction.  Default is to post to the
-        currently open content series.  -s option selects a different series."""
-
-        if not args[0] in ["file", "link", "package"]:
-                print "pkg: unknown add object '%s'" % args[0]
-                usage()
+def freeze(config, args):
+        """Attempt to take package specified to FROZEN state, with given
+        restrictions."""
+        return
 
-        trans_id = os.environ["PKG_TRANS_ID"]
-        repo = config.install_uri
-        uri_exp = urlparse.urlparse(repo)
-        host, port = re.split(":", uri_exp[1])
-        selector = "/add/%s/%s" % (trans_id, args[0])
+def unfreeze(config, args):
+        """Attempt to return package specified to INSTALLED state from FROZEN state."""
+        return
 
-        if args[0] == "file":
-                # XXX Need to handle larger files than available swap.
-                file = open(args[2])
-                data = file.read()
-        else:
-                sys.exit(99)
-
-        headers = {}
-        headers["Path"] = args[1]
-
-        c = httplib.HTTPConnection(host, port)
-        c.connect()
-        c.request("POST", selector, data, headers)
-
+# XXX need an Image configuration by default
 
 pcfg = ParentRepo("http://localhost:10000", ["http://localhost:10000"])
 
@@ -176,12 +127,14 @@
 
         if subcommand == "catalog":
                 catalog(pcfg, pargs)
-        elif subcommand == "open":
-                trans_open(pcfg, pargs)
-        elif subcommand == "close":
-                trans_close(pcfg, pargs)
-        elif subcommand == "add":
-                trans_add(pcfg, pargs)
+        elif subcommand == "install":
+                install(pcfg, pargs)
+        elif subcommand == "uninstall":
+                uninstall(pcfg, pargs)
+        elif subcommand == "freeze":
+                freeze(pcfg, pargs)
+        elif subcommand == "unfreeze":
+                unfreeze(pcfg, pargs)
         else:
                 print "pkg: unknown subcommand '%s'" % pargs[0]
                 usage()
--- a/src/depot.py	Tue Apr 03 14:46:40 2007 -0700
+++ b/src/depot.py	Wed Apr 04 18:26:56 2007 -0700
@@ -7,7 +7,20 @@
 import shutil
 import time
 
+import pkg.version as version
+import pkg.fmri as fmri
+import pkg.catalog as catalog
+import pkg.config as config
+
 def catalog(scfg, request):
+        """The marshalled form of the catalog is
+
+        pkg_name (release (branch (sequence ...) ...) ...)
+
+        since we know that the server is only to report packages for which it
+        can offer a record.
+        """
+
         request.send_response(200)
         request.send_header('Content-type:', 'text/plain')
         request.end_headers()
@@ -23,13 +36,16 @@
                 pass
         opening_time = time.time()
         m = re.match("^/open/(.*)", request.path)
-        pkg = m.group(1)
+        pkg_name = m.group(1)
 
         # XXX opaquify using hash
-        trans_basename = "%d_%s" % (opening_time, pkg)
+        trans_basename = "%d_%s" % (opening_time, pkg_name)
         os.makedirs("%s/%s" % (trans_root, trans_basename))
 
         # record transaction metadata:  opening_time, package, user
+        # lookup package by name
+        # if not found, create package
+        # set package state to TRANSACTING
 
         request.send_response(200)
         request.send_header('Content-type:', 'text/plain')
@@ -43,6 +59,12 @@
 
         trans_root = "%s/trans" % scfg.repo_root
         # XXX refine try/except
+        #
+        # set package state to SUBMITTED
+        # attempt to reconcile dependencies
+        # if reconciled, set state to PUBLISHED
+        #   call back to check incomplete list
+        # else set state to INCOMPLETE
         try:
                 shutil.rmtree("%s/%s" % (trans_root, trans_id))
                 request.send_response(200)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pkg-modules/catalog.py	Wed Apr 04 18:26:56 2007 -0700
@@ -0,0 +1,60 @@
+#!/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.
+#
+#ident	"%Z%%M%	%I%	%E% SMI"
+
+class Catalog(object):
+        """A Catalog is the representation of the package FMRIs available to
+        this client or repository.  Both purposes utilize the same storage
+        format."""
+
+        def __init__(self, authority, catalog_root):
+                self.authority = authority
+                self.catalog_root = catalog_root
+
+                # XXX We should try to open the directory, so that we fail
+                # early.
+
+        def add_package_fmri(self, pkg_fmri):
+                return
+
+        def delete_package_fmri(self, pkg_fmri):
+                return
+
+        def to_string(self):
+                """Return the catalog in its marshallable format."""
+                return ""
+
+        def from_string(self, str):
+                """Parse the given string back into the on-disk catalog."""
+                return
+
+        def difference(self, catalog):
+                """Return a pair of lists, the first list being those package
+                FMRIs present in the current object but not in the presented
+                catalog, the second being those present in the presented catalog
+                but not in the current catalog."""
+                return
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pkg-modules/content.py	Wed Apr 04 18:26:56 2007 -0700
@@ -0,0 +1,43 @@
+#!/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.
+#
+#ident	"%Z%%M%	%I%	%E% SMI"
+
+class Content(object):
+        """A Content object is a piece of one or more Packages.  The simplest
+        example of a Content object is a file.
+        
+        XXX Is an Action a Content?
+        
+        XXX Bad name."""
+
+        def __init__(self, type, name, resource):
+                self.type = type
+                self.name = name
+                self.resource = version
+
+        def set_resource(self, resource):
+                return
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pkg-modules/dependency.py	Wed Apr 04 18:26:56 2007 -0700
@@ -0,0 +1,48 @@
+#!/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.
+#
+#ident	"%Z%%M%	%I%	%E% SMI"
+
+REQUIRE = 0
+INCORPORATE = 1
+
+class Dependency(object):
+        """A Dependency object is a relationship between one Package and
+        another.  It is a bidirectional expression.
+
+        A package may require a minimum version of another package."""
+
+        def __init__(self, host_pkg_fmri, req_pkg_fmri, type = REQUIRE):
+                self.host_pkg_fmri = host_pkg_fmri
+                self.req_pkg_fmri = req_pkg_fmri
+
+                assert type == REQUIRE || type == INCORPORATE
+                self.type = type
+
+        def satisfied(self, pkg_fmri):
+                # compare pkg_fmri to req_pkg_fmri
+                # compare versions
+                return False
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pkg-modules/package.py	Wed Apr 04 18:26:56 2007 -0700
@@ -0,0 +1,55 @@
+#!/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.
+#
+#ident	"%Z%%M%	%I%	%E% SMI"
+
+class Package(object):
+        """A Package is the node in the package graph.  It consists of the
+        versioning data and authority required to construct a legitimate FMRI,
+        its dependencies and incorporations of other package FMRIs, and the
+        contents metadata used to request and install its extended content.
+
+        The dependencies are presented as a list of Dependency objects.
+
+        The contents are presented as a list of Contents objects."""
+
+        def __init__(self, authority, name, version, dependencies, contents):
+                self.authority = authority
+                self.name = name
+                self.version = version
+                self.dependencies = dependencies
+                self.contents = contents
+
+                # XXX We should try to open the directory, so that we fail
+                # early.
+
+        def add_content(self, content):
+                return
+
+        def add_dependency(self, dependency):
+                return
+
+# XXX PackageHistory or PackageSequence class?  Or is it sufficient to have a
+# content_differences(self, pkg) in the Package class?
--- a/src/pkg-modules/version.py	Tue Apr 03 14:46:40 2007 -0700
+++ b/src/pkg-modules/version.py	Wed Apr 04 18:26:56 2007 -0700
@@ -85,12 +85,12 @@
                 self.args = args
 
 class Version(object):
-        """Version format is release,branch.sequence, which we decompose
+        """Version format is release,branch:sequence, which we decompose
         into a DotSequence and branch and sequence values."""
 
         def __init__(self, version_string):
                 # XXX If illegally formatted, raise exception.
-                m = re.match("([\.\d]*),(\d*)\.(\d*)", version_string)
+                m = re.match("([\.\d]*),(\d*)\:(\d*)", version_string)
                 if m != None:
                         self.release = DotSequence(m.group(1))
                         self.branch = int(m.group(2))
@@ -116,7 +116,7 @@
                 raise IllegalVersion
 
         def __str__(self):
-                return "%s,%s.%s" % (self.release, self.branch, self.sequence)
+                return "%s,%s:%s" % (self.release, self.branch, self.sequence)
 
         def __ne__(self, other):
                 if self.release == other.release and \
@@ -163,8 +163,8 @@
         d2 = DotSequence("1.1.3")
         assert d1 == d2
 
-        v1 = Version("5.5.1,10.6")
-        v2 = Version("5.5.1,10.8")
+        v1 = Version("5.5.1,10:6")
+        v2 = Version("5.5.1,10:8")
         v3 = Version("5.5.1,10")
         v4 = Version("5.5.1,6")
         v5 = Version("5.6,1")