#!/usr/bin/python ######################################################################## # Copyright (C) 2010-2019 VMWare, Inc. # # All Rights Reserved # ######################################################################## from .Version import VibVersion from .Vib import VibProvide, VibRelation from .Utils.Misc import isString # # This is the version of the esximage library. # It is injected into the dependency graph as a provides with the # special name "installer:esximage". # This name may be used in a if a VIB needs to use certain # features of a particular version of the esximage library. # ESXIMAGE_LIB_VERSION = "1.0" ESXIMAGE_PROVIDE = "installer:esximage" class ScanResult(object): """Holds information about component relationships, where a component may be a VIB or something else. Attributes: * id - Component ID, unique amongst all components * comptype - Component type * depends - A dictionary. Each key is a VibRelation ID. Each value is a set of VIB IDs that provide the dependency. * dependedOnBy - A set of VIB IDs which depend on this VIB. * replaces - A set of VIB IDs which this VIB replaces. * replacedBy - A set of VIB IDs which replace this VIB. * conflicts - A set of VIB IDs with which this VIB conflicts. (Note that conflicts are reflexive; if a->conflicts->b; then b->conflicts->a.) """ TYPE_VIB = "vib" TYPE_ESXIMGLIB = "esximagelib" # The esximage library version TYPE_IMPLICITREQ = "implicitreq" # Implicit depend from installer:esximage TYPE_COMPONENT = "component" ALL_TYPES = (TYPE_VIB, TYPE_ESXIMGLIB, TYPE_IMPLICITREQ, TYPE_COMPONENT) def __init__(self, compid, comptype=TYPE_VIB): self.id = compid self.comptype = comptype self.depends = dict() self.dependedOnBy = set() self.replaces = set() self.replacedBy = set() self.conflicts = set() class VibScanner(object): """Provides a method for establishing relationships between VIBs, and holds a mapping of VIB IDs to ScanResult objects. Attributes: * results - A dictionary containing ScanResults objects. Each key is a component ID, and each value is a ScanResult object. * vibs - The subset of results that are actually VIBs. * extraprovides - A list of tuples for injection of extra provides into the scanning system. Useful for external deps, the esximage library version, and other special situations. Each tuple consists of (provideobj, compids, comptype), where provideobj is an instance of Vib.VibProvide; compids is a list or set of unique component IDs for the results dict that provides the thing in provideobj, and comptype is one of ScanResult.TYPES_*. """ def __init__(self): self.results = dict() self.vibs = dict() self.extraprovides = list() # Inject the esximage library version in as a provide. Note that # we also need to inject a special ID just so that any depends # on the esximage library can get a nonzero result. esximgid = ESXIMAGE_PROVIDE esximgprov = VibProvide(esximgid, VibVersion.fromstring(ESXIMAGE_LIB_VERSION)) self.AddExtraProvide(esximgprov, (esximgid,), ScanResult.TYPE_ESXIMGLIB) def AddExtraProvide(self, provideobj, compids, comptype): """Inject an extra provide into the components being scanned. Useful for external dependencies, the esximage library version, etc. Parameters: * provideobj - An instance of Vib.VibProvide * compids - A list or set of each unique component ID being provided. Unless this list is nonempty, matches for the extra provide will not be able to return any providing comp IDs. * comptype - One of the ScanResult.TYPE_* types. """ if comptype not in ScanResult.ALL_TYPES: raise ValueError("'%s' is not a valid component type" % (comptype)) self.extraprovides.append((provideobj, compids, comptype)) def GetResultsByType(self, comptype): """Returns a dict containing a subset of the scan results by type. Parameters: * comptype - The component type to get results for Returns: A dictionary. Each key is a component ID, and each value is a ScanResult object. """ return dict((r.id, r) for r in self.results.values() if r.comptype == comptype) def GetNewestSet(self): """Returns the newest VIBs amongst the scanned VIBs. The newest VIBs are those whose scan results have an empty replacedBy. Returns: A set of VIB IDs corresponding to the newest VIBs. """ return set(vid for vid in self.vibs if len(self.vibs[vid].replacedBy) == 0) def GetUpdatesSet(self, vibs): """Returns all the VIBs that update some set of VIBs. No results will be returned for VIBs that are not part of the scan results. Parameters: * vibs - An iterable of either VIB IDs or Vib instances. A VibCollection should work. These are the VIBs to find updates for. Returns: A set of VIB IDs for the VIBs that update the parameter vibs. """ vibids = set() for vib in vibs: if isString(vib): vibids.add(vib) else: vibids.add(vib.id) remaining = set(self.vibs.keys()) - vibids return set(vid for vid in remaining \ if len(self.vibs[vid].replaces & vibids)) def GetDowngradesSet(self, vibs): """Returns all the VIBs that are older than some set of VIBs. No results will be returned for VIBs that are not part of the scan results. Parameters: * vibs - An iterable of either VIB IDs or Vib instances. A VibCollection should work. These are the VIBs to find downgrades or older VIBs for. Returns: A set of VIB IDs for the VIBs that downgrade the parameter vibs. """ vibids = set() for vib in vibs: if isString(vib): vibids.add(vib) else: vibids.add(vib.id) remaining = set(self.vibs.keys()) - vibids return set(vid for vid in remaining \ if len(self.vibs[vid].replacedBy & vibids)) def Scan(self, vibs): """Populates relationships between VIBs. Parameters: * vibs - A VibCollection instance. Note: None of the child objects in the vibs parameter are modified. """ # vibbyid is a complex dict. Keys are vib IDs, and values are lists. # Each list itself contains three dictionaries. Dictionary 0 maps depends # IDs to a set of matching VibProvide IDs; dictionary 1 maps replaces IDs # to a set of matching VibProvide IDs; dictionary 2 maps conflicts IDs to # a set of matching VibProvide IDs. # vibbyid[vibid] = [{dependsid: set((provideids,)), # replacesid: set((provideids,)), # conflictsid: set(provideids,))}] vibbyid = dict() # provbyid maps VibProvide IDs to VibProvide objects and the VIBs that # provide them. provbyid[provideid] = [provideobj, set((providingvibs,))] provbyid = dict() # relbyname is a complex dict. Each key is a VibRelation name. Each value # is then itself a dictionary, where the keys are VibRelation IDs, and # values are a list. The first item in each list is a VibRelation object, # and the second value is a set of matching provide IDs. # relbyname[relationname] = {relationid: [relationobj, # set((provideids,))]} relbyname = dict() def cacherel(rel): relname = rel.name relid = rel.id if relname in relbyname: namecache = relbyname[relname] if relid in namecache: provids = namecache[relid][1] else: provids = set() namecache[relid] = [rel, provids] else: provids = set() relbyname[relname] = {relid: [rel, provids]} return relid, provids # Add in extra provides to the scanned components for provideobj, compids, comptype in self.extraprovides: provbyid[provideobj.id] = [provideobj, set(compids)] for compid in compids: self.results[compid] = ScanResult(compid, comptype) for vibid, vib in vibs.items(): # We don't do anything with ScanResult objects in this loop. We # just create the objects here so that we don't have to worry about # whether they exist or not later in this function. self.results[vibid] = ScanResult(vibid) depends = dict() replaces = dict() conflicts = dict() vibbyid[vibid] = [depends, replaces, conflicts] # important: the values that we push into the depends, replaces and # conflicts dictionaries are the same set() instances that # are populated into the relbyname cache. for dep in vib.depends: depid, provids = cacherel(dep) depends[depid] = provids # Add an entry to delegate the requirement, which will be linked to # the VIBs actually providing the ESXIMAGE_PROVIDE requirement. # This will help VUM to know which VIB to install first. if dep.name == ESXIMAGE_PROVIDE: newname = dep.name.split(':')[1] newdep = VibRelation(newname, dep.relation, dep.version, True) self.results[dep.id] = ScanResult(dep.id, ScanResult.TYPE_IMPLICITREQ) newdepid, newprovids = cacherel(newdep) vibbyid[dep.id] = [{newdepid : newprovids}, dict(), dict()] for rep in vib.replaces: repid, provids = cacherel(rep) replaces[repid] = provids for con in vib.conflicts: conid, provids = cacherel(con) conflicts[conid] = provids for prov in vib.provides: provid = prov.id if provid in provbyid: provbyid[provid][1].add(vibid) else: provbyid[provid] = [prov, set((vibid,))] for provid, (prov, vibset) in provbyid.items(): name = prov.name if name in relbyname: # The provids set here will be the same instances recorded in the # vibbyid dict, so that adding an item to provids here also adds it # to the inner dictionaries in vibbyid. for relid, (rel, provids) in relbyname[name].items(): if rel.matchesprovide(prov): provids.add(provid) for vibid, (depends, replaces, conflicts) in vibbyid.items(): vibsr = self.results[vibid] for depid, provids in depends.items(): providingvibs = set() for provid in provids: for providingvibid in provbyid[provid][1]: providingvibs.add(providingvibid) self.results[providingvibid].dependedOnBy.add(vibid) vibsr.depends[depid] = providingvibs for repid, provids in replaces.items(): replacedvibs = vibsr.replaces for provid in provids: for providingvibid in provbyid[provid][1]: replacedvibs.add(providingvibid) self.results[providingvibid].replacedBy.add(vibid) for conid, provids in conflicts.items(): conflictingvibs = vibsr.conflicts for provid in provids: for providingvibid in provbyid[provid][1]: conflictingvibs.add(providingvibid) self.results[providingvibid].conflicts.add(vibid) # Don't forget to update the vibs-(only) attribute self.vibs = self.GetResultsByType(ScanResult.TYPE_VIB)