1505N/A#!/usr/bin/python
1505N/A#
1505N/A# CDDL HEADER START
1505N/A#
1505N/A# The contents of this file are subject to the terms of the
1505N/A# Common Development and Distribution License (the "License").
1505N/A# You may not use this file except in compliance with the License.
1505N/A#
1505N/A# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
1505N/A# or http://www.opensolaris.org/os/licensing.
1505N/A# See the License for the specific language governing permissions
1505N/A# and limitations under the License.
1505N/A#
1505N/A# When distributing Covered Code, include this CDDL HEADER in each
1505N/A# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
1505N/A# If applicable, add the following below this CDDL HEADER, with the
1505N/A# fields enclosed by brackets "[]" replaced with your own identifying
1505N/A# information: Portions Copyright [yyyy] [name of copyright owner]
1505N/A#
1505N/A# CDDL HEADER END
1505N/A#
1505N/A
2608N/A#
3339N/A# Copyright (c) 2007, 2016, Oracle and/or its affiliates. All rights reserved.
2608N/A#
1505N/A
1505N/A# basic facet support
1505N/A
3234N/Aimport fnmatch
3234N/Aimport re
3234N/Aimport six
3234N/Aimport types
3245N/Afrom functools import cmp_to_key
3234N/A
3245N/Aimport pkg.misc as misc
2639N/Afrom pkg._varcet import _allow_facet
2925N/Afrom pkg.misc import EmptyI, ImmutableDict
1505N/A
1505N/Aclass Facets(dict):
2690N/A # store information on facets; subclass dict
1505N/A # and maintain ordered list of keys sorted
1505N/A # by length.
1505N/A
2925N/A # subclass __getitem__ so that queries w/
1505N/A # actual facets find match
1505N/A
2925N/A #
2931N/A # For image planning purposes and to be able to compare facet objects
2931N/A # deterministically, facets must be sorted. They are first sorted by
2931N/A # source (more details below), then by length, then lexically.
2931N/A #
2925N/A # Facets can come from three different sources.
2925N/A #
2925N/A # SYSTEM facets are facets whose values are assigned by the system.
2925N/A # These are usually facets defined in packages which are not set in an
2925N/A # image, and the value assigned by the system is always true. These
2925N/A # facets will usually never be found in a Facets dictionary. (Facets
2925N/A # dictionaries only contain facets which are explicitly set.)
2925N/A #
2925N/A # LOCAL facets are facets which have been set locally in an image
2925N/A # using pkg(1) or the pkg api. Explicitly set LOCAL facets are stored
2925N/A # in Facets.__local. Facets which are not explicitly set but match an
2925N/A # explicitly set LOCAL facet glob pattern are also considered to be
2925N/A # LOCAL.
2925N/A #
2925N/A # PARENT facets are facets which are inherited from a parent image.
2925N/A # they are managed internally by the packaging subsystem. Explicitly
2925N/A # inherited facets are stored in Facets.__inherited. Facets which are
2925N/A # not explicitly set but match an explicitly set PARENT facet glob
2925N/A # pattern are also considered to be PARENT.
2925N/A #
2925N/A # When evaluating facets, all PARENT facets are evaluated before LOCAL
2925N/A # facets. This is done by ensuring that all PARENT facets come before
2925N/A # any LOCAL facets in __keylist. This is done because PARENT facets
2925N/A # exist to propagate faceted dependencies between linked images, which
2925N/A # is needed to ensure the solver can run successfully. ie, if a
2925N/A # parent image relaxes dependencies via facet version-locks, then the
2925N/A # child needs to inherit those facets since otherwise it is more
2925N/A # constrained in possible solutions than it's parent and likely won't
2925N/A # be able to plan an update that keeps it in sync with it's parent.
2925N/A #
2925N/A # Sine PARENT facets take priority over LOCAL facets, it's possible to
2925N/A # have conflicts between the two. In the case where a facet is both
2925N/A # inherited and set locally, both values are preserved, but the
2925N/A # inherited value masks the local value. Users can list and update
2925N/A # local values while they are masked using pkg(1), but as long as the
2925N/A # values are masked they will not affect image planning operations.
2925N/A # Once an inherited facet that masks a local facet is removed, the
2925N/A # local facet will be restored.
2925N/A #
2925N/A
2925N/A FACET_SRC_SYSTEM = "system"
2925N/A FACET_SRC_LOCAL = "local"
2925N/A FACET_SRC_PARENT = "parent"
2925N/A
1505N/A def __init__(self, init=EmptyI):
1505N/A dict.__init__(self)
1505N/A self.__keylist = []
2608N/A self.__res = {}
2925N/A self.__local = {}
2925N/A self.__local_ro = None
2925N/A self.__inherited = {}
2925N/A self.__inherited_ro = None
2925N/A
2925N/A # initialize ourselves
2925N/A self.update(init)
1505N/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."""
2925N/A
2925N/A return [
3274N/A [misc.force_text(k), v, True]
3234N/A for k, v in six.iteritems(obj.__inherited)
2925N/A ] + [
3274N/A [misc.force_text(k), v, False]
3234N/A for k, v in six.iteritems(obj.__local)
2925N/A ]
2690N/A
2690N/A @staticmethod
2690N/A def fromstate(state, jd_state=None):
2690N/A """Update the state of this object using previously serialized
2690N/A state obtained via getstate()."""
2925N/A
2925N/A rv = Facets()
2925N/A for k, v, inhertited in state:
2925N/A if not inhertited:
2925N/A rv[k] = v
2925N/A else:
2925N/A rv._set_inherited(k, v)
2925N/A return rv
2925N/A
2925N/A def _cmp_priority(self, other):
2925N/A """Compare the facet match priority of two Facets objects.
2925N/A Since the match priority of a Facets object is dependent upon
2925N/A facet sources (local vs parent) and names, we're essentially
2925N/A ensuring that both objects have the same set of facet sources
2925N/A and names."""
2925N/A
2925N/A assert type(other) is Facets
3245N/A return misc.cmp(self.__keylist, other.__keylist)
2925N/A
2925N/A def _cmp_values(self, other):
2925N/A """Compare the facet values of two Facets objects. This
2925N/A comparison ignores any masked values."""
2925N/A
2925N/A assert type(other) is Facets
3245N/A return misc.cmp(self, other)
2925N/A
2925N/A def _cmp_all_values(self, other):
2925N/A """Compare all the facet values of two Facets objects. This
2925N/A comparison takes masked values into account."""
2925N/A
2925N/A assert type(other) is Facets
3245N/A rv = misc.cmp(self.__inherited, other.__inherited)
2925N/A if rv == 0:
3245N/A rv = misc.cmp(self.__local, other.__local)
2925N/A return rv
2925N/A
3253N/A # this __cmp__ is used as a helper function for the rich comparison
3253N/A # methods.
3253N/A # __cmp__ defined; pylint: disable=W1630
2925N/A def __cmp__(self, other):
2925N/A """Compare two Facets objects. This comparison takes masked
2925N/A values into account."""
2925N/A
2925N/A # check if we're getting compared against something other than
2925N/A # another Factes object.
2925N/A if type(other) is not Facets:
2925N/A return 1
2925N/A
2925N/A # Check for effective facet value changes that could affect
2925N/A # solver computations.
2925N/A rv = self._cmp_values(other)
2925N/A if rv != 0:
2925N/A return rv
2925N/A
2925N/A # Check for facet priority changes that could affect solver
2925N/A # computations. (Priority changes can occur when local or
2925N/A # inherited facets are added or removed.)
2925N/A rv = self._cmp_priority(other)
2925N/A if rv != 0:
2925N/A return rv
2925N/A
2925N/A # There are no outwardly visible facet priority or value
2925N/A # changes that could affect solver computations, but it's
2925N/A # still possible that we're changing the set of local or
2925N/A # inherited facets in a way that doesn't affect solver
2925N/A # computations. For example: we could be adding a local
2925N/A # facet with a value that is masked by an inherited facet, or
2925N/A # having a facet transition from being inherited to being
2925N/A # local without a priority or value change. Check if this is
2925N/A # the case.
2925N/A rv = self._cmp_all_values(other)
2925N/A return rv
2925N/A
3245N/A def __hash__(self):
3245N/A return hash(str(self))
3245N/A
2925N/A def __eq__(self, other):
2925N/A """redefine in terms of __cmp__()"""
2925N/A return (Facets.__cmp__(self, other) == 0)
2925N/A
2925N/A def __ne__(self, other):
2925N/A """redefine in terms of __cmp__()"""
2925N/A return (Facets.__cmp__(self, other) != 0)
2925N/A
2925N/A def __ge__(self, other):
2925N/A """redefine in terms of __cmp__()"""
2925N/A return (Facets.__cmp__(self, other) >= 0)
2925N/A
2925N/A def __gt__(self, other):
2925N/A """redefine in terms of __cmp__()"""
2925N/A return (Facets.__cmp__(self, other) > 0)
2925N/A
2925N/A def __le__(self, other):
2925N/A """redefine in terms of __cmp__()"""
2925N/A return (Facets.__cmp__(self, other) <= 0)
2925N/A
2925N/A def __lt__(self, other):
2925N/A """redefine in terms of __cmp__()"""
2925N/A return (Facets.__cmp__(self, other) < 0)
2690N/A
1505N/A def __repr__(self):
1505N/A s = "<"
2925N/A s += ", ".join([
3158N/A "{0}:{1}".format(k, dict.__getitem__(self, k))
2925N/A for k in self.__keylist
2925N/A ])
1505N/A s += ">"
1505N/A
1505N/A return s
2690N/A
2925N/A def __keylist_sort(self):
2925N/A """Update __keysort, which is used to determine facet matching
2925N/A order. Inherited facets always take priority over local
2925N/A facets so make sure all inherited facets come before local
2931N/A facets in __keylist. All facets from a given source are
2931N/A sorted by length, and facets of equal length are sorted
2931N/A lexically."""
2925N/A
2925N/A def facet_sort(x, y):
2931N/A i = len(y) - len(x)
2931N/A if i != 0:
2931N/A return i
3245N/A return misc.cmp(x, y)
2925N/A
2925N/A self.__keylist = []
2925N/A self.__keylist += sorted([
2925N/A i
2925N/A for i in self
2925N/A if i in self.__inherited
3245N/A ], key=cmp_to_key(facet_sort))
2925N/A self.__keylist += sorted([
2925N/A i
2925N/A for i in self
2925N/A if i not in self.__inherited
3245N/A ], key=cmp_to_key(facet_sort))
2925N/A
2925N/A def __setitem_internal(self, item, value, inherited=False):
1505N/A if not item.startswith("facet."):
3194N/A raise KeyError('key must start with "facet".')
1505N/A
1505N/A if not (value == True or value == False):
3194N/A raise ValueError("value must be boolean")
1505N/A
2925N/A keylist_sort = False
2925N/A if (inherited and item not in self.__inherited) or \
2925N/A (not inherited and item not in self):
2925N/A keylist_sort = True
2925N/A
2925N/A # save the facet in the local or inherited dictionary
2925N/A # clear the corresponding read-only dictionary
2925N/A if inherited:
2925N/A self.__inherited[item] = value
2925N/A self.__inherited_ro = None
2925N/A else:
2925N/A self.__local[item] = value
2925N/A self.__local_ro = None
2925N/A
2925N/A # Inherited facets always take priority over local facets.
2925N/A if inherited or item not in self.__inherited:
2925N/A dict.__setitem__(self, item, value)
2925N/A self.__res[item] = re.compile(fnmatch.translate(item))
2608N/A
2925N/A if keylist_sort:
2925N/A self.__keylist_sort()
1505N/A
2925N/A def __setitem__(self, item, value):
2925N/A """__setitem__ only operates on local facets."""
2925N/A self.__setitem_internal(item, value)
2925N/A
2925N/A def __getitem_internal(self, item):
2925N/A """Implement facet lookup algorithm here
2925N/A
2925N/A Note that _allow_facet bypasses __getitem__ for performance
2925N/A reasons; if __getitem__ changes, _allow_facet in _varcet.c
2925N/A must also be updated.
2925N/A
2925N/A We return a tuple of the form (<key>, <value>) where key is
2925N/A the explicitly set facet name (which may be a glob pattern)
2925N/A that matched the caller specific facet name."""
2925N/A
1505N/A if not item.startswith("facet."):
3194N/A raise KeyError("key must start w/ facet.")
1505N/A
1505N/A if item in self:
2925N/A return item, dict.__getitem__(self, item)
1505N/A for k in self.__keylist:
2608N/A if self.__res[k].match(item):
2925N/A return k, dict.__getitem__(self, k)
2925N/A
3070N/A # The trailing '.' is to encourage namespace usage.
3070N/A if item.startswith("facet.debug.") or \
3070N/A item.startswith("facet.optional."):
3070N/A return None, False # exclude by default
2925N/A return None, True # be inclusive
2925N/A
2925N/A def __getitem__(self, item):
2925N/A return self.__getitem_internal(item)[1]
2925N/A
2925N/A def __delitem_internal(self, item, inherited=False):
2925N/A
2925N/A # check for an attempt to delete an invalid facet
2925N/A if not dict.__contains__(self, item):
3194N/A raise KeyError(item)
2925N/A
2925N/A # check for an attempt to delete an invalid local facet
2925N/A if not inherited and item not in self.__local:
3194N/A raise KeyError(item)
2925N/A
2925N/A # we should never try to delete an invalid inherited facet
2925N/A assert not inherited or item in self.inherited
1505N/A
2925N/A keylist_sort = False
2925N/A if inherited and item in self.__local:
2925N/A # the inherited value was overriding a local value
2925N/A # that should now be exposed
2925N/A dict.__setitem__(self, item, self.__local[item])
2925N/A self.__res[item] = re.compile(fnmatch.translate(item))
2925N/A keylist_sort = True
2925N/A else:
2925N/A # delete the item
2925N/A dict.__delitem__(self, item)
2925N/A del self.__res[item]
2925N/A self.__keylist.remove(item)
2925N/A
2925N/A # delete item from the local or inherited dictionary
2925N/A # clear the corresponding read-only dictionary
2925N/A if inherited:
2925N/A rv = self.__inherited[item]
2925N/A del self.__inherited[item]
2925N/A self.__inherited_ro = None
2925N/A else:
2925N/A rv = self.__local[item]
2925N/A del self.__local[item]
2925N/A self.__local_ro = None
2925N/A
2925N/A if keylist_sort:
2925N/A self.__keylist_sort()
2925N/A return rv
1505N/A
1505N/A def __delitem__(self, item):
2925N/A """__delitem__ only operates on local facets."""
2945N/A self.__delitem_internal(item)
1505N/A
2639N/A # allow_action is provided as a native function (see end of class
2639N/A # declaration).
2639N/A
2925N/A def _set_inherited(self, item, value):
2925N/A """Set an inherited facet."""
2925N/A self.__setitem_internal(item, value, inherited=True)
2925N/A
2925N/A def _clear_inherited(self):
2925N/A """Clear all inherited facet."""
3339N/A for k in list(self.__inherited.keys()):
2925N/A self.__delitem_internal(k, inherited=True)
2925N/A
2925N/A def _action_match(self, act):
2925N/A """Find the subset of facet key/values pairs which match any
2925N/A facets present on an action."""
2925N/A
2925N/A # find all the facets present in the current action
2925N/A action_facets = frozenset([
2925N/A a
2925N/A for a in act.attrs
2925N/A if a.startswith("facet.")
2925N/A ])
2925N/A
2925N/A rv = set()
2925N/A for facet in self.__keylist:
2925N/A if facet in action_facets:
2925N/A # we found a matching facet.
2925N/A rv.add((facet, self[facet]))
2925N/A continue
2925N/A for action_facet in action_facets:
2925N/A if self.__res[facet].match(action_facet):
2925N/A # we found a matching facet.
2925N/A rv.add((facet, self[facet]))
2925N/A break
2925N/A
2925N/A return (frozenset(rv))
2925N/A
2482N/A def pop(self, item, *args, **kwargs):
2925N/A """pop() only operates on local facets."""
2925N/A
2482N/A assert len(args) == 0 or (len(args) == 1 and
2482N/A "default" not in kwargs)
2925N/A
2925N/A if item not in self.__local:
2925N/A # check if the user specified a default value
2925N/A if args:
2925N/A return args[0]
2925N/A elif "default" in kwargs:
2925N/A return kwargs["default"]
2925N/A if len(self) == 0:
3194N/A raise KeyError('pop(): dictionary is empty')
3194N/A raise KeyError(item)
2925N/A
2925N/A return self.__delitem_internal(item, inherited=False)
1505N/A
1505N/A def popitem(self):
2925N/A """popitem() only operates on local facets."""
2925N/A
2925N/A item = None
2925N/A for item, value in self.__local:
2925N/A break
2925N/A
2925N/A if item is None:
3194N/A raise KeyError('popitem(): dictionary is empty')
2925N/A
2925N/A self.__delitem_internal(item)
2925N/A return (item, value)
1505N/A
1505N/A def setdefault(self, item, default=None):
1505N/A if item not in self:
1505N/A self[item] = default
1505N/A return self[item]
1505N/A
1505N/A def update(self, d):
2925N/A if type(d) == Facets:
2925N/A # preserve inherited facets.
3234N/A for k, v in six.iteritems(d.__inherited):
2925N/A self._set_inherited(k, v)
3234N/A for k, v in six.iteritems(d.__local):
2925N/A self[k] = v
2925N/A return
2925N/A
2925N/A for k in d:
2925N/A self[k] = d[k]
1505N/A
1505N/A def keys(self):
1505N/A return self.__keylist[:]
1505N/A
1505N/A def values(self):
1505N/A return [self[k] for k in self.__keylist]
1505N/A
2925N/A def _src_values(self, name):
2925N/A """A facet may be set via multiple sources and hence have
2925N/A multiple values. If there are multiple values for a facet,
2925N/A all but one of those values will be masked. So for a given
2925N/A facet, return a list of tuples of the form (<value>, <src>,
2925N/A <masked>) which represent all currently set values for this
2925N/A facet."""
2925N/A
2925N/A rv = []
2925N/A if name in self.__inherited:
2925N/A src = self.FACET_SRC_PARENT
2925N/A value = self.__inherited[name]
2925N/A masked = False
2925N/A rv.append((value, src, masked))
2925N/A if name in self.__local:
2925N/A src = self.FACET_SRC_LOCAL
2925N/A value = self.__local[name]
2925N/A masked = False
2925N/A if name in self.__inherited:
2925N/A masked = True
2925N/A rv.append((value, src, masked))
2925N/A return rv
2925N/A
1505N/A def items(self):
3339N/A return [a for a in self.iteritems()]
1505N/A
1505N/A def iteritems(self): # return in sorted order for display
1505N/A for k in self.__keylist:
1505N/A yield k, self[k]
1505N/A
1505N/A def copy(self):
1505N/A return Facets(self)
1505N/A
1505N/A def clear(self):
1505N/A self.__keylist = []
2925N/A self.__res = {}
2925N/A self.__local = {}
2925N/A self.__local_ro = None
2925N/A self.__inherited = {}
2925N/A self.__inherited_ro = None
1505N/A dict.clear(self)
1505N/A
2925N/A def _match_src(self, name):
2925N/A """Report the source of a facet value if we were to attempt to
2925N/A look it up in the current Facets object dictionary."""
2925N/A
2925N/A k = self.__getitem_internal(name)[0]
2925N/A if k in self.__inherited:
2925N/A return self.FACET_SRC_PARENT
2925N/A if k in self.__local:
2925N/A return self.FACET_SRC_LOCAL
2925N/A assert k is None and k not in self
2925N/A return self.FACET_SRC_SYSTEM
2925N/A
2925N/A # For convenience, provide callers with direct access to local and
2925N/A # parent facets via cached read-only dictionaries.
2925N/A @property
2925N/A def local(self):
2925N/A if self.__local_ro is None:
2925N/A self.__local_ro = ImmutableDict(self.__local)
2925N/A return self.__local_ro
2925N/A
2925N/A @property
2925N/A def inherited(self):
2925N/A if self.__inherited_ro is None:
2925N/A self.__inherited_ro = ImmutableDict(self.__inherited)
2925N/A return self.__inherited_ro
2925N/A
2925N/A
3339N/A if six.PY3:
3339N/A def allow_action(self, action, publisher=None):
3339N/A return _allow_facet(self, action, publisher=publisher)
3339N/A
3339N/Aif six.PY2:
3339N/A Facets.allow_action = types.MethodType(_allow_facet, None, Facets)