2690N/A#!/usr/bin/python
2690N/A#
2690N/A# CDDL HEADER START
2690N/A#
2690N/A# The contents of this file are subject to the terms of the
2690N/A# Common Development and Distribution License (the "License").
2690N/A# You may not use this file except in compliance with the License.
2690N/A#
2690N/A# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
2690N/A# or http://www.opensolaris.org/os/licensing.
2690N/A# See the License for the specific language governing permissions
2690N/A# and limitations under the License.
2690N/A#
2690N/A# When distributing Covered Code, include this CDDL HEADER in each
2690N/A# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
2690N/A# If applicable, add the following below this CDDL HEADER, with the
2690N/A# fields enclosed by brackets "[]" replaced with your own identifying
2690N/A# information: Portions Copyright [yyyy] [name of copyright owner]
2690N/A#
2690N/A# CDDL HEADER END
2690N/A#
2690N/A
2690N/A#
3336N/A# Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved.
2690N/A#
2690N/A
2690N/A"""
2690N/APlanDescription and _ActionPlan classes
2690N/A
2690N/AThese classes are part of the public API, and any changes here may require
2690N/Abumping CURRENT_API_VERSION in pkg.api
2690N/A
2690N/AThe PlanDescription class is a public interface which contains all the data
2690N/Aassociated with an image-modifying operation.
2690N/A
2690N/AThe _ActionPlan class is a private interface used to keep track of actions
2690N/Amodified within an image during an image-modifying operation.
2690N/A"""
2690N/A
2690N/Aimport collections
2690N/Aimport itertools
2690N/Aimport operator
2690N/Aimport simplejson as json
3234N/Aimport six
2690N/A
2690N/Aimport pkg.actions
2690N/Aimport pkg.client.actuator
2690N/Aimport pkg.client.api_errors as apx
2690N/Aimport pkg.client.linkedimage as li
2690N/Aimport pkg.client.pkgplan
2690N/Aimport pkg.facet
2690N/Aimport pkg.fmri
2690N/Aimport pkg.misc
2690N/Aimport pkg.version
2690N/A
2690N/Afrom pkg.api_common import (PackageInfo, LicenseInfo)
3336N/Afrom pkg.client.pkgdefs import MSG_GENERAL
2690N/A
2690N/AUNEVALUATED = 0 # nothing done yet
2690N/AEVALUATED_PKGS = 1 # established fmri changes
2690N/AMERGED_OK = 2 # created single merged plan
2690N/AEVALUATED_OK = 3 # ready to execute
2690N/APREEXECUTED_OK = 4 # finished w/ preexecute
2690N/APREEXECUTED_ERROR = 5 # whoops
2690N/AEXECUTED_OK = 6 # finished execution
2690N/AEXECUTED_ERROR = 7 # failed
2690N/A
3452N/AOP_STAGE_PLAN = 0
3452N/AOP_STAGE_PREP = 1
3452N/AOP_STAGE_EXEC = 2
3452N/A
2690N/Aclass _ActionPlan(collections.namedtuple("_ActionPlan", "p src dst")):
2690N/A """A named tuple used to keep track of all the actions that will be
2690N/A executed during an image-modifying procecure."""
2828N/A # Class has no __init__ method; pylint: disable=W0232
2828N/A # Use __slots__ on an old style class; pylint: disable=E1001
2690N/A
2690N/A __slots__ = []
2690N/A
2690N/A __state__desc = tuple([
2690N/A pkg.client.pkgplan.PkgPlan,
2690N/A pkg.actions.generic.NSG,
2690N/A pkg.actions.generic.NSG,
2690N/A ])
2690N/A
2690N/A @staticmethod
2690N/A def getstate(obj, je_state=None):
2690N/A """Returns the serialized state of this object in a format
2690N/A that that can be easily stored using JSON, pickle, etc."""
2690N/A return pkg.misc.json_encode(_ActionPlan.__name__, tuple(obj),
2690N/A _ActionPlan.__state__desc, je_state=je_state)
2690N/A
2690N/A @staticmethod
2690N/A def fromstate(state, jd_state=None):
2690N/A """Allocate a new object using previously serialized state
2690N/A obtained via getstate()."""
2828N/A # Access to protected member; pylint: disable=W0212
2690N/A
2690N/A # get the name of the object we're dealing with
2690N/A name = _ActionPlan.__name__
2690N/A
2690N/A # decode serialized state into python objects
2690N/A state = pkg.misc.json_decode(name, state,
2690N/A _ActionPlan.__state__desc, jd_state=jd_state)
2690N/A
2690N/A return _ActionPlan(*state)
2690N/A
2690N/A
2690N/Aclass PlanDescription(object):
2690N/A """A class which describes the changes the plan will make."""
2690N/A
2690N/A __state__desc = {
2690N/A "_actuators": pkg.client.actuator.Actuator,
2690N/A "_cfg_mediators": {
2690N/A str: {
2690N/A "version": pkg.version.Version,
2690N/A "implementation-version": pkg.version.Version,
2690N/A }
2690N/A },
2690N/A "_fmri_changes": [ ( pkg.fmri.PkgFmri, pkg.fmri.PkgFmri ) ],
3366N/A # avoid, implicit-avoid, obsolete
3366N/A "_new_avoid_obs": ( set(), set(), set() ),
2690N/A "_new_mediators": collections.defaultdict(set, {
2690N/A str: {
2690N/A "version": pkg.version.Version,
2690N/A "implementation-version": pkg.version.Version,
2690N/A }
2690N/A }),
2925N/A "_old_facets": pkg.facet.Facets,
2925N/A "_new_facets": pkg.facet.Facets,
2690N/A "_rm_aliases": { str: set() },
3047N/A "_preserved": {
3047N/A "moved": [[str, str]],
3047N/A "removed": [[str]],
3047N/A "installed": [[str]],
3047N/A "updated": [[str]],
3047N/A },
3336N/A # Messaging looks like:
3336N/A # {"item_id": {"sub_item_id": [], "messages": []}}
3336N/A "_item_msgs": collections.defaultdict(dict),
3160N/A "_pkg_actuators": { str: { str: [ str ] } },
2690N/A "added_groups": { str: pkg.fmri.PkgFmri },
2690N/A "added_users": { str: pkg.fmri.PkgFmri },
3025N/A "child_op_vectors": [ ( str, [ li.LinkedImageName ], {}, bool ) ],
2690N/A "children_ignored": [ li.LinkedImageName ],
2690N/A "children_nop": [ li.LinkedImageName ],
2690N/A "children_planned": [ li.LinkedImageName ],
2690N/A "install_actions": [ _ActionPlan ],
2925N/A "li_pfacets": pkg.facet.Facets,
2690N/A "li_ppkgs": frozenset([ pkg.fmri.PkgFmri ]),
2690N/A "li_props": { li.PROP_NAME: li.LinkedImageName },
2690N/A "pkg_plans": [ pkg.client.pkgplan.PkgPlan ],
2708N/A "release_notes": (bool, []),
2690N/A "removal_actions": [ _ActionPlan ],
2690N/A "removed_groups": { str: pkg.fmri.PkgFmri },
2690N/A "removed_users": { str: pkg.fmri.PkgFmri },
2690N/A "update_actions": [ _ActionPlan ],
2690N/A }
2690N/A
2690N/A __state__commonize = frozenset([
2690N/A pkg.actions.generic.NSG,
2690N/A pkg.client.pkgplan.PkgPlan,
2690N/A pkg.fmri.PkgFmri,
2690N/A ])
2690N/A
2690N/A def __init__(self, op=None):
2690N/A self.state = UNEVALUATED
2690N/A self._op = op
2690N/A
2690N/A #
2690N/A # Properties set when state >= EVALUATED_PKGS
2690N/A #
2690N/A self._image_lm = None
2690N/A self._cfg_mediators = {}
2690N/A self._varcets_change = False
2690N/A self._new_variants = None
2925N/A self._old_facets = None
2690N/A self._new_facets = None
2925N/A self._facet_change = False
2925N/A self._masked_facet_change = False
2690N/A self._new_mediators = collections.defaultdict(set)
2690N/A self._mediators_change = False
3366N/A self._new_avoid_obs = (set(), set(), set())
2690N/A self._fmri_changes = [] # install (None, fmri)
2690N/A # remove (oldfmri, None)
2690N/A # update (oldfmri, newfmri|oldfmri)
3047N/A self._preserved = {
3047N/A "moved": [],
3047N/A "removed": [],
3047N/A "installed": [],
3047N/A "updated": [],
3047N/A }
2690N/A self._solver_summary = []
2690N/A self._solver_errors = None
2690N/A self.li_attach = False
2690N/A self.li_ppkgs = frozenset()
2690N/A self.li_ppubs = None
2690N/A self.li_props = {}
2925N/A self._li_pkg_updates = True
3336N/A self._item_msgs = collections.defaultdict(dict)
2690N/A
2690N/A #
2690N/A # Properties set when state >= EVALUATED_OK
2690N/A #
2690N/A # raw actions
2690N/A self.pkg_plans = []
2690N/A # merged actions
2690N/A self.removal_actions = []
2690N/A self.update_actions = []
2690N/A self.install_actions = []
2690N/A # smf and other actuators (driver actions get added during
2690N/A # execution stage).
2690N/A self._actuators = pkg.client.actuator.Actuator()
2690N/A # Used to track users and groups that are part of operation.
2690N/A self.added_groups = {}
2690N/A self.added_users = {}
2690N/A self.removed_groups = {}
2690N/A self.removed_users = {}
2708N/A # release notes that are part of this operation
2708N/A self.release_notes = (False, [])
2690N/A # plan properties
2690N/A self._cbytes_added = 0 # size of compressed files
2690N/A self._bytes_added = 0 # size of files added
2690N/A self._need_boot_archive = None
2690N/A # child properties
3025N/A self.child_op_vectors = []
2690N/A self.children_ignored = None
2690N/A self.children_planned = []
2690N/A self.children_nop = []
2690N/A # driver aliases to remove
2690N/A self._rm_aliases = {}
2690N/A
2690N/A #
2690N/A # Properties set when state >= EXECUTED_OK
2690N/A #
2690N/A self._salvaged = []
2708N/A self.release_notes_name = None
2690N/A
2690N/A #
2690N/A # Set by imageplan.set_be_options()
2690N/A #
2690N/A self._backup_be = None
2690N/A self._backup_be_name = None
2690N/A self._new_be = None
2690N/A self._be_name = None
2690N/A self._be_activate = False
2690N/A
2690N/A # Accessed via imageplan.update_index
2690N/A self._update_index = True
2690N/A
2690N/A # stats about the current image
2690N/A self._cbytes_avail = 0 # avail space for downloads
2690N/A self._bytes_avail = 0 # avail space for fs
2690N/A
3014N/A self._act_timed_out = False
3014N/A
3160N/A # Pkg actuators
3160N/A self._pkg_actuators = {}
3160N/A
2690N/A @staticmethod
2690N/A def getstate(obj, je_state=None, reset_volatiles=False):
2690N/A """Returns the serialized state of this object in a format
2690N/A that that can be easily stored using JSON, pickle, etc."""
2828N/A # Access to protected member; pylint: disable=W0212
2690N/A
2690N/A if reset_volatiles:
2690N/A # backup and clear volatiles
2690N/A _bytes_avail = obj._bytes_avail
2690N/A _cbytes_avail = obj._cbytes_avail
2690N/A obj._bytes_avail = obj._cbytes_avail = 0
2690N/A
2690N/A name = PlanDescription.__name__
2690N/A state = pkg.misc.json_encode(name, obj.__dict__,
2690N/A PlanDescription.__state__desc,
2690N/A commonize=PlanDescription.__state__commonize,
2690N/A je_state=je_state)
2690N/A
2690N/A # add a state version encoding identifier
2690N/A state[name] = 0
2690N/A
2690N/A if reset_volatiles:
2690N/A obj._bytes_avail = obj._bytes_avail
2690N/A obj._cbytes_avail = obj._cbytes_avail
2690N/A
2690N/A return state
2690N/A
2690N/A @staticmethod
2690N/A def setstate(obj, state, jd_state=None):
2690N/A """Update the state of this object using previously serialized
2690N/A state obtained via getstate()."""
2828N/A # Access to protected member; pylint: disable=W0212
2690N/A
2690N/A # get the name of the object we're dealing with
2690N/A name = PlanDescription.__name__
2690N/A
2690N/A # version check and delete the encoding identifier
2690N/A assert state[name] == 0
2690N/A del state[name]
2690N/A
2690N/A # decode serialized state into python objects
2690N/A state = pkg.misc.json_decode(name, state,
2690N/A PlanDescription.__state__desc,
2690N/A commonize=PlanDescription.__state__commonize,
2690N/A jd_state=jd_state)
2690N/A
2690N/A # bulk update
2690N/A obj.__dict__.update(state)
2690N/A
2690N/A # clear volatiles
2690N/A obj._cbytes_avail = 0
2690N/A obj._bytes_avail = 0
2690N/A
2690N/A @staticmethod
2690N/A def fromstate(state, jd_state=None):
2690N/A """Allocate a new object using previously serialized state
2690N/A obtained via getstate()."""
2690N/A rv = PlanDescription()
2690N/A PlanDescription.setstate(rv, state, jd_state)
2690N/A return rv
2690N/A
2690N/A def _save(self, fobj, reset_volatiles=False):
2690N/A """Save a json encoded representation of this plan
2690N/A description objects into the specified file object."""
2690N/A
2690N/A state = PlanDescription.getstate(self,
2690N/A reset_volatiles=reset_volatiles)
2690N/A try:
2690N/A fobj.truncate()
2690N/A json.dump(state, fobj, encoding="utf-8")
2690N/A fobj.flush()
3171N/A except OSError as e:
2828N/A # Access to protected member; pylint: disable=W0212
2690N/A raise apx._convert_error(e)
2690N/A
2690N/A del state
2690N/A
2690N/A def _load(self, fobj):
2690N/A """Load a json encoded representation of a plan description
2690N/A from the specified file object."""
2690N/A
2690N/A assert self.state == UNEVALUATED
2690N/A
2690N/A try:
2690N/A fobj.seek(0)
3173N/A state = json.load(fobj, encoding="utf-8",
3173N/A object_hook=pkg.misc.json_hook)
3171N/A except OSError as e:
2828N/A # Access to protected member; pylint: disable=W0212
2690N/A raise apx._convert_error(e)
2690N/A
2690N/A PlanDescription.setstate(self, state)
2690N/A del state
2690N/A
2690N/A def _executed_ok(self):
2690N/A """A private interface used after a plan is successfully
2690N/A invoked to free up memory."""
2690N/A
2690N/A # reduce memory consumption
2690N/A self._fmri_changes = []
3047N/A self._preserved = {}
3014N/A # We have to save the timed_out state.
3014N/A self._act_timed_out = self._actuators.timed_out
2690N/A self._actuators = pkg.client.actuator.Actuator()
2690N/A self.added_groups = {}
2690N/A self.added_users = {}
2690N/A self.removed_groups = {}
2690N/A self.removed_users = {}
2690N/A
2690N/A @property
2690N/A def executed(self):
2690N/A """A boolean indicating if we attempted to execute this
2690N/A plan."""
2690N/A return self.state in [EXECUTED_OK, EXECUTED_ERROR]
2690N/A
2690N/A @property
2690N/A def services(self):
2690N/A """Returns a list of string tuples describing affected services
2690N/A (action, SMF FMRI)."""
2690N/A return sorted(
2690N/A ((str(a), str(smf_fmri))
2690N/A for a, smf_fmri in self._actuators.get_services_list()),
2690N/A key=operator.itemgetter(0, 1)
2690N/A )
2690N/A
2690N/A @property
2690N/A def mediators(self):
2690N/A """Returns a list of three-tuples containing information about
2690N/A the mediators. The first element in the tuple is the name of
2690N/A the mediator. The second element is a tuple containing the
2690N/A original version and source and the new version and source of
2690N/A the mediator. The third element is a tuple containing the
2690N/A original implementation and source and new implementation and
2690N/A source."""
2690N/A
2690N/A ret = []
2690N/A
2690N/A if not self._mediators_change or \
2690N/A (not self._cfg_mediators and not self._new_mediators):
2690N/A return ret
2690N/A
2690N/A def get_mediation(mediators, m):
2828N/A # Missing docstring; pylint: disable=C0111
2690N/A mimpl = mver = mimpl_source = \
2690N/A mver_source = None
2690N/A if m in mediators:
2690N/A mimpl = mediators[m].get(
2690N/A "implementation")
2690N/A mimpl_ver = mediators[m].get(
2690N/A "implementation-version")
2690N/A if mimpl_ver:
2690N/A mimpl_ver = \
2690N/A mimpl_ver.get_short_version()
2690N/A if mimpl and mimpl_ver:
3158N/A mimpl += "(@{0})".format(mimpl_ver)
2690N/A mimpl_source = mediators[m].get(
2690N/A "implementation-source")
2690N/A
2690N/A mver = mediators[m].get("version")
2690N/A if mver:
2690N/A mver = mver.get_short_version()
2690N/A mver_source = mediators[m].get(
2690N/A "version-source")
2690N/A return mimpl, mver, mimpl_source, mver_source
2690N/A
3455N/A for m in sorted(set(self._new_mediators) |
3455N/A set(self._cfg_mediators)):
2690N/A orig_impl, orig_ver, orig_impl_source, \
2690N/A orig_ver_source = get_mediation(
2690N/A self._cfg_mediators, m)
2690N/A new_impl, new_ver, new_impl_source, new_ver_source = \
2690N/A get_mediation(self._new_mediators, m)
2690N/A
2690N/A if orig_ver == new_ver and \
2690N/A orig_ver_source == new_ver_source and \
2690N/A orig_impl == new_impl and \
2690N/A orig_impl_source == new_impl_source:
2690N/A # Mediation not changed.
2690N/A continue
2690N/A
2690N/A out = (m,
2690N/A ((orig_ver, orig_ver_source),
2690N/A (new_ver, new_ver_source)),
2690N/A ((orig_impl, orig_impl_source),
2690N/A (new_impl, new_impl_source)))
2690N/A
2690N/A ret.append(out)
2690N/A
2690N/A return ret
2690N/A
2690N/A def get_mediators(self):
2690N/A """Returns list of strings describing mediator changes."""
2690N/A
2690N/A ret = []
2690N/A for m, ver, impl in sorted(self.mediators):
2690N/A ((orig_ver, orig_ver_source),
2690N/A (new_ver, new_ver_source)) = ver
2690N/A ((orig_impl, orig_impl_source),
2690N/A (new_impl, new_impl_source)) = impl
3158N/A out = "mediator {0}:\n".format(m)
2690N/A if orig_ver and new_ver:
3158N/A out += " version: {0} ({1} default)" \
3158N/A " -> {2} ({3} default)\n".format(orig_ver,
2690N/A orig_ver_source, new_ver, new_ver_source)
2690N/A elif orig_ver:
3158N/A out += " version: {0} ({1} default)" \
3158N/A " -> None\n".format(orig_ver,
3158N/A orig_ver_source)
2690N/A elif new_ver:
2690N/A out += " version: None -> " \
3158N/A "{0} ({1} default)\n".format(new_ver,
2690N/A new_ver_source)
2690N/A
2690N/A if orig_impl and new_impl:
3158N/A out += " implementation: {0} ({1} default)" \
3158N/A " -> {2} ({3} default)\n".format(orig_impl,
2690N/A orig_impl_source, new_impl, new_impl_source)
2690N/A elif orig_impl:
3158N/A out += " implementation: {0} ({1} default)" \
3158N/A " -> None\n".format(orig_impl,
3158N/A orig_impl_source)
2690N/A elif new_impl:
2690N/A out += " implementation: None -> " \
3158N/A "{0} ({1} default)\n".format(new_impl,
2690N/A new_impl_source)
2690N/A ret.append(out)
2690N/A return ret
2690N/A
2690N/A @property
2690N/A def plan_desc(self):
2690N/A """Get the proposed fmri changes."""
2690N/A return self._fmri_changes
2690N/A
2690N/A @property
2690N/A def salvaged(self):
2690N/A """A list of tuples of items that were salvaged during plan
2690N/A execution. Each tuple is of the form (original_path,
2690N/A salvage_path). Where 'original_path' is the path of the item
2690N/A before it was salvaged, and 'salvage_path' is where the item was
2690N/A moved to. This property is only valid after plan execution
2690N/A has completed."""
2690N/A return self._salvaged
2690N/A
2690N/A @property
2690N/A def varcets(self):
2925N/A """Returns a tuple of two lists containing the facet and
2925N/A variant changes in this plan.
2925N/A
2925N/A The variant list contains tuples with the following format:
2925N/A
2925N/A (<variant>, <new-value>)
2925N/A
2925N/A The facet list contains tuples with the following format:
2925N/A
2925N/A (<facet>, <new-value>, <old-value>, <source>,
2925N/A <new-masked>, <old-masked>)
2925N/A
2925N/A """
2925N/A
2690N/A vs = []
2690N/A if self._new_variants:
3234N/A vs = list(self._new_variants.items())
2925N/A
2925N/A # sort results by variant name
2925N/A vs.sort(key=lambda x: x[0])
2925N/A
2690N/A fs = []
2925N/A if self._new_facets is None:
2925N/A return (vs, fs)
2925N/A
2925N/A # create new dictionaries that index facets by name and
2925N/A # source:
2925N/A # dict[(<facet, src>)] = (<value>, <masked>)
2925N/A old_facets = dict([
2925N/A ((f, src), (v, masked))
3455N/A # not-an-iterable self._old_facets;
3455N/A # pylint: disable=E1133
2925N/A for f in self._old_facets
2925N/A # W0212 Access to a protected member
2925N/A # pylint: disable=W0212
2925N/A for v, src, masked in self._old_facets._src_values(f)
2925N/A ])
2925N/A new_facets = dict([
2925N/A ((f, src), (v, masked))
3455N/A # not-an-iterable self._new_facets;
3455N/A # pylint: disable=E1133
2925N/A for f in self._new_facets
2925N/A # W0212 Access to a protected member
2925N/A # pylint: disable=W0212
2925N/A for v, src, masked in self._new_facets._src_values(f)
2925N/A ])
2925N/A
2925N/A # check for removed facets
2925N/A for f, src in set(old_facets) - set(new_facets):
2925N/A v, masked = old_facets[f, src]
2925N/A fs.append((f, None, v, src, masked, False))
2925N/A
2925N/A # check for added facets
2925N/A for f, src in set(new_facets) - set(old_facets):
2925N/A v, masked = new_facets[f, src]
2925N/A fs.append((f, v, None, src, False, masked))
2925N/A
2925N/A # check for changing facets
2925N/A for f, src in set(old_facets) & set(new_facets):
2925N/A if old_facets[f, src] == new_facets[f, src]:
2925N/A continue
2925N/A v_old, m_old = old_facets[f, src]
2925N/A v_new, m_new = new_facets[f, src]
2925N/A fs.append((f, v_new, v_old, src, m_old, m_new))
2925N/A
2925N/A # sort results by facet name
2925N/A fs.sort(key=lambda x: x[0])
2925N/A
2690N/A return (vs, fs)
2690N/A
2690N/A def get_varcets(self):
2690N/A """Returns a formatted list of strings representing the
2690N/A variant/facet changes in this plan"""
2690N/A vs, fs = self.varcets
2925N/A rv = [
3158N/A "variant {0}: {1}".format(name[8:], val)
2910N/A for (name, val) in vs
2925N/A ]
2925N/A masked_str = _(" (masked)")
2925N/A for name, v_new, v_old, src, m_old, m_new in fs:
2925N/A m_old = m_old and masked_str or ""
2925N/A m_new = m_new and masked_str or ""
3158N/A msg = " facet {0} ({1}): {2}{3} -> {4}{5}".format(
3158N/A name[6:], src, v_old, m_old, v_new, m_new)
2925N/A rv.append(msg)
2925N/A return rv
2690N/A
2690N/A def get_changes(self):
3047N/A """A generator function that yields tuples of PackageInfo
2690N/A objects of the form (src_pi, dest_pi).
2690N/A
2690N/A If 'src_pi' is None, then 'dest_pi' is the package being
2690N/A installed.
2690N/A
2690N/A If 'src_pi' is not None, and 'dest_pi' is None, 'src_pi'
2690N/A is the package being removed.
2690N/A
2690N/A If 'src_pi' is not None, and 'dest_pi' is not None,
2690N/A then 'src_pi' is the original version of the package,
2690N/A and 'dest_pi' is the new version of the package it is
2690N/A being upgraded to."""
2690N/A
2910N/A key = operator.attrgetter("origin_fmri", "destination_fmri")
2910N/A for pp in sorted(self.pkg_plans, key=key):
2910N/A sfmri = pp.origin_fmri
2910N/A dfmri = pp.destination_fmri
2910N/A if sfmri == dfmri:
2910N/A sinfo = dinfo = PackageInfo.build_from_fmri(
2910N/A sfmri)
2910N/A else:
2910N/A sinfo = PackageInfo.build_from_fmri(sfmri)
2910N/A dinfo = PackageInfo.build_from_fmri(dfmri)
2910N/A yield (sinfo, dinfo)
2690N/A
3047N/A def get_editable_changes(self):
3047N/A """This function returns a tuple of generators that yield tuples
3047N/A of the form (src, dest) of the preserved ("editable") files that
3047N/A will be installed, moved, removed, or updated. The returned
3047N/A list of generators is (moved, removed, installed, updated)."""
3047N/A
3047N/A return (
3047N/A (entry for entry in self._preserved["moved"]),
3047N/A ((entry[0], None) for entry in self._preserved["removed"]),
3047N/A ((None, entry[0])
3047N/A for entry in self._preserved["installed"]),
3047N/A ((entry[0], entry[0])
3047N/A for entry in self._preserved["updated"]),
3047N/A )
3047N/A
2690N/A def get_actions(self):
2690N/A """A generator function that yields action change descriptions
2690N/A in the order they will be performed."""
2690N/A
2828N/A # Unused variable '%s'; pylint: disable=W0612
2690N/A for pplan, o_act, d_act in itertools.chain(
2690N/A self.removal_actions,
2690N/A self.update_actions,
2690N/A self.install_actions):
2828N/A # pylint: enable=W0612
3158N/A yield "{0} -> {1}".format(o_act, d_act)
2690N/A
2708N/A def has_release_notes(self):
2708N/A """True if there are release notes for this plan"""
2708N/A return bool(self.release_notes[1])
2708N/A
2708N/A def must_display_notes(self):
2708N/A """True if the release notes must be displayed"""
2708N/A return self.release_notes[0]
2708N/A
2708N/A def get_release_notes(self):
2708N/A """A generator that returns the release notes for this plan"""
2708N/A for notes in self.release_notes[1]:
2708N/A yield notes
2708N/A
2690N/A def get_licenses(self, pfmri=None):
2690N/A """A generator function that yields information about the
2690N/A licenses related to the current plan in tuples of the form
2690N/A (dest_fmri, src, dest, accepted, displayed) for the given
2690N/A package FMRI or all packages in the plan. This is only
2690N/A available for licenses that are being installed or updated.
2690N/A
2690N/A 'dest_fmri' is the FMRI of the package being installed.
2690N/A
2690N/A 'src' is a LicenseInfo object if the license of the related
2690N/A package is being updated; otherwise it is None.
2690N/A
2690N/A 'dest' is the LicenseInfo object for the license that is being
2690N/A installed.
2690N/A
2690N/A 'accepted' is a boolean value indicating that the license has
2690N/A been marked as accepted for the current plan.
2690N/A
2690N/A 'displayed' is a boolean value indicating that the license has
2690N/A been marked as displayed for the current plan."""
2690N/A
2690N/A for pp in self.pkg_plans:
2690N/A dfmri = pp.destination_fmri
2690N/A if pfmri and dfmri != pfmri:
2690N/A continue
2690N/A
2828N/A # Unused variable; pylint: disable=W0612
2690N/A for lid, entry in pp.get_licenses():
2690N/A src = entry["src"]
2690N/A src_li = None
2690N/A if src:
2690N/A src_li = LicenseInfo(pp.origin_fmri,
2690N/A src, img=pp.image)
2690N/A
2690N/A dest = entry["dest"]
2690N/A dest_li = None
2690N/A if dest:
2690N/A dest_li = LicenseInfo(
2690N/A pp.destination_fmri, dest,
2690N/A img=pp.image)
2690N/A
2690N/A yield (pp.destination_fmri, src_li, dest_li,
2690N/A entry["accepted"], entry["displayed"])
2690N/A
2690N/A if pfmri:
2690N/A break
2690N/A
2690N/A def get_solver_errors(self):
2690N/A """Returns a list of strings for all FMRIs evaluated by the
2690N/A solver explaining why they were rejected. (All packages
2690N/A found in solver's trim database.) Only available if
2690N/A DebugValues["plan"] was set when the plan was created.
2690N/A """
2690N/A
2690N/A assert self.state >= EVALUATED_PKGS, \
3158N/A "{0} >= {1}".format(self.state, EVALUATED_PKGS)
2690N/A
2690N/A # in case this operation doesn't use solver
2690N/A if self._solver_errors is None:
2690N/A return []
2690N/A
2690N/A return self._solver_errors
2690N/A
3336N/A def get_parsable_plan(self, parsable_version, child_images=None,
3336N/A api_inst=None):
3336N/A """Display the parsable version of the plan."""
3336N/A
3336N/A assert parsable_version == 0, \
3336N/A "parsable_version was {0!r}".format(parsable_version)
3336N/A # Set the default values.
3336N/A added_fmris = []
3336N/A removed_fmris = []
3336N/A changed_fmris = []
3336N/A affected_fmris = []
3336N/A backup_be_created = False
3336N/A new_be_created = False
3336N/A backup_be_name = None
3336N/A be_name = None
3336N/A boot_archive_rebuilt = False
3336N/A be_activated = True
3336N/A space_available = None
3336N/A space_required = None
3336N/A facets_changed = []
3336N/A variants_changed = []
3336N/A services_affected = []
3336N/A mediators_changed = []
3336N/A editables_changed = []
3336N/A licenses = []
3336N/A
3336N/A if child_images is None:
3336N/A child_images = []
3336N/A release_notes = []
3336N/A if self:
3336N/A for rem, add in self.get_changes():
3336N/A assert rem is not None or add is not None
3336N/A if rem is not None and add is not None:
3336N/A # Lists of lists are used here becuase
3336N/A # json will convert lists of tuples
3336N/A # into lists of lists anyway.
3336N/A if rem.fmri == add.fmri:
3336N/A affected_fmris.append(str(rem))
3336N/A else:
3336N/A changed_fmris.append(
3336N/A [str(rem), str(add)])
3336N/A elif rem is not None:
3336N/A removed_fmris.append(str(rem))
3336N/A else:
3336N/A added_fmris.append(str(add))
3336N/A variants_changed, facets_changed = self.varcets
3336N/A backup_be_created = self.backup_be
3336N/A new_be_created = self.new_be
3336N/A backup_be_name = self.backup_be_name
3336N/A be_name = self.be_name
3336N/A boot_archive_rebuilt = self.update_boot_archive
3336N/A be_activated = self.activate_be
3336N/A space_available = self.bytes_avail
3336N/A space_required = self.bytes_added
3336N/A services_affected = self.services
3336N/A mediators_changed = self.mediators
3336N/A
3336N/A emoved, eremoved, einstalled, eupdated = \
3336N/A self.get_editable_changes()
3336N/A
3336N/A # Lists of lists are used here to ensure a consistent
3336N/A # ordering and because tuples will be converted to
3336N/A # lists anyway; a dictionary would be more logical for
3336N/A # the top level entries, but would make testing more
3336N/A # difficult and this is a small, known set anyway.
3336N/A emoved = [[e for e in entry] for entry in emoved]
3336N/A eremoved = [src for (src, dest) in eremoved]
3336N/A einstalled = [dest for (src, dest) in einstalled]
3336N/A eupdated = [dest for (src, dest) in eupdated]
3336N/A if emoved:
3336N/A editables_changed.append(["moved", emoved])
3336N/A if eremoved:
3336N/A editables_changed.append(["removed", eremoved])
3336N/A if einstalled:
3336N/A editables_changed.append(["installed",
3336N/A einstalled])
3336N/A if eupdated:
3336N/A editables_changed.append(["updated", eupdated])
3336N/A
3336N/A for n in self.get_release_notes():
3336N/A release_notes.append(n)
3336N/A
3336N/A for dfmri, src_li, dest_li, dummy_acc, dummy_disp in \
3336N/A self.get_licenses():
3339N/A src_tup = ()
3336N/A if src_li:
3336N/A li_txt = pkg.misc.decode(
3336N/A src_li.get_text())
3336N/A src_tup = (str(src_li.fmri),
3336N/A src_li.license, li_txt,
3336N/A src_li.must_accept,
3336N/A src_li.must_display)
3339N/A dest_tup = ()
3336N/A if dest_li:
3336N/A li_txt = pkg.misc.decode(
3336N/A dest_li.get_text())
3336N/A dest_tup = (str(dest_li.fmri),
3336N/A dest_li.license, li_txt,
3336N/A dest_li.must_accept,
3336N/A dest_li.must_display)
3336N/A licenses.append(
3336N/A (str(dfmri), src_tup, dest_tup))
3336N/A
3336N/A # If api_inst is set, mark licenses as
3336N/A # displayed.
3336N/A if api_inst:
3336N/A api_inst.set_plan_license_status(dfmri,
3336N/A dest_li.license, displayed=True)
3336N/A
3336N/A # The image name for the parent image is always None. If this
3336N/A # image is a child image, then the image name will be set when
3336N/A # the parent image processes this dictionary.
3336N/A ret = {
3336N/A "activate-be": be_activated,
3336N/A "add-packages": sorted(added_fmris),
3336N/A "affect-packages": sorted(affected_fmris),
3336N/A "affect-services": sorted(services_affected),
3336N/A "backup-be-name": backup_be_name,
3336N/A "be-name": be_name,
3336N/A "boot-archive-rebuild": boot_archive_rebuilt,
3336N/A "change-facets": sorted(facets_changed),
3336N/A "change-editables": editables_changed,
3336N/A "change-mediators": sorted(mediators_changed),
3336N/A "change-packages": sorted(changed_fmris),
3336N/A "change-variants": sorted(variants_changed),
3336N/A "child-images": child_images,
3336N/A "create-backup-be": backup_be_created,
3336N/A "create-new-be": new_be_created,
3336N/A "image-name": None,
3336N/A "item-messages": self.get_parsable_item_messages(),
3339N/A "licenses": sorted(licenses,
3339N/A key=lambda x: (x[0], x[1], x[2])),
3336N/A "release-notes": release_notes,
3336N/A "remove-packages": sorted(removed_fmris),
3336N/A "space-available": space_available,
3336N/A "space-required": space_required,
3336N/A "version": parsable_version
3336N/A }
3336N/A return ret
3336N/A
3336N/A def get_parsable_item_messages(self):
3336N/A """Return parsable item messages."""
3336N/A return self._item_msgs
3336N/A
3336N/A def add_item_message(self, item_id, msg_time, msg_level, msg_text,
3336N/A msg_type=MSG_GENERAL, parent=None):
3110N/A """Add a new message with its time, type and text for an
3110N/A item."""
3336N/A if parent:
3336N/A item_key = parent
3336N/A sub_item = item_id
3336N/A else:
3336N/A item_key = item_id
3336N/A sub_item = "messages"
3452N/A if self.state >= PREEXECUTED_OK:
3452N/A msg_stage = OP_STAGE_EXEC
3452N/A elif self.state >= EVALUATED_OK:
3452N/A msg_stage = OP_STAGE_PREP
3452N/A else:
3452N/A msg_stage = OP_STAGE_PLAN
3336N/A # First level messaging looks like:
3336N/A # {"item_id": {"messages": [msg_payload ...]}}
3336N/A # Second level messaging looks like:
3336N/A # {"item_id": {"sub_item_id": [msg_payload ...]}}.
3336N/A msg_payload = {"msg_time": msg_time,
3336N/A "msg_level": msg_level,
3336N/A "msg_type": msg_type,
3452N/A "msg_text": msg_text,
3452N/A "msg_stage": msg_stage}
3336N/A self._item_msgs[item_key].setdefault(sub_item,
3336N/A []).append(msg_payload)
3110N/A
3336N/A def extend_item_messages(self, item_id, messages, parent=None):
3110N/A """Add new messages to an item."""
3336N/A if parent:
3336N/A item_key = parent
3336N/A sub_item = item_id
3336N/A else:
3336N/A item_key = item_id
3336N/A sub_item = "messages"
3336N/A self._item_msgs[item_key].setdefault(sub_item, []).extend(
3336N/A messages)
3336N/A
3336N/A @staticmethod
3336N/A def __msg_dict2list(msg):
3336N/A """Convert a message dictionary to a list."""
3336N/A return [msg["msg_time"], msg["msg_level"], msg["msg_type"],
3336N/A msg["msg_text"]]
3110N/A
3452N/A def __gen_ordered_msg(self, stages):
3452N/A """Generate ordered messages."""
3452N/A ordered_list = []
3452N/A for item_id in self._item_msgs:
3452N/A # To make the first level messages come
3452N/A # relatively earlier.
3452N/A if "messages" in self._item_msgs[item_id]:
3452N/A for msg in self._item_msgs[item_id]["messages"]:
3452N/A if (stages is not None and
3452N/A msg["msg_stage"] not in stages):
3452N/A continue
3452N/A ordered_list.append([item_id, None] + \
3452N/A PlanDescription. \
3452N/A __msg_dict2list(msg))
3452N/A for si, si_list in six.iteritems(
3452N/A self._item_msgs[item_id]):
3452N/A if si == "messages":
3452N/A continue
3452N/A for msg in si_list:
3452N/A if (stages is not None and
3452N/A msg["msg_stage"] not in stages):
3452N/A continue
3452N/A ordered_list.append([si, item_id] + \
3452N/A PlanDescription. \
3452N/A __msg_dict2list(msg))
3452N/A for entry in sorted(ordered_list, key=operator.itemgetter(2)):
3452N/A yield entry
3452N/A
3452N/A def __gen_unordered_msg(self, stages):
3452N/A """Generate unordered messages."""
3452N/A for item_id in self._item_msgs:
3452N/A for si, si_list in six.iteritems(
3452N/A self._item_msgs[item_id]):
3452N/A if si == "messages":
3452N/A iid = item_id
3452N/A pid = None
3452N/A else:
3452N/A iid = si
3452N/A pid = item_id
3452N/A for mp in si_list:
3452N/A if (stages is not None and
3452N/A mp["msg_stage"] not in stages):
3452N/A continue
3452N/A yield([iid, pid] + \
3452N/A PlanDescription.__msg_dict2list(mp))
3452N/A
3452N/A def gen_item_messages(self, ordered=False, stages=None):
3146N/A """Return all item messages.
3146N/A
3146N/A 'ordered' is an optional boolean value that indicates that
3253N/A item messages will be sorted by msg_time. If False, item
3452N/A messages will be in an arbitrary order.
3452N/A
3452N/A 'stages' is an optional list or set of the stages of messages
3452N/A to return."""
3146N/A
3146N/A if ordered:
3452N/A return self.__gen_ordered_msg(stages)
3146N/A else:
3452N/A return self.__gen_unordered_msg(stages)
3110N/A
3014N/A def set_actuator_timeout(self, timeout):
3014N/A """Set timeout for synchronous actuators."""
3014N/A assert type(timeout) == int, "Actuator timeout must be an "\
3014N/A "integer."
3014N/A self._actuators.set_timeout(timeout)
3014N/A
3160N/A def add_pkg_actuator(self, trigger_pkg, exec_op, cpkg):
3160N/A """Add a pkg actuator to the plan. The internal dictionary looks
3160N/A like this:
3160N/A { trigger_pkg: {
3160N/A exec_op : [ changed pkg, ... ],
3160N/A ...
3160N/A },
3160N/A ...
3160N/A }
3160N/A """
3160N/A
3160N/A if trigger_pkg in self._pkg_actuators:
3160N/A if exec_op in self._pkg_actuators[trigger_pkg]:
3160N/A self._pkg_actuators[trigger_pkg][
3160N/A exec_op].append(cpkg)
3160N/A self._pkg_actuators[trigger_pkg][exec_op].sort()
3160N/A else:
3160N/A self._pkg_actuators[trigger_pkg][exec_op] = \
3160N/A [cpkg]
3160N/A else:
3160N/A self._pkg_actuators[trigger_pkg] = {exec_op: [cpkg]}
3160N/A
3160N/A def gen_pkg_actuators(self):
3160N/A """Pkg actuators which got triggered by operation."""
3160N/A for trigger_pkg in sorted(self._pkg_actuators):
3160N/A yield (trigger_pkg, self._pkg_actuators[trigger_pkg])
3160N/A
3014N/A @property
3014N/A def actuator_timed_out(self):
3014N/A """Indicates that a synchronous actuator timed out."""
3014N/A return self._act_timed_out
3014N/A
2690N/A @property
2690N/A def plan_type(self):
2690N/A """Return the type of plan that was created (ex:
2690N/A API_OP_UPDATE)."""
2690N/A return self._op
2690N/A
2690N/A @property
2690N/A def update_index(self):
2690N/A """Boolean indicating if indexes will be updated as part of an
2690N/A image-modifying operation."""
2690N/A return self._update_index
2690N/A
2690N/A @property
2690N/A def backup_be(self):
2690N/A """Either None, True, or False. If None then executing this
2690N/A plan may create a backup BE. If False, then executing this
2690N/A plan will not create a backup BE. If True, then executing
2690N/A this plan will create a backup BE."""
2690N/A return self._backup_be
2690N/A
2690N/A @property
2690N/A def be_name(self):
2690N/A """The name of a new BE that will be created if this plan is
2690N/A executed."""
2690N/A return self._be_name
2690N/A
2690N/A @property
2690N/A def backup_be_name(self):
2690N/A """The name of a new backup BE that will be created if this
2690N/A plan is executed."""
2690N/A return self._backup_be_name
2690N/A
2690N/A @property
2690N/A def activate_be(self):
2690N/A """A boolean value indicating whether any new boot environment
2690N/A will be set active on next boot."""
2690N/A return self._be_activate
2690N/A
2690N/A @property
2690N/A def reboot_needed(self):
2690N/A """A boolean value indicating that execution of the plan will
2690N/A require a restart of the system to take effect if the target
2690N/A image is an existing boot environment."""
2690N/A return self._actuators.reboot_needed()
2690N/A
2690N/A @property
2690N/A def new_be(self):
2690N/A """A boolean value indicating that execution of the plan will
2690N/A take place in a clone of the current live environment"""
2690N/A return self._new_be
2690N/A
2690N/A @property
2690N/A def update_boot_archive(self):
2690N/A """A boolean value indicating whether or not the boot archive
2690N/A will be rebuilt"""
2690N/A return self._need_boot_archive
2690N/A
2690N/A @property
2690N/A def bytes_added(self):
2690N/A """Estimated number of bytes added"""
2690N/A return self._bytes_added
2690N/A
2690N/A @property
2690N/A def cbytes_added(self):
2690N/A """Estimated number of download cache bytes added"""
2690N/A return self._cbytes_added
2690N/A
2690N/A @property
2690N/A def bytes_avail(self):
2690N/A """Estimated number of bytes available in image /"""
2690N/A return self._bytes_avail
2690N/A
2690N/A @property
2690N/A def cbytes_avail(self):
2690N/A """Estimated number of bytes available in download cache"""
2690N/A return self._cbytes_avail
2925N/A
2925N/A @property
2925N/A def new_facets(self):
2925N/A """If facets are changing, this is the new set of facets being
2925N/A applied."""
2925N/A if self._new_facets is None:
2925N/A return None
2925N/A return pkg.facet.Facets(self._new_facets)