src/modules/client/history.py
changeset 1027 e827313523d8
parent 1019 e61c57c724c9
child 1032 ff3c6b09f430
equal deleted inserted replaced
1026:d4aa3ac69dc0 1027:e827313523d8
    23 #
    23 #
    24 # Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
    24 # Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
    25 # Use is subject to license terms.
    25 # Use is subject to license terms.
    26 #
    26 #
    27 
    27 
       
    28 import copy
    28 import errno
    29 import errno
    29 import os
    30 import os
    30 import shutil
    31 import shutil
    31 import sys
    32 import sys
    32 import xml.dom.minidom as xmini
    33 import xml.dom.minidom as xmini
    70 DISCARDED_OPERATIONS = ["contents", "info", "list"]
    71 DISCARDED_OPERATIONS = ["contents", "info", "list"]
    71 
    72 
    72 # Cross-reference table for errors and results.  Entries should be ordered
    73 # Cross-reference table for errors and results.  Entries should be ordered
    73 # most-specific to least-specific.
    74 # most-specific to least-specific.
    74 error_results = {
    75 error_results = {
       
    76     api_errors.BENamingNotSupported: RESULT_FAILED_BAD_REQUEST,
       
    77     api_errors.InvalidBENameException: RESULT_FAILED_BAD_REQUEST,
    75     api_errors.CertificateError: RESULT_FAILED_CONFIGURATION,
    78     api_errors.CertificateError: RESULT_FAILED_CONFIGURATION,
    76     api_errors.PublisherError: RESULT_FAILED_BAD_REQUEST,
    79     api_errors.PublisherError: RESULT_FAILED_BAD_REQUEST,
    77     api_errors.CanceledException: RESULT_CANCELED,
    80     api_errors.CanceledException: RESULT_CANCELED,
       
    81     api_errors.ImageUpdateOnLiveImageException: RESULT_FAILED_BAD_REQUEST,
       
    82     api_errors.ProblematicPermissionsIndexException: RESULT_FAILED_STORAGE,
       
    83     api_errors.PermissionsException: RESULT_FAILED_STORAGE,
       
    84     api_errors.MainDictParsingException: RESULT_FAILED_STORAGE,
       
    85     api_errors.SearchException: RESULT_FAILED_SEARCH,
       
    86     api_errors.NonLeafPackageException: RESULT_FAILED_CONSTRAINED,
       
    87     api_errors.IpkgOutOfDateException: RESULT_FAILED_CONSTRAINED,
    78     fmri.IllegalFmri: RESULT_FAILED_BAD_REQUEST,
    88     fmri.IllegalFmri: RESULT_FAILED_BAD_REQUEST,
       
    89     KeyboardInterrupt: RESULT_CANCELED,
    79 }
    90 }
    80 
    91 
    81 class _HistoryException(Exception):
    92 class _HistoryException(Exception):
    82         """Private base exception class for all History exceptions."""
    93         """Private base exception class for all History exceptions."""
    83         def __init__(self, *args):
    94         def __init__(self, *args):
   123 
   134 
   124         This class provides an abstraction layer between the stack of
   135         This class provides an abstraction layer between the stack of
   125         operations that History manages should these values need to be
   136         operations that History manages should these values need to be
   126         manipulated as they are set or retrieved.
   137         manipulated as they are set or retrieved.
   127         """
   138         """
       
   139 
       
   140         def __copy__(self):
       
   141                 h = _HistoryOperation()
       
   142                 for attr in ("name", "start_time", "end_time", "start_state",
       
   143                     "end_state", "username", "userid", "result"):
       
   144                         setattr(h, attr, getattr(self, attr))
       
   145                 h.errors = [copy.copy(e) for e in self.errors]
       
   146                 return h
   128 
   147 
   129         def __setattr__(self, name, value):
   148         def __setattr__(self, name, value):
   130                 if name not in ("result", "errors"):
   149                 if name not in ("result", "errors"):
   131                         # Force all other attribute values to be a string
   150                         # Force all other attribute values to be a string
   132                         # to avoid issues with minidom.
   151                         # to avoid issues with minidom.
   170         result = None
   189         result = None
   171 
   190 
   172         def __init__(self):
   191         def __init__(self):
   173                 self.errors = []
   192                 self.errors = []
   174 
   193 
       
   194 
   175 class History(object):
   195 class History(object):
   176         """A History object is a representation of data about a pkg(5) client
   196         """A History object is a representation of data about a pkg(5) client
   177         and about operations that the client is executing or has executed.  It
   197         and about operations that the client is executing or has executed.  It
   178         uses the _HistoryOperation class to represent the data about an
   198         uses the _HistoryOperation class to represent the data about an
   179         operation.
   199         operation.
   189         # How the client was invoked (e.g. 'pkg install -n foo').
   209         # How the client was invoked (e.g. 'pkg install -n foo').
   190         client_args = None
   210         client_args = None
   191 
   211 
   192         # A stack where operation data will actually be stored.
   212         # A stack where operation data will actually be stored.
   193         __operations = None
   213         __operations = None
       
   214 
       
   215         # A private property used by preserve() and restore() to store snapshots
       
   216         # of history and operation state information.
       
   217         __snapshot = None
   194 
   218 
   195         # These attributes exist to fake access to the operations stack.
   219         # These attributes exist to fake access to the operations stack.
   196         operation_name = None
   220         operation_name = None
   197         operation_username = None
   221         operation_username = None
   198         operation_userid = None
   222         operation_userid = None
   200         operation_end_time = None
   224         operation_end_time = None
   201         operation_start_state = None
   225         operation_start_state = None
   202         operation_end_state = None
   226         operation_end_state = None
   203         operation_errors = None
   227         operation_errors = None
   204         operation_result = None
   228         operation_result = None
       
   229 
       
   230         def __copy__(self):
       
   231                 h = History()
       
   232                 for attr in ("root_dir", "client_name", "client_version"):
       
   233                         setattr(h, attr, getattr(self, attr))
       
   234                 object.__setattr__(self, "client_args",
       
   235                     [copy.copy(a) for a in self.client_args])
       
   236                 # A deepcopy has to be performed here since this a list of dicts
       
   237                 # and not just History operation objects.
       
   238                 h.__operations = [copy.deepcopy(o) for o in self.__operations]
       
   239                 return h
   205 
   240 
   206         def __getattribute__(self, name):
   241         def __getattribute__(self, name):
   207                 if name == "client_args":
   242                 if name == "client_args":
   208                         return object.__getattribute__(self, name)[:]
   243                         return object.__getattribute__(self, name)[:]
   209 
   244 
   612                 and 'error' is provided, the final result of the operation will
   647                 and 'error' is provided, the final result of the operation will
   613                 be based on the class of 'error' and 'error' will be recorded
   648                 be based on the class of 'error' and 'error' will be recorded
   614                 for the current operation.  If 'result' and 'error' is not
   649                 for the current operation.  If 'result' and 'error' is not
   615                 provided, success is assumed."""
   650                 provided, success is assumed."""
   616 
   651 
       
   652                 if error:
       
   653                         self.log_operation_error(error)
       
   654 
   617                 if error and not result:
   655                 if error and not result:
   618                         try:
   656                         try:
   619                                 # Attempt get an exact error match first.
   657                                 # Attempt get an exact error match first.
   620                                 result = error_results[error.__class__]
   658                                 result = error_results[error.__class__]
   621                         except (AttributeError, KeyError):
   659                         except (AttributeError, KeyError):
   637         def log_operation_error(self, error):
   675         def log_operation_error(self, error):
   638                 """Adds an error to the list of errors to be recorded in image
   676                 """Adds an error to the list of errors to be recorded in image
   639                 history for the current opreation."""
   677                 history for the current opreation."""
   640                 if self.operation_name:
   678                 if self.operation_name:
   641                         self.operation_errors.append(error)
   679                         self.operation_errors.append(error)
       
   680 
       
   681         def create_snapshot(self):
       
   682                 """Stores a snapshot of the current history and operation state
       
   683                 information in memory so that it can be restored in the event of
       
   684                 client failure (such as inability to store history information
       
   685                 or the failure of a boot environment operation).  Each call to
       
   686                 this function will overwrite the previous snapshot."""
       
   687 
       
   688                 attrs = self.__snapshot = {}
       
   689                 for attr in ("root_dir", "client_name", "client_version"):
       
   690                         attrs[attr] = getattr(self, attr)
       
   691                 attrs["client_args"] = [copy.copy(a) for a in self.client_args]
       
   692                 # A deepcopy has to be performed here since this a list of dicts
       
   693                 # and not just History operation objects.
       
   694                 attrs["__operations"] = \
       
   695                     [copy.deepcopy(o) for o in self.__operations]
       
   696 
       
   697         def discard_snapshot(self):
       
   698                 """Discards the current history and operation state information
       
   699                 snapshot."""
       
   700                 self.__snapshot = None
       
   701 
       
   702         def restore_snapshot(self):
       
   703                 """Restores the last snapshot taken of history and operation
       
   704                 state information completely discarding the existing history and
       
   705                 operation state information.  If nothing exists to restore, this
       
   706                 this function will silently return."""
       
   707 
       
   708                 if not self.__snapshot:
       
   709                         return
       
   710 
       
   711                 for name, val in self.__snapshot.iteritems():
       
   712                         if not name.startswith("__"):
       
   713                                 object.__setattr__(self, name, val)
       
   714                 self.__operations = self.__snapshot["__operations"]