common.py revision 2896
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
#
"""
Linked image module classes.
The following classes for manipulating linked images are defined here:
LinkedImage
LinkedImageChild
The following template classes which linked image plugins should inherit from
are also defined here:
LinkedImagePlugin
LinkedImageChildPlugin
"""
# standard python classes
import collections
import copy
import operator
import os
import select
import simplejson as json
# pkg classes
import pkg.pkgsubprocess
# linked image relationship types (returned by LinkedImage.list_related())
REL_PARENT = "parent"
REL_SELF = "self"
REL_CHILD = "child"
# linked image properties
PROP_NAME = "li-name"
PROP_ALTROOT = "li-altroot"
PROP_PARENT_PATH = "li-parent"
PROP_PATH = "li-path"
PROP_MODEL = "li-model"
PROP_RECURSE = "li-recurse"
prop_values = frozenset([
])
# properties that never get saved
])
# special linked image name values (PROP_NAME)
PV_NAME_NONE = "-"
# linked image model values (PROP_MODEL)
PV_MODEL_PUSH = "push"
PV_MODEL_PULL = "pull"
])
# files which contain linked image data
__DATA_DIR = "linked"
def _li_rvtuple_check(rvtuple):
"""Sanity check a linked image operation return value tuple.
The format of said tuple is:
process return code
LinkedImageException exception (optional)
json dictionary containing planned image changes
"""
# make sure we're using the LI_RVTuple class
# decode the tuple
# rv must be an integer
# any exception returned must be a LinkedImageException
# if specified, p_dict must be a dictionary
# some child return codes should never be associated with an exception
# a p_dict can only be returned if the child returned EXIT_OK
# return the value that was passed in
return rvtuple
def _li_rvdict_check(rvdict):
"""Given a linked image return value dictionary, sanity check all the
entries."""
assert type(k) == LinkedImageName, \
("Unexpected rvdict key: ", k)
# return the value that was passed in
return rvdict
def _li_rvdict_exceptions(rvdict):
"""Given a linked image return value dictionary, return a list of any
exceptions that were encountered while processing children."""
# sanity check rvdict
# get a list of exceptions
return [
]
"""If an exception was encountered while operating on a linked
child then raise that exception. If multiple exceptions were
encountered while operating on multiple children, then bundle
those exceptions together and raise them."""
# get a list of exceptions
# one exception encountered
raise exceptions[0]
if exceptions:
# multiple exceptions encountered
class LinkedImagePlugin(object):
"""This class is a template that all linked image plugins should
inherit from. Linked image plugins derived from this class are
designed to manage linked aspects of the current image (vs managing
linked aspects of a specific child of the current image).
All the interfaces exported by this class and its descendants are
private to the linked image subsystem and should not be called
directly by any other subsystem."""
# functionality flags
# Unused argument; pylint: disable=W0613
"""Initialize a linked image plugin.
'pname' is the name of the plugin class derived from this
base class.
'linked' is the LinkedImage object initializing this plugin.
"""
return
"""Called when the path to the image that we're operating on
is changing. This normally occurs when we clone an image
after we've planned and prepared to do an operation."""
# return value: None
raise NotImplementedError
"""If the linked image plugin is able to detect that we're
operating on an image in an alternate root then return the
path of the alternate root."""
# return value: string or None
raise NotImplementedError
"""Return a list of the child images associated with the
current image."""
# return value: list
raise NotImplementedError
"""Get the linked image properties associated with the
specified child image."""
# return value: dict
raise NotImplementedError
"""Attach the specified child image. This operation should
only affect in-memory state of the current image. It should
not update any persistent on-disk linked image state or access
the child image in any way. This routine should assume that
the linked image properties have already been validated."""
# return value: None
raise NotImplementedError
"""Detach the specified child image. This operation should
only affect in-memory state of the current image. It should
not update any persistent on-disk linked image state or access
the child image in any way."""
# return value: None
raise NotImplementedError
def sync_children_todisk(self):
"""Sync out the in-memory linked image state of this image to
disk."""
# return value: LI_RVTuple()
raise NotImplementedError
class LinkedImageChildPlugin(object):
"""This class is a template that all linked image child plugins should
inherit from. Linked image child plugins derived from this class are
designed to manage linked aspects of children of the current image.
(vs managing linked aspects of the current image itself).
All the interfaces exported by this class and its descendants are
private to the linked image subsystem and should not be called
directly by any other subsystem."""
"""Initialize a linked image child plugin.
'lic' is the LinkedImageChild object initializing this plugin.
"""
return
"""Called before a parent image saves linked image properties
into a child image. Gives the linked image child plugin a
chance to update the properties that will be saved within the
child image."""
# return value: None
raise NotImplementedError
class LinkedImageName(object):
"""A class for naming child linked images. Linked image names are
used for all child images (and only child images), and they encode two
pieces of information. The name of the plugin used to manage the
image and a linked image name. Linked image names have the following
format "<linked_image_plugin>:<linked_image_name>"""
try:
except ValueError:
"""Returns the serialized state of this object in a format
that that can be easily stored using JSON, pickle, etc."""
# Unused argument; pylint: disable=W0613
"""Allocate a new object using previously serialized state
obtained via getstate()."""
# Unused argument; pylint: disable=W0613
return LinkedImageName(state)
if not other:
return 1
if other == PV_NAME_NONE:
return 1
if c != 0:
return c
return c
return False
class LinkedImage(object):
"""A LinkedImage object is used to manage the linked image aspects of
an image. This image could be a child image, a parent image, or both
a parent and child. This object allows for access to linked image
properties and also provides routines that allow operations to be
performed on child images."""
# Properties that a parent image with push children should save locally.
])
# Properties that a pull child image should save locally.
])
# Properties that a parent image with push children should save in
# those children.
])
# make sure there is no invalid overlap
assert not (temporal_props & (
"""Initialize a new LinkedImage object."""
# globals
# variables reset by self.__update_props()
# variables reset by self.__recursion_init()
self.__lic_ignore = None
self.__lic_dict = {}
# variables reset by self._init_root()
self.__path_ppkgs = None
self.__path_prop = None
self.__path_ppubs = None
# initialize with no properties
# initialize linked image plugin objects
# if the image has a path setup, we can load data from it.
"""Get a pointer to the image object associated with this
linked image object."""
def _init_root(self):
"""Called during object initialization and by
image.py`__set_root() to let us know when we're changing the
root location of the image. (The only time we change the root
path is when changes BEs during operations which clone BEs.
So when this happens most our metadata shouldn't actually
change."""
# save the old root image path
old_root = None
# figure out the new root image path
if new_root == "":
# initialize paths for linked image data files
# if this isn't a reset, then load data from the image
if not old_root:
# we're not linked or we're not changing root paths we're done
return
# get the old altroot directory
# update the altroot property
# Tell linked image plugins about the updated paths
# Unused variable 'plugin'; pylint: disable=W0612
# pylint: enable=W0612
# Tell linked image children about the updated paths
"""Internal helper routine used when we want to update any
linked image properties. This routine sanity check the
new properties, updates them, and resets any cached state
that is affected by property values."""
if props == None:
elif props:
# all temporal properties must exist
# update state
"""Perform internal consistency checks for a set of linked
image properties. Don't update any state."""
# if we're not a child image ourselves, then we're done
return props
# make sure PROP_MODEL was specified
# validate the linked image name
try:
except apx.LinkedImageException:
# make sure PROP_MODEL was specified
if PROP_MODEL not in props:
if model not in model_values:
if model == PV_MODEL_PUSH:
if missing:
if model == PV_MODEL_PULL:
if missing:
def __unset_altroot(props):
"""Given a set of linked image properties, strip out any
altroot properties. This involves removing the altroot
component from the image path property. This is normally done
before we write image properties to disk."""
# get the current altroot
# remove it from the image path
if PROP_PARENT_PATH in props:
# remove it from the parent image path
# delete the current altroot
del props[PROP_ALTROOT]
"""Given a set of linked image properties, the image paths
stored within those properties may not match the actual image
paths if we're executing within an alternate root environment.
We try to detect this condition here, and if this situation
occurs we update the linked image paths to reflect the current
image paths and we fabricate a new linked image altroot
property that points to the new path prefix that was
pre-pended to the image paths."""
# we may have to update the parent image path as well
p_path = None
if PROP_PARENT_PATH in props:
if old_root:
# get the old altroot
# remove the altroot from the image paths
if p_path:
# get the new altroot
else:
# update properties with altroot
if p_path:
props[PROP_PARENT_PATH] = \
"""If we're initializing parent linked image properties for
the first time (or if those properties somehow got deleted)
then we need to know if the parent image that we're currently
operating on is located within an alternate root. One way to
do this is to ask our linked image plugins if they can
determine this (the zones linked image plugin usually can
if the image is a global zone)."""
# ask each plugin if we're operating in an alternate root
p_altroots = []
if p_altroot:
if not p_altroots:
# no altroot suggested by plugins
# check for conflicting altroots
# Unused variable; pylint: disable=W0612
# pylint: enable=W0612
]))
# we have an altroot from our plugins
return altroots[0]
# we have conflicting altroots, time to die
"""Fabricate the minimum set of properties required for a
parent image."""
return props
"""Load linked image properties from disk and return them to
the caller. We sanity check the properties, but we don't
update any internal linked image state.
linked image metadata files, or if we should access temporary
versions (which have ".<runid>" appended to them."""
# read the linked image properties from disk
elif path_exists(path):
else:
return None
# make sure there are no saved temporal properties
# convert PROP_NAME into a linked image name obj
try:
except apx.LinkedImageException:
# sanity check our properties
return props
"""Load linked image parent constraints from disk.
Don't update any internal state.
linked image metadata files, or if we should access temporary
versions (which have ".<runid>" appended to them."""
return frozenset([
])
if path_exists(path):
return frozenset([
])
return None
"""Load linked image parent publishers from disk.
Don't update any internal state.
linked image metadata files, or if we should access temporary
versions (which have ".<runid>" appended to them."""
if path_exists(path):
return None
"""Load linked image properties and constraints from disk.
Update the linked image internal state with the loaded data."""
#
# Normally, if we're a parent image we'll have linked image
# properties stored on disk. So load those now.
#
# If no properties are loaded, we may still be a parent image
# that is just missing it's metadata. (oops.) We attempt to
# detect this situation by invoking __isparent(), which will
# ask each child if there are any children. This is a best
# effort attempt, so when we do this we ignore any plugin
# runtime errors since we really want Image object
# initialization to succeed. If we don't have any linked
# image metadata, and we're having runtime errors querying for
# children, then we'll allow initialization here, but any
# subsequent operation that tries to access children will fail
# and the caller will have to specify that they want to ignore
# all children to allow the operation to succeed.
#
# we're not linked
return
if not props:
#
# Oops. We're a parent image with no properties
# stored on disk. Rather than throwing an exception
# try to fabricate up some props with reasonably
# guessed values which the user can subsequently
#
else:
# load parent publisher data. if publisher data is missing
# continue along and we'll just skip the publisher checks,
# it's better than failing and preventing any image updates.
def __validate_prop_recurse(v):
"""Verify property value for PROP_RECURSE."""
return True
return True
return False
"""Validate user supplied linked image attach properties.
Don't update any internal state."""
# make sure that only attach time options have been
# specified, and that they have allowed values.
validate_props = {
}
if model == PV_MODEL_PUSH:
else:
assert model == PV_MODEL_PULL
errs = []
# check each property the user specified.
# did the user specify an allowable property?
if k not in validate_props:
attach_bad_prop=k))
continue
# did the user specify a valid property value?
if not validate_props[k](v):
attach_bad_prop_value=(k, v)))
continue
# is this property valid for this type of image?
if k not in allowed_props:
attach_bad_prop=k))
continue
raise errs[0]
if errs:
"""Initialize an Image object which can be used to access a
parent image."""
try:
except OSError:
try:
except apx.ImageNotFoundException:
return pimg
"""Return the altroot path prefix for the current image."""
def nothingtodo(self):
"""If our in-memory linked image state matches the on-disk
linked image state then there's nothing to do. If the state
differs then there is stuff to do since the new state needs
to be saved to disk."""
# compare in-memory and on-disk properties
if li_ondisk_props == None:
li_ondisk_props = dict()
if li_ondisk_props != li_inmemory_props:
return False
# compare in-memory and on-disk constraints
if li_ondisk_ppkgs == None:
return False
# compare in-memory and on-disk parent publishers
return False
return True
"""Return publisher information for the specified image. If
no image is specified we return publisher information for the
current image.
Publisher information is returned in a sorted list of lists
of the format:
<publisher name>, <sticky>
Where:
<publisher name> is a string
<sticky> is a boolean
The tuples are sorted by publisher rank.
"""
# default to ourselves
if img == None:
# get a sorted list of the images publishers
rv = []
for p in pubs:
return rv
"""If we're a child image's, verify that the parent image
publisher configuration is a subset of the child images
publisher configuration. This means that all publishers
configured within the parent image must also be configured
within the child image with the same:
- publisher rank
- sticky and disabled settings
The child image may have additional publishers configured but
they must all be lower ranked than the parent's publishers.
"""
# if we're not a child image then bail
return
# if we're using the sysrepo then don't bother
return
if ppubs == None:
# parent publisher data is missing, press on and hope
# for the best.
return
# child image needs at least as many publishers as the parent
raise apx.PlanCreationException(
# check rank, sticky, and disabled settings
if p == pp:
continue
raise apx.PlanCreationException(
"""Update linked image constraint, publisher data, and
state from our parent image."""
# we're not a child image, nothing to do
return
# parent pushes data to us, nothing to do
return
# initialize the parent image
# generate new constraints
# generate new publishers
# check if anything has changed
# we have new constraints
# parent has new publishers
if not need_sync:
# nothing changed
return
# if we're not planning an image attach operation then write
# the linked image metadata to disk.
"""Write in-memory linked image state to disk."""
# create a list of metadata file paths
# cleanup any temporary files
# we're no longer linked; delete metadata
return
# save our properties, but first remove altroot path prefixes
# and any temporal properties
# if we're not a child we don't have constraints
return
# we're a child so save our latest constraints
def child_name(self):
"""If the current image is a child image, this function
returns a linked image name object which represents the name
of the current image."""
raise self.__apx_not_child()
"""Indicates whether the current image is a child image."""
"""Indicates whether the current image is a parent image.
'ignore_plugin_errors' ignore plugin runtime errors when
trying to determine if we're a parent image.
"""
"""Indicates whether the current image is a parent image."""
"""Return a dictionary which represents the linked image
properties associated with a linked image.
'lin' is the name of the child image. If lin is None then
the current image is assumed to be a linked image and it's
properties are returned.
Always returns a copy of the properties in case the caller
tries to update them."""
if lin == None:
# If we're not linked we'll return an empty
# dictionary. That's ok.
# make sure the specified child exists
# make a copy of the props in case they are updated
# add temporal properties
return props
def __apx_not_child(self):
"""Raise an exception because the current image is not a child
image."""
"""Check if a specific child image exists."""
for i in self.__list_children():
if i[0] == lin:
return True
if raise_except:
return False
"""Given a list of linked image name objects, make sure all
the children exist."""
"type(lin_list) == %s, str(lin_list) == %s" % \
def parent_fmris(self):
"""A set of the fmris installed in our parent image."""
# We return None since frozenset() would indicate
# that there are no packages installed in the parent
# image.
return None
"""Given a string representing a linked image child name,
returns linked image name object representing the same name.
'allow_unknown' indicates whether the name must represent
actual children or simply be syntactically correct."""
if not allow_unknown:
return lin
"""Returns a list of linked child images associated with the
current image.
'li_ignore' see list_related() for a description.
The returned value is a list of tuples where each tuple
contains (<li name>, <li path>)."""
if li_ignore == []:
# ignore all children
return []
li_children = [
]
# sort by linked image name
if li_ignore == None:
# don't ignore any children
return li_children
errs = [
]
raise errs[0]
if errs:
return [
]
"""Returns a list of linked images associated with the
current image. This includes both child and parent images.
'li_ignore' is either None or a list. If it's None (the
default), all children will be listed. If it's an empty list
no children will be listed. Otherwise, any children listed
in li_ignore will be ommited from the results.
The returned value is a list of tuples where each tuple
contains (<li name>, <relationship>, <li path>)."""
li_list = [
]
# we're not linked
return []
# we're linked so append ourself to the list
# if we have a path to our parent then append that as well.
# sort by linked image name
return li_list
"""We only update in-memory state; nothing is written to
disk, to sync linked image state to disk call syncmd."""
if props == None:
raise apx.LinkedImageException(
# Path must be an absolute path.
# we don't bother to cleanup the path to the parent image here
# because when we allocate an Image object for the parent
# image, it will do that work for us.
# make sure we're not linking to ourselves
# make sure we're not linking the root image as a child
raise apx.LinkedImageException(
# get the cleaned up parent image path.
# If we're in an alternate root, the parent must also be within
# that alternate root.
raise apx.LinkedImageException(
# make a copy of the properties
if k not in self.__pull_child_props:
# this prop doesn't apply to pull images
continue
if k not in props:
props[k] = v
"""We only update in memory state; nothing is written to
disk, to sync linked image state to disk call syncmd."""
if not force:
raise apx.LinkedImageException(
if not lip.support_detach:
raise apx.LinkedImageException(
# Generate a new set of linked image properties. If we have
# no children then we don't need any more properties.
props = None
# If we have children we'll need to keep some properties.
strip = prop_values - \
# Update our linked image properties.
"""Determine if an image is in sync with its constraints."""
sync_fmris = []
# get parent dependencies from the catalog
parent_deps = [
a
if a.name == "depend" and \
]
if parent_deps:
if not sync_fmris:
# No packages to sync
return True
# create a dictionary of packages installed in the parent
ppkgs_dict = dict([
])
for fmri in sync_fmris:
return False
return False
return True
"""If the current image is a child image, this function
audits the current image to see if it's in sync with its
parent."""
e = self.__apx_not_child()
try:
if li_parent_sync:
# try to refresh linked image constraints from
# the parent image.
except apx.LinkedImageException, e:
return LI_RVTuple(e.lix_exitrv, e, None)
e = apx.LinkedImageException(
"""Internal helper function that takes a dictionary returned
from an operations on multiple children and merges the results
into a single return code."""
if not rvdict:
if not rv_map:
p_dicts = [
if rvtuple.rvt_p_dict is not None
]
])
if (rv_seen == rv_map_set):
# keep track of all the return values that are mapped
# the mappings better have included pkgdefs.EXIT_OK
# if we had errors for unmapped return values, bundle them up
errs = [
]
elif errs:
else:
err = None
# we have one consistent return value
"""Convenience function that takes a dictionary returned from
an operations on multiple children and merges the results into
a single return code."""
rv_map = [
]
"""Convenience function that takes a dictionary returned from
an operations on multiple children and merges the results into
a single return code."""
rv_map = [
]
"""Convenience function that takes a dictionary returned from
an operations on multiple children and merges the results into
a single return code."""
"""Sanity check the parameters associated with a child image
that we are trying to attach."""
# check the name to make sure it doesn't already exist
# Path must be an absolute path.
# If we're in an alternate root, the child must also be within
# that alternate root
raise apx.LinkedImageException(
# path must be an image
try:
except OSError:
if not img_prefix:
# Does the parent image (ourselves) reside in clonable BE?
# Unused variable 'be_uuid'; pylint: disable=W0612
# pylint: enable=W0612
if be_name:
else:
# If the parent image is clonable then the new child image
# must be nested within the parents filesystem namespace.
raise apx.LinkedImageException(
# Find the common parent directory of the both parent and the
# child image.
# Make sure there are no additional images in between the
# parent and the child. (Ie, prevent linking of images if one
# of the images is nested within another unrelated image.)
# This is done by looking at all the parent directories for
# both the parent and the child image until we reach a common
# ancestor.
# First check the parent directories of the child.
try:
except OSError, e:
# W0212 Access to a protected member
# pylint: disable=W0212
raise apx._convert_error(e)
if not tmp:
continue
# Then check the parent directories of the parent.
try:
except OSError, e:
# W0212 Access to a protected member
# pylint: disable=W0212
raise apx._convert_error(e)
if not tmp:
continue
# Child image should not already be linked
try:
except OSError, e:
# W0212 Access to a protected member
# pylint: disable=W0212
raise apx._convert_error(e)
if exists and not allow_relink:
"""Attach an image as a child to the current image (the
current image will become a parent image. This operation
results in attempting to sync the child image with the parent
image.
For descriptions of parameters please see the descriptions in
api.py`gen_plan_*"""
if props == None:
e = apx.LinkedImageException(
return LI_RVTuple(e.lix_exitrv, e, None)
# Path must be an absolute path.
return LI_RVTuple(e.lix_exitrv, e, None)
# cleanup specified path
try:
except OSError, e:
return LI_RVTuple(e.lix_exitrv, e, None)
# make sure we're not linking to ourselves
# make sure we're not linking the root image as a child
raise apx.LinkedImageException(
# if the current image isn't linked yet then we need to
# generate some linked image properties for ourselves
# sanity check the input
try:
except apx.LinkedImageException, e:
return LI_RVTuple(e.lix_exitrv, e, None)
# make a copy of the options and start updating them
# fill in any missing defaults options
if k not in child_props:
child_props[k] = v
# attach the child in memory
if noexecute and li_md_only:
# we've validated parameters, nothing else to do
# update the child
try:
except apx.LinkedImageException, e:
return LI_RVTuple(e.lix_exitrv, e, None)
rvdict = {}
return rvtuple
# commit child image property updates
return rvtuple2
# save parent image properties
# The recursive child operation may have returned NOP, but
# since we always update our own image metadata, we always
# return OK.
return rvtuple
"""Audit one or more children of the current image to see if
they are in sync with this image."""
if lin_list == []:
lin_list = None
return rvdict
"""Sync one or more children of the current image."""
if progtrack is None:
if lin_list == []:
lin_list = None
rvdict = {}
return rvdict
"""Detach one or more children from the current image. This
operation results in the removal of any constraint package
from the child images."""
if lin_list == []:
lin_list = None
# check if we support detach for these children. we don't use
# iteritems() when walking lic_dict because we might modify
# lic_dict.
continue
# we can't detach this type of image.
e = apx.LinkedImageException(
# do the detach
# if any of the children successfully detached, then we want
# to discard our metadata for that child.
# if the detach failed leave metadata in parent
continue
# detach the child in memory
if noexecute:
continue
# commit child image property updates
# don't overwrite previous errors
# we're not linked anymore, so delete all our linked
# properties.
return rvdict
"""An iterator function which performs a linked image
operation on multiple children in parallel.
'_pkg_op' is the pkg.1 operation that we're going to perform
'_lic_list' is a list of linked image child objects to perform
the operation on.
'_rvdict' is a dictionary, indexed by linked image name, which
contains rvtuples of the result of the operation for each
child.
'_prograck' is a ProgressTracker pointer.
'_failfast' is a boolean. If True and we encounter a failure
operating on a child then we raise an exception immediately.
If False then we'll attempt to perform the operation on all
children and rvdict will contain a LI_RVTuple result for all
children.
'_expect_plan' is a boolean that indicates if we expect this
operation to generate an image plan.
'_ignore_syncmd_nop' a boolean that indicates if we should
always recurse into a child even if the linked image meta data
isn't changing.
'_pd' a PlanDescription pointer."""
if _lic_list:
# these operations are cheap so ideally we'd like to
# use full parallelism. but if the user specified a
# concurrency limit we should respect that.
else:
# no limit was specified, use full concurrency
concurrency = -1
else:
# setup operation for each child
lic_setup = []
try:
except apx.LinkedImageException, e:
LI_RVTuple(e.lix_exitrv, e, None)
# if _failfast is true, then throw an exception if we failed
# to setup any of the children. if _failfast is false we'll
# continue to perform the operation on any children that
# successfully initialized and we'll report setup errors along
# with the final results for all children.
# before we raise an exception we need to cleanup any
# children that we setup.
# raise an exception
"""An iterator function invoked when a child has
finished an operation.
'lic' is the child that has finished execution.
'lic_list' a list of children to remove 'lic' from.
See __children_op() for an explanation of the other
parameters."""
assert lic.child_op_is_done()
# check if we should raise an exception
# we're going to raise an exception. abort
# the remaining children.
# raise an exception
# only display child output if there was no
# error (otherwise the exception includes the
# output so we'll display it twice.)
# check if we should yield a plan.
yield rvtuple.rvt_p_dict
# check if we did everything we needed to do during child
# setup. (this can happen if we're just doing an implicit
# syncmd during setup we discover the linked image metadata
# isn't changing.) we iterate over a copy of lic_setup to
# allow __child_op_finish() to remove elements from lic_setup
# while we're walking through it.
if not lic.child_op_is_done():
continue
yield p_dict
# keep track of currently running children
lic_running = []
# keep going as long as there are children to process
while lic_setup and (
concurrency <= 0):
# start processing on a child
if progtrack_update:
# display progress on children
lin_running = sorted([
done)
# poll on all the linked image children and see which
# ones have pending output.
for lic in lic_running
])
if not lic.child_op_is_done():
continue
# a child finished processing
yield p_dict
if _lic_list:
"""Initialize LinkedImageChild objects for children specified
in 'lin_list'. If 'lin_list' is not specified, then
initialize objects for all children (excluding any being
ignored via 'li_ignore')."""
# you can't specify children to operate on and children to be
# ignored at the same time
# if no children we listed, build a list of children
if lin_list is None:
lin_list = [
i[0]
]
else:
rvdict = {}
lic_dict = {}
try:
except apx.LinkedImageException, e:
if failfast:
return lic_dict
"""Initialize child objects used during recursive packaging
operations."""
"""Initialize planning state. If we're a child image we save
our current state (which may reflect a planned state that we
have not committed to disk) into the plan. We also initialize
all our children to prepare to recurse into them."""
# we don't want to recurse
return
# Initialize children
if not self.__lic_dict:
# we don't need to recurse
return
# if we have any children we don't support operations using
# temporary repositories.
if repos:
"""Do a recursive publisher check"""
# get a list of of children to recurse into.
# do a publisher check on all of them
rvdict = {}
# raise an exception if one or more children failed the
# publisher check.
"""This is an iterator function. It recurses into linked
image children to perform the specified operation.
"""
# get a pointer to the current image plan
# get a list of of children to recurse into.
# sanity check stage
# if we're ignoring all children then we can't be recursing
# sanity check the plan description state
# the state should be uninitialized
assert pd.children_planned == []
assert pd.children_nop == []
else:
# if we ignored all children, we better not have
# recursed into any children.
assert pd.children_ignored != [] or \
# there shouldn't be any overloap between sets of
# children in the plan
if pd.children_ignored:
# make sure set of child handles matches the set of
# previously planned children.
# if we're in the planning stage, we should pass the current
# image plan onto the child and also expect an image plan from
# the child.
# get target op and arguments
# assume that for most operations we want to recurse into the
# child image even if the linked image metadata isn't
# changing. (this would be required for recursive operations,
# update operations, etc.)
# the exception is if we're doing an implicit sync.
# to improve performance we assume the child is
# already in sync, so if its linked image metadata
# isn't changing then the child won't need any updates
# so there will be no need to recurse into it.
rvdict = {}
**pd.child_kwargs):
yield p_dict
assert not _li_rvdict_exceptions(rvdict)
# check for children that don't need any updates
# record the children that are done planning
"""Determine what pkg command to use when recursing into child
images."""
#
# given the api operation being performed on the current
# image, figure out what api operation should be performed on
# child images.
#
# the recursion policy which hard coded here is that if we do
# an pkg update in the parent image without any packages
# specified (ie, we want to update everything) then when we
# recurse we'll also do an update of everything. but if we're
# doing any other operation like install, uninstall, an update
# of specific packages, etc, then when we recurse we'll do a
# sync in the child.
#
api_kwargs["pkgs_update"]:
else:
return pkg_op
"""Determine what pkg command arguments to use when recursing
into child images."""
kwargs = {}
#
# when we recurse we always accept all new licenses (for now).
#
# ultimately (when start yielding back plan descriptions for
# children) in addition to accepting licenses on the plan for
# the current image the api client will also have to
# explicitly accept licenses for all child images. but until
# that happens we'll just assume that the parent image license
# space is a superset of the child image license space (and
# since the api consumer must accept licenses in the parent
# before we'll do anything, we'll assume licenses in the child
# are accepted as well).
#
if "li_pkg_updates" in api_kwargs:
# option specific to: attach, set-property-linked, sync
# skip ipkg up to date check for child images
return kwargs
"""Plan child image updates."""
# update the plan arguments
# recurse into children
yield p_dict
"""Prepare child image updates."""
"""Execute child image updates."""
"""Initialize our state in the PlanDescription."""
# if we're a child, save our parent package state into the
# plan description
"""Reload a previously created plan."""
# load linked image state from the plan
# now initialize our recursion state, this involves allocating
# handles to operate on children. we don't need handles for
# children that were either ignored during planning, or which
# return EXIT_NOP after planning (since these children don't
# need any updates).
# merge the children that returned nop into li_ignore (since
# we don't need to recurse into them). if li_ignore is [],
# then we ignored all children during planning
if li_ignore is None:
# no children were ignored during planning
li_ignore = []
# Initialize children
def recurse_nothingtodo(self):
"""Return True if there is no planned work to do on child
image."""
if lic.child_name not in \
return False
return True
"""Check if a package has a parent dependency."""
return True
return False
"""Since we don't publish packages with parent dependencies
yet, but we want to be able to sync packages between zones,
we'll need to fake up some extra package parent dependencies.
Here we'll inspect the catalog to find packages that we think
should have parent dependencies and then we'll return a
dictionary, indexed by fmri, which contains the extra
dependency actions that should be added to each package."""
# create a parent dependency action with a nonglobal zone
# variant tag.
# we're not operating on a nonglobal zone image so we
# don't need to fabricate parent zone dependencies
return dict()
# we're not a child image so parent dependencies are
# irrelevant
return dict()
#
# it's time consuming to walk the catalog looking for packages
# to dynamically add parent dependencies too. so to speed
# things up we'll check if the currently installed osnet and
# ips incorporations already have parent dependencies. if
# they do then this image has already been upgraded to a build
# where these dependencies are being published so there's no
# need for us to dynamically add them.
#
# osnet incorporation has parent deps
# ips incorporation has parent deps
if osnet_has_pdep and ips_has_pdep:
return dict()
if not installed_catalog:
# search the known catalog
# assume that the osnet and ips incorporations should always
# have a parent dependencies.
excludes):
if (a.name != "depend") or \
continue
# create an fmri for the incorporated package
a.attrs["fmri"],
# translate the incorporated package fmris into actual
# packages in the known catalog
# all the fmris we want to add dependencies to.
# remove some unwanted fmris
# eliminate renamed or obsoleted fmris
continue
# eliminate any group packages
continue
class LinkedImageChild(object):
"""A LinkedImageChild object is used when a parent image wants to
access a child image. These accesses may include things like:
auditing a child image, or recursing into a child image to keep it in
sync with planned changes in the parent image."""
# globals
# cache properties.
try:
except OSError:
raise apx.LinkedImageException(
if not imgdir:
raise apx.LinkedImageException(
# initialize paths for linked image data files
# initialize a linked image child plugin
self.__child_op_rvtuple = None
def child_name(self):
"""Get the path associated with a child image."""
def child_path(self):
"""Get the path associated with a child image."""
def child_pimage(self):
"""Get a pointer to the parent image object associated with
this child."""
"""Write data to a child image."""
# first save our data to a temporary file
# check if we're updating the data
try:
except OSError, e:
# W0212 Access to a protected member
# pylint: disable=W0212
raise apx._convert_error(e)
if exists:
try:
except OSError, e:
# W0212 Access to a protected member
# pylint: disable=W0212
raise apx._convert_error(e)
# if we're not actually updating any data, or if we were just
# doing a test to see if the data has changed, then delete the
# temporary data file
return updated
if not tmp:
# we are updating the real data.
try:
except OSError, e:
# W0212 Access to a protected member
# pylint: disable=W0212
raise apx._convert_error(e)
return True
"""Sync linked image parent constraint data to a child image.
linked image metadata files, or if we should access temporary
versions (which have ".<runid>" appended to them."""
# there has to be an image plan to export
if pd is not None:
# if there's an image plan the we need to update the
# installed packages based on that plan.
continue
if src:
if dst:
# paranoia
# save the planned cips
"""Sync linked image properties data to a child image.
linked image metadata files, or if we should access temporary
versions (which have ".<runid>" appended to them."""
# make a copy of the props we want to push
assert PROP_PARENT_PATH not in props
# delete temporal properties
"""Sync linked image parent publisher data to a child image.
linked image metadata files, or if we should access temporary
versions (which have ".<runid>" appended to them."""
"""Sync linked image data to a child image.
linked image metadata files, or if we should access temporary
versions (which have ".<runid>" appended to them."""
if pd:
"""Prepare to perform an operation on a child image by syncing
the latest linked image data to that image. As part of this
operation, if we discover that the meta data hasn't changed we
may report back that there is nothing to do (EXIT_NOP).
'ignore_syncmd_nop' a boolean that indicates if we should
always recurse into a child even if the linked image meta data
isn't changing.
'tmp' a boolean that indicates if we should save the child
image meta data into temporary files (instead of overwriting
the persistent meta data files).
'test' a boolean that indicates we shouldn't save any child
image meta data, instead we should just test to see if the
meta data is changing.
'pd' an optional plan description object. this plan
description describes changes that will be made to the parent
image. if this is supplied then we derive the meta data that
we write into the child from the planned parent image state
(instead of the current parent image state).
'stage' indicates which stage of execution we should be
performing on a child image."""
# we don't actually update metadata during other stages of
# operation
if stage not in [
return True
try:
except apx.LinkedImageException, e:
LI_RVTuple(e.lix_exitrv, e, None)
return False
# we successfully updated the metadata
return True
# if the metadata changed then report success
if updated:
return True
# the metadata didn't change, so this operation is a NOP
return False
"""Prepare to sync a child image. This involves updating the
linked image metadata in the child and then possibly recursing
into the child to actually update packages.
'li_attach_sync' indicates if this sync is part of an attach
operation.
For descriptions of parameters please see the descriptions in
api.py`gen_plan_*"""
if li_md_only:
#
# we're not going to recurse into the child image,
# we're just going to update its metadata.
#
# we don't support updating packages in the parent
# during attach metadata only sync.
#
assert not _pd
if not self.__child_op_setup_syncmd(
# the update failed
return
return
#
# first sync the metadata
#
# if we're doing this sync as part of an attach, then
# temporarily sync the metadata since we don't know yet if the
# attach will succeed. if the attach doesn't succeed this
# means we don't have to delete any metadata. if the attach
# succeeds the child will make the temporary metadata
# permanent as part of the commit.
#
# we don't support updating packages in the parent
# during attach.
#
assert not li_attach_sync or _pd is None
if not self.__child_op_setup_syncmd(
# the update failed or the metadata didn't change
return
backup_be=None,
backup_be_name=None,
be_name=None,
li_ignore=None,
li_target_list=[],
new_be=None,
origins=[],
"""Prepare to update a child image."""
# first sync the metadata
if not self.__child_op_setup_syncmd(
# the update failed or the metadata didn't change
return
backup_be=None,
backup_be_name=None,
be_name=None,
li_ignore=None,
new_be=None,
origins=[],
"""Prepare to detach a child image."""
li_target_list=[],
def __child_setup_pubcheck(self):
"""Prepare to a check if a child's publishers are in sync."""
# first sync the metadata
if not self.__child_op_setup_syncmd():
# the update failed
return
# setup recursion into the child image
def __child_setup_audit(self):
"""Prepare to a child image to see if it's in sync with its
constraints."""
# first sync the metadata
if not self.__child_op_setup_syncmd():
# the update failed
return
# setup recursion into the child image
li_target_list=[],
def child_op_abort(self):
"""Public interface to abort an operation on a child image."""
self.__child_op_rvtuple = None
**kwargs):
"""Public interface to setup an operation that we'd like to
perform on a child image."""
assert self.__child_op_rvtuple is None
else:
raise RuntimeError(
"Unsupported package client op: %s" % _pkg_op)
def child_op_start(self):
"""Public interface to start an operation on a child image."""
# if we have a return value this operation is done
if self.__child_op_rvtuple is not None:
return True
def child_op_is_done(self):
"""Public interface to query if an operation on a child image
is done."""
# if we have a return value this operation is done
if self.__child_op_rvtuple is not None:
return True
# make sure there is some data from the child
"""Public interface to get the result of an operation on a
child image.
'expect_plan' boolean indicating if the child is performing a
planning operation. this is needed because if we're running
in parsable output mode then the child will emit a parsable
json version of the plan on stdout, and we'll verify it by
running it through the json parser.
"""
# if we have a return value this operation is done
if self.__child_op_rvtuple is not None:
self.__child_op_rvtuple = None
return (rvtuple, None, None)
# make sure we're not going to block
if e is not None:
# if we got an exception, or a return value other than OK or
# NOP, then return an exception.
if e is not None or \
e = apx.LinkedImageException(
# check for NOP.
assert e is None
if global_settings.client_output_parsable_version is None or \
not expect_plan:
# If a plan was created and we're in parsable output mode then
# parse the plan that should have been displayed to stdout.
p_dict = None
try:
except ValueError, e:
# JSON raises a subclass of ValueError when it
# can't parse a string.
e = apx.LinkedImageException(
"""Return the progress pipe associated with the PkgRemote
instance that is operating on a child image."""
"""Our image path is being updated, so figure out our new
child image paths. This interface only gets invoked when:
- We're doing a packaging operation on a parent image and
we've just cloned that parent to create a new BE that we're
going to update. This clone also cloned all the children
and so now we need to update our paths to point to the newly
created children.
- We tried to update a cloned image (as described above) and
our update failed, hence we're changing paths back to the
original images that were the source of the clone."""
# get the image path without the altroot
# update the path with the current altroot
# update properties with altroot
# we don't bother to update update PROP_PARENT_PATH since
# that is only used when reading constraint data from the
# parent image, and this interface is only invoked when we're
# starting or finishing execution of a plan on a cloned image
# (at which point we have no need to access the parent
# anymore).
# ---------------------------------------------------------------------------
# Utility Functions
#
"""Save JSON encoded linked image metadata to a file."""
# make sure the directory we're about to save data into exists.
try:
# write the output to a temporary file
# atomically create the desired file
except OSError, e:
# W0212 Access to a protected member
# pylint: disable=W0212
raise apx._convert_error(e)
"""Load JSON encoded linked image metadata from a file."""
try:
return missing_val
except OSError, e:
# W0212 Access to a protected member
# pylint: disable=W0212
raise apx._convert_error(e)
return data
"""Utility class used when json encoding linked image metadata."""
# E0202 An attribute inherited from JSONEncoder hide this method
# pylint: disable=E0202
"""Required routine that overrides the default base
class version. This routine must serialize 'obj' when
attempting to save 'obj' json format."""
def PkgDecoder(dct):
"""Utility class used when json decoding linked image metadata."""
rvdct = {}
# unicode must die
k = k.encode("utf-8")
v = v.encode("utf-8")
# convert boolean strings values back into booleans
if v.lower() == "true":
v = True
elif v.lower() == "false":
v = False
rvdct[k] = v
return rvdct
def rm_dict_ent(d, keys):
"""Remove a set of keys from a dictionary."""
return dict([
(k, v)
for k, v in d.iteritems()
if k not in keys
])
bad_cp=None,
bad_iup=None,
bad_lin_type=None,
bad_prop=None,
missing_props=None,
multiple_altroots=None,
saved_temporal_props=None):
"""Oops. We hit a runtime error. Die with a nice informative
message. Note that runtime errors should never happen and usually
indicate bugs (or possibly corrupted linked image metadata), so they
are not localized (just like asserts are not localized)."""
if bad_cp:
assert err == None
elif bad_iup:
assert err == None
elif bad_lin_type:
assert err == None
elif bad_prop:
assert err == None
elif missing_props:
assert err == None
err = "Missing required linked properties: %s" % \
elif multiple_altroots:
assert err == None
err = "Multiple plugins reported different altroots:"
elif saved_temporal_props:
assert err == None
err = "Found saved temporal linked properties: %s" % \
else:
assert err != None
if li:
if lic:
err_prefix = "Linked image error: "
if lin:
err_suffix = ""
elif path:
raise RuntimeError(
# ---------------------------------------------------------------------------
# Functions for accessing files in the current root
#
def path_exists(path):
"""Simple wrapper for accessing files in the current root."""
try:
except OSError, e:
# W0212 Access to a protected member
# pylint: disable=W0212
raise apx._convert_error(e)
def path_isdir(path):
"""Simple wrapper for accessing files in the current root."""
try:
except OSError, e:
# W0212 Access to a protected member
# pylint: disable=W0212
raise apx._convert_error(e)
"""Simple wrapper for accessing files in the current root."""
try:
except OSError, e:
# W0212 Access to a protected member
# pylint: disable=W0212
raise apx._convert_error(e)
"""Simple wrapper for accessing files in the current root."""
try:
except OSError, e:
# W0212 Access to a protected member
# pylint: disable=W0212
raise apx._convert_error(e)
# ---------------------------------------------------------------------------
# Functions for managing images which may be in alternate roots
#
"""Check if 'path' is nested within 'altroot'"""
# make sure both paths have one trailing os.sep.
# check for nested or equal paths
return True
return False
"""Return a path where 'path' is nested within 'altroot'"""
# sanity check
return altroot_path
"""Return the relative porting of 'path', which must be nested within
'altroot'"""
if rv == "":
rv = "/"
return rv
"""Given 'path', and a relative path 'path_suffix' that must match
the suffix of 'path', return the unmatched prefix of 'path'."""
# make sure both paths have one trailing os.sep.
if i <= 0:
# path and path_suffix are either unrelated or equal
else:
# sanity check
return altroot