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. |
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"] |