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