"""
Copyright (c) 2019-2024 Broadcom. All Rights Reserved.
Broadcom Confidential. The term "Broadcom" refers to Broadcom Inc.
and/or its subsidiaries.
"""
import datetime
import itertools
import logging
import os
import platform
import tempfile
import uuid
import zipfile
import re

from . import DepotMgr
from . import StagingArea
from . import Utils
from .Constants import (ADDON, ADD_ON, BASE_IMAGE_REMOVABLE_COMPS, BASE_IMG,
   COMPONENT, COMP_REMOVAL_WARNING_ID, COMP_REMOVAL_WARNING_MSG,
   COMP_REMOVAL_RES_ID, COMP_REMOVAL_RES_MSG, COMP_RETENTION_WARNING_ID,
   COMP_RETENTION_WARNING_MSG, COMP_RETENTION_RES_ID, COMP_RETENTION_RES_MSG,
   HARDWARE_SUPPORT, IMAGE_CUSTOMIZATION_ACTION_REMOVED,
   IMAGE_CUSTOMIZATION_ACTION_RETAINED, IMAGE_CUSTOMIZATION_RM_COMP_ID,
   IMAGE_CUSTOMIZATION_RM_COMP_MSG, IMAGE_CUSTOMIZATION_RT_COMP_ID,
   IMAGE_CUSTOMIZATION_RT_COMP_MSG, SOLUTIONS, SOURCE_USER_REMOVED,
   VALIDATE_SUCCESS_ID, VALIDATE_SUCCESS_MSG)
from .Utils import Notification

from ..Bulletin import Bulletin, ComponentCollection
from ..Depot import DepotFromImageProfile
from ..Errors import (AddonBaseImageMismatchError, AddonNotFound,
   AddonRecalledError, BaseImageNotFound, ComponentNotFoundError,
   ComponentDowngradeError, ComponentRecalledError,
   ComponentValidationError, ConfigComponentDowngradeError,
   ESXioComponentDowngradeError, HardwareSupportPackageNotFound,
   IncompatibleSolutionComponentError, IncompatibleSolutionCompsError,
   ManifestBaseImageMismatchError, MultipleManifestError,
   ReleaseUnitSchemaVersionError, RemovedComponentNotFound,
   SolutionComponentNotFound, SolutionNotFound, SoftwareSpecFormatError,
   UnsupportedRemovedComponent)
from ..ImageProfile import ImageProfile
from ..Manifest import (FIRMWARE_ONLY_PREFIX, HardwareSupportInfo,
                        HardwareSupportManager, HardwareSupportPackage,
                        Manifest)
from ..OfflineBundle import CreatePartialOfflineDepot
from ..ReleaseCollection import ManifestCollection, SolutionCollection
from ..ReleaseUnit import NameSpec, VersionSpec
from ..Solution import ComponentConstraintList
from ..Version import VibVersion
from ..VibCollection import VibCollection
from ..Vib import GetHostSoftwarePlatform, SoftwarePlatform
from .. import (PERSONALITY_MANAGER_COMPONENT_REMOVAL_ENABLED,
                PERSONALITY_MANAGER_DEPOT_RECALL_ENABLED)

# True when platform is ESXi or ESXio
IS_ESX = (platform.system() == 'VMkernel')
if IS_ESX:
   from ..HostImage import HostImage
   from ..Vib import ArFileVib
else:
   from ..ImageBuilder import EsxIsoImage

from collections import OrderedDict, defaultdict
from copy import deepcopy

log = logging.getLogger(__name__)

RESOLUTION_TYPE_BASEIMAGE = 'baseimage'
RESOLUTION_TYPE_ADDON = 'addon'
RESOLUTION_TYPE_USERCOMP = 'userComponent'
RESOLUTION_TYPE_SOLUTION = 'solution'
RESOLUTION_TYPE_MANIFEST = 'manifest'
RESOLUTION_TYPE_REMOVED_COMP = 'removedComponent'

# From com/vmware/esx/settings_client.py
SOURCE_TYPE_BASEIMAGE = 'BASE_IMAGE'
SOURCE_TYPE_ADDON = 'ADD_ON'
SOURCE_TYPE_USER = 'USER'
SOURCE_TYPE_SOLUTION = 'SOLUTION'
SOURCE_TYPE_MANIFEST = 'HARDWARE_SUPPORT_PACKAGE'

COMPONENT_OVERRIDE_ID = \
   'com.vmware.vcIntegrity.lifecycle.image.ComponentOverride%s%s'

DEFAULT_OVERIDE = {
    (SOURCE_TYPE_BASEIMAGE, SOURCE_TYPE_ADDON):
       'Vendor addon component overrides ESXi component',
    (SOURCE_TYPE_BASEIMAGE, SOURCE_TYPE_USER):
       'Manually added component overrides ESXi component',
    (SOURCE_TYPE_BASEIMAGE, SOURCE_TYPE_MANIFEST):
       'Hardware support package component overrides ESXi component',
    (SOURCE_TYPE_BASEIMAGE, SOURCE_TYPE_SOLUTION):
       'Solution component overrides ESXi component',
    (SOURCE_TYPE_ADDON, SOURCE_TYPE_USER):
       'Manually added component overrides vendor addon component',
    (SOURCE_TYPE_ADDON, SOURCE_TYPE_SOLUTION):
       'Solution component overrides vendor addon component',
    (SOURCE_TYPE_ADDON, SOURCE_TYPE_MANIFEST):
       'Hardware support package component overrides vendor addon component',
    (SOURCE_TYPE_MANIFEST, SOURCE_TYPE_USER):
       'Manually added component overrides hardware support package component',
    (SOURCE_TYPE_MANIFEST, SOURCE_TYPE_SOLUTION):
       'Solution component overrides hardware support package component',
    (SOURCE_TYPE_USER, SOURCE_TYPE_SOLUTION):
       'Solution component overrides manually added component'
   }

USER_RM_COMP_OVERRIDE_ID = COMPONENT_OVERRIDE_ID % ('User', 'Removed')
USER_RM_COMP_OVERRIDE_MSG = "The component has been removed by the user."

COMPONENT_SOURCE_ID = \
   'com.vmware.vcIntegrity.lifecycle.image.ComponentSource%s'

COMPONENT_SOURCE_MAP = {
    SOURCE_TYPE_BASEIMAGE: 'ESXi component',
    SOURCE_TYPE_ADDON: 'Vendor addon component',
    SOURCE_TYPE_MANIFEST: 'Hardware support package component',
    SOURCE_TYPE_USER: 'Manually added component',
    SOURCE_TYPE_SOLUTION: 'Solution component',
    SOURCE_USER_REMOVED: 'User removed component',
   }

# Export formats
FORMAT_ISO_IMAGE = 'ISO_IMAGE'
FORMAT_ISO_IMAGE_INSTALLER = 'ISO_IMAGE_INSTALLER'
FORMAT_BUNDLE = 'OFFLINE_BUNDLE'
FORMAT_DEPOT = 'DEPOT'
EXPORT_FORMATS = (FORMAT_BUNDLE, FORMAT_DEPOT, FORMAT_ISO_IMAGE,
                  FORMAT_ISO_IMAGE_INSTALLER)

# The components required to patch the patcher before scan, including
# esx-update, esxio-update, loadesx VIBs.
SCAN_COMP_NAMES = ("esx-update", "esxio-update")
ESXIO_UPDATE_COMP_NAME = "esxio-update"

def _CreateLock(fileName):
   try:
      open(fileName + '.lock', 'w').write('Lock is created')
   except:
      pass

def _getComponentVersions(comp):
   """Here a component is a Bulletin object when found, or name/version pair.

      Params:
         comp: A bulletin or a (name, version) tuple.

      Returns:
         (version, displayVersion) for Bulletini; otherwise (version, '')
   """
   if isinstance(comp, Bulletin):
      versionSpec = comp.componentversionspec
      return str(versionSpec['version']), versionSpec['uistring']
   else:
      return comp[1], ''


def _getComponentNames(comp):
   """Here a component is a Bulletin object when found, or name/version pair.

      Params:
         comp: A bulletin or a (name, version) tuple.

      Returns:
         (name, displayName) for Bulletini; otherwise (name, '')
   """
   if isinstance(comp, Bulletin):
      nameSpec = comp.componentnamespec
      return nameSpec['name'], nameSpec['uistring']
   else:
      return comp[0], ''


class _LocalizableMessage(object):
   """See com.vmware.vapi.std_client.LocalizableMessage class.
   """
   def __init__(self, id, dmsg, args=None, params=None, localized=None):
      self.id = id
      self.default_message = dmsg
      self.args = args or []
      self.params = params
      self.localized = localized


class _ComponentOverrideInfo(object):
   """See com.vmware.esx.settings.ComponentOverrideInfo
   """
   def __init__(self, version, display_version, source, note):
      self.version = version
      self.display_version = display_version
      self.source = source
      self.note = note


class _EffectiveComponentDetails(object):
   """See com.vmware.esx.settings.EffectiveComponentDetails.
   """
   def __init__(self, dname, dver, vendor, src, note, ovrd, removable,
                customizationAction, customizationDesc):
      self.display_name = dname
      self.display_version = dver
      self.vendor = vendor
      self.source = src
      self.note = note
      self.overridden_components = ovrd

      if PERSONALITY_MANAGER_COMPONENT_REMOVAL_ENABLED:
         # Below fields are PersonalityManagerComponentRemoval only.
         self.removable = removable
         self.image_customization_action = customizationAction
         # imageCustomizationDescription must be a _LocalizableMessage instance.
         # If not None, must convert to a dict for imageService._JSONEncoder to
         # work.
         self.image_customization_description = \
            customizationDesc.__dict__ if customizationDesc else None


class _EffectiveComponentInfo(object):
   """See com.vmware.esx.settings.EffectiveComponentInfo.
      removable/customizationAction/customizationDesc are passed into
      _EffectiveComponentDetails only.
   """
   def __init__(self, ver, dname, dver, vendor, src, note, ovrd,
                removable, customizationAction, customizationDesc):
      self.version = ver
      self.details = _EffectiveComponentDetails(dname, dver, vendor, src,
         note, ovrd, removable, customizationAction, customizationDesc)


class _AddOperation(object):
   """The add opeation with the added version and the resolution step.
   """
   def __init__(self, version, source):
      self.version = version
      self.source = source


class _RemoveOperation(object):
   """The remove operation with the resolution step.
   """
   def __init__(self, source):
      self.source = source


# Reformat upper case string to capitalized and remove underscore.
_reformat = lambda x: x.capitalize().replace('_', '')


def _createOverrideNote(source, altSource):
   """Create the override localization message.
   """
   args = (_reformat(altSource), _reformat(source))
   return _LocalizableMessage(COMPONENT_OVERRIDE_ID % args,
                              DEFAULT_OVERIDE[(altSource, source)])


def _createSourceNote(source):
   """Create the source localization message.
   """
   return _LocalizableMessage(COMPONENT_SOURCE_ID % _reformat(source),
                              COMPONENT_SOURCE_MAP[source])


def _raiseManifestError(hsi, baseImageVer, found):
   hsmName = hsi.manager.name
   hspName = hsi.package.name
   hspVer = hsi.package.version
   if found:
      raise ManifestBaseImageMismatchError(
               hsmName, hspName, hspVer, baseImageVer,
               'No manifest (%s, %s, %s) supports base image %s' %
               (hsmName, hspName, hspVer, baseImageVer))
   raise HardwareSupportPackageNotFound(hsmName, hspName, hspVer,
            'The manifest (%s, %s, %s) not found' %
            (hsmName, hspName, hspVer))


class SoftwareSpecMgr(object):
   """Implementation of the SoftwareSpec manager.

      a) Software spec manager provides APIs to edit the desired state documents
      b) It manages the desired state document in a staging location.
      b) It tries to resolve all the intents mentioned in the SoftwareSpec and
         tries to solve issues, if any.
      d) Validation is performed before every commit to the softwareSpec.
   """

   def __init__(self, depotManager=None, softwareSpec=None):
      """Initialize the software spec manager.

         a) Sets up the depotMgr and softwareSpec objects.
         b) If the softwareSpec is not passed then it loads it from the staging
            area.

         Parameters:
            * depotManager: DepotMgr object which contains all the components
                            listed in the spec. If not provided then DepotMgr
                            will be instantiated using the staged depot spec.
            * softwareSpec: SoftwareSpec which lists the name and
                            version of the component to be installed. If not
                            provided then the spec will be loaded from the
                            staging area.
      """
      if not IS_ESX:
         self.hasManifest = False
      if depotManager:
         self.depotMgr = depotManager
      else:
         self.depotMgr = DepotMgr.DepotMgr(connect=True)
         log.debug("Initialized the depotMgr using the staged depot spec.")

      if softwareSpec:
         self.softwareSpec = softwareSpec
      else:
         self.softwareSpec = StagingArea.getStagedSoftwareSpec(extract=True)
         log.debug("Initialized the SoftwareSpecMgr using the staged spec.")

      # Removable components, populated in _resolveComponentRemovals().
      self._removableComps = set()
      # Retained components, populated in _checkDowngrade().
      self._retainedComps = set()

   def _checkRecall(self, recalledType, name, version):
      """If a release unit with certain name+version is not found, check
         whether it has been recalled or not.
         Return:
            * True if such combination of name+version of the release unit
              can be found in self.depotMgr._dc.recallReference, which means
              that it has been recalled.
            * False otherwise
         Parameters:
            * recalledType: type of recalled release units, e.g.,
                            'recalledComponents'.
            * name, version: (non-display) name & version of this release unit.
      """
      recallReference = self.depotMgr._dc.recallReference
      for notifId, recallRef in recallReference.items():
         if recalledType not in recallRef:
            continue
         for releaseUnitName, releaseUnitVersion in recallRef[recalledType]:
            if name == releaseUnitName and version == releaseUnitVersion:
               return True
      return False

   def _checkItemNotFoundOrRecalled(self, name, ver, relUnitType,
                                    recalledErrType, notFoundErrType):
      """Given name and version of a release unit, check whether it is recalled
         Return: error message and an exception of the proper type.
         Parameters:
            * name, ver: (non-display) name and version of a release unit
            * relUnitType: type of the release unit
            * recalledErrType: *RecalledError
            * notFoundErrType: *NotFoundError
      """
      relUnitTypeMap = {ADDON: self.depotMgr._dc.RECALLED_ADDONS,
                        COMPONENT: self.depotMgr._dc.RECALLED_COMPONENTS}
      isRecalled = False
      if PERSONALITY_MANAGER_DEPOT_RECALL_ENABLED:
         # check if such a release unit with name and version has been recalled
         isRecalled = self._checkRecall(relUnitTypeMap[relUnitType], name, ver)

      # Did not find a release unit which matches the input parameters.
      if isRecalled:
         msg = ("The %s with name = %s, version = %s in the depot has been"
                " recalled." % (relUnitType, name, ver))
         err = recalledErrType(name, ver, msg)
      else:
         msg = ("Could not find the %s with name = %s, version = %s in the "
                "depot." % (relUnitType, name, ver))
         err = notFoundErrType(name, ver, msg)
      return msg, err

   def getComponent(self, name, version, ignoreErrors=False):
      """Return the id and the component which matches the given name and the
         version.

         Parameters:
            * name: name of the component.
            * version: version of the component to be fetched.
            * ignoreErrors: whether silent ComponentNotFoundError
         Returns:
            * Returns a tuple of component id and the component object.
         Raises:
            ComponentNotFoundError: If the component is not found, with
                                    ignoreErrors as False.
            ComponentRecalledError: If the component has been recalled,
                                    with ignoreErrors as False.
      """
      try:
         comp = self.depotMgr.components.GetComponent(name, version)
         log.debug("Found component with id = %s name = %s, version = %s.",
                   comp.id, name, version)
         return comp
      except KeyError:
         # Not found, continue below.
         pass

      msg, err = self._checkItemNotFoundOrRecalled(name, version, COMPONENT,
                         ComponentRecalledError, ComponentNotFoundError)

      if ignoreErrors:
         log.warning(msg)
         return name, version

      raise err

   def _getIntent(self, componentName):
      """ Return the intent for the given componentName from the SoftwareSpec.

         Parameters:
            * componentName: name of the component to search in the swSpec.
         Returns:
            * Intent which is a tuple of componentName and Version.
            * If the input componentName is 'esx' then we return the intended
              componentName as 'ESXi'. This needs to be in sync with the
              componentName of ESX is the depot.
         Raises:
            ComponentNotFoundError: If the component is not found in the spec.
      """
      try:
         if componentName == 'esx':
            return ('ESXi', self.softwareSpec[componentName])
         else:
            return (componentName,
                    self.softwareSpec.get('components')[componentName])
      except KeyError:
         raise ComponentNotFoundError(componentName,
                                      "Could not find the component with"
                                      " name: %s in the SoftwareSpec" %
                                      componentName)

   def _getBaseImageVersion(self):
      try:
         baseImageInfo = self.softwareSpec.get(BASE_IMG)
         return baseImageInfo.get('version')
      except KeyError as err:
         errMsg = 'Missing base image %s attribute in the software spec.' % err
         field = '%s/%s' % (BASE_IMG, err)
         raise SoftwareSpecFormatError(field, errMsg)

   def _findBaseImage(self):
      version = self._getBaseImageVersion()
      if version and self.depotMgr and self.depotMgr.baseimages:
         for bi in self.depotMgr.baseimages.values():
            if bi and bi.versionSpec.version.versionstring == version:
               bi.Validate(jsonSchemaCheck=False, schemaVersionCheck=True)
               return bi
      raise BaseImageNotFound(version,
                              'The base image with version %s not found' %
                              version)

   def _findAddon(self):
      addonInfo = self.softwareSpec.get(ADD_ON, None)
      if not addonInfo:
         return None
      try:
         name = addonInfo['name']
         version = addonInfo['version']
         if name and version and self.depotMgr and self.depotMgr.addons:
            for addon in self.depotMgr.addons.values():
               if (addon.nameSpec.name == name and
                   addon.versionSpec.version.versionstring == version):
                  addon.Validate(jsonSchemaCheck=False,
                                 schemaVersionCheck=True)
                  return addon
         _, _err = self._checkItemNotFoundOrRecalled(name, version, ADDON,
                                    AddonRecalledError, AddonNotFound)
         raise _err

      except KeyError as err:
         errMsg = 'Missing addon %s attribute in the software spec.' % err
         field = '%s/%s' % (ADD_ON, err)
         raise SoftwareSpecFormatError(field, errMsg)


   def _matchBaseImage(self, supportedBaseImageVersions):
      """Whether the base image version macthes any of the provided supported
         base image versions.
      """
      baseImageVer = self._getBaseImageVersion()
      for ver in supportedBaseImageVersions:
         # The supported BI version is a wild card string without '*'
         # Convert supBIVer to a pattern and check whether the
         # base image version from software spec matches it.
         if re.match(ver, baseImageVer):
            return True
      return False

   def _findManifests(self):
      manifests = ManifestCollection()
      hardwareSupport = self.softwareSpec.get(HARDWARE_SUPPORT, None)
      if not hardwareSupport or (not IS_ESX and not self.hasManifest):
         return manifests

      raiseException = not IS_ESX and self.hasManifest
      logFunc = log.error if raiseException else log.warning

      try:
         hsis = []
         for name, package in hardwareSupport['packages'].items():
            version = package['version']
            hsi = HardwareSupportInfo(
                     HardwareSupportManager(name),
                     HardwareSupportPackage(package['pkg'], version))
            hsis.append(hsi)

         # XXX: In 7.0, only support single manifest.
         if len(hsis) > 1:
            raise MultipleManifestError(
                     'Multiple hardware support packages are not supported.')

         fwOnlyHsis = []
         if self.depotMgr and self.depotMgr.manifests:
            baseImageVer = self._getBaseImageVersion()
            for hsi in hsis:
               hasManifest = False
               hsiIsCompatible = False
               for manifest in self.depotMgr.manifests.values():
                  if manifest.hardwareSupportInfo == hsi:
                     # There should be only one manifest with the same HSI.

                     if not manifest.isFirmwareOnly:
                        # Only software component count as found.
                        hasManifest = True

                     biVers = manifest.supportedBaseImageVersions
                     if self._matchBaseImage(biVers):
                        # 1) Found compatible software manifest in depot.
                        # 2) Retaining compatible firmware-only manifest.
                        hsiIsCompatible = True
                        manifests.AddManifest(manifest, replace=True)
                     break

               if not hsiIsCompatible:
                  # 1) Software manifest not found in depot.
                  # 2) Software manifest found but is incompatible with Base
                  #    Image.
                  # 3) Firmware-only manifest needs to be created with desired
                  #    Base Image as compatible version.
                  logFunc('Target HSI (%s, %s, %s) found in depot: %s, '
                          'compatible: %s', hsi.manager.name, hsi.package.name,
                          hsi.package.version, hasManifest, hsiIsCompatible)
                  if raiseException:
                     _raiseManifestError(hsi, baseImageVer, hasManifest)
                  if not hasManifest:
                     # Adding a new firmware-only HSP.
                     fwOnlyHsis.append(hsi)
         elif hsis:
            fwOnlyHsis = hsis
            logFunc('Depot manager is not set' if not self.depotMgr else
                    'Empty manifest list in depot manager')
            if raiseException:
               _raiseManifestError(hsis[0], None, False)
      except KeyError as err:
         errMsg = ('Missing hardware support %s attribute in the software spec.'
                   % err)
         field = '%s/%s' % (HARDWARE_SUPPORT, err)
         raise SoftwareSpecFormatError(field, errMsg)

      if IS_ESX:
         # For firmware-only HSP, create and store a manifest object on the host
         # to track HSP name and version. This is for scan to be able to return
         # the "current" firmware-only HPS name/version and distinguish against
         # the case where HSP has not been applied.
         # The ReleaseUnit name and version of the manifest are generated
         # in a fixed way, since firmware-only HSP name/version may not conform
         # to the requirements of the fields in a software manifest. The
         # generated manifest will be marked compatible with the desired Base
         # Image's 3-digit version.
         for hsi in fwOnlyHsis:
            manifestName = '%s%s' % (FIRMWARE_ONLY_PREFIX, uuid.uuid4().hex)
            manifest = Manifest()
            manifest.SetNameSpec(
               NameSpec(manifestName,
                        'Generated firmware-only manifest'))
            manifest.SetVersionSpec(
               VersionSpec('1.0-0',
                           'Hard-coded firmware-only manifest version'))
            manifest.SetVendor('VMware')
            manifest.SetReleaseDate(datetime.datetime.now())
            manifest.SetHardwareSupportInfo(hsi)
            manifest.SetSupportedBaseImageVersions([
               str(VibVersion.fromstring(self._getBaseImageVersion()).version)])
            log.debug('Adding firmware-only manifest for HSI (%s, %s, %s)',
                      hsi.manager.name, hsi.package.name, hsi.package.version)
            manifests.AddManifest(manifest, replace=True)

      for manifest in manifests.values():
         manifest.Validate(jsonSchemaCheck=False, schemaVersionCheck=True)

      return manifests

   def _findSolution(self, solutionName, solutionVersion):
      if self.depotMgr and self.depotMgr.solutions:
         for solution in self.depotMgr.solutions.values():
            if (solution.nameSpec.name == solutionName and
                solution.versionSpec.version.versionstring == solutionVersion):
               return solution
      raise SolutionNotFound(solutionName, solutionVersion,
                             'The solution (%s, %s) not found' %
                              (solutionName, solutionVersion))

   def _getBaseImage(self):
      """Get the base image object for the software spec.
      """
      try:
         return self._findBaseImage()
      except SoftwareSpecFormatError:
         raise
      except:
         return None

   def _getManifests(self):
      """Get the manifest object for the software spec.
      """
      try:
         return self._findManifests()
      except:
         return None

   def getManifests(self):
      """Get the manifest objects for the hardware support and
         base image in software spec. Raise exception on missing fields.
      """
      try:
         hardwareSupport = self.softwareSpec[HARDWARE_SUPPORT]
      except Exception as err:
         errMsg = ('Missing hardware support attribute in the software spec.')
         field = '%s/%s' % (HARDWARE_SUPPORT, err)
         raise SoftwareSpecFormatError(field, errMsg)

      try:
         baseImage = self.softwareSpec[BASE_IMG]
      except Exception as err:
         errMsg = ('Missing base image attribute in the software spec.')
         field = '%s/%s' % (BASE_IMG, err)
         raise SoftwareSpecFormatError(field, errMsg)

      return self._findManifests()

   def _getAddon(self):
      """Get the addon object for the software spec.
      """
      try:
         return self._findAddon()
      except:
         return None

   def _getSolutions(self):
      """Get solution objects for the software spec.
      """
      solutions = SolutionCollection()
      if SOLUTIONS in self.softwareSpec and self.softwareSpec[SOLUTIONS]:
         for solName, solInfo in self.softwareSpec[SOLUTIONS].items():
            try:
               solVersion = solInfo['version']
               solution = self._findSolution(solName, solVersion)
               solutions[solution.releaseID] = solution
            except (KeyError, SolutionNotFound) as e:
               log.warn('Failed to find solution: %s', str(e))
      return solutions

   def _addIntentComponents(self, comps, intents, opHistory, source,
                            ignoreErrors=False):
      """Helper function to loop through a component name/version list to:
         1. Add the component into intents.
         2. Add an add operation into operation history.
      """
      for name, version in comps:
         comp = self.getComponent(name, version, ignoreErrors)
         if isinstance(comp, Bulletin):
            intents.AddComponent(comp, True)
         opHistory[name].append(_AddOperation(version, source))

   def _resolveBaseImageIntents(self, source, intents, opHistory,
                                ignoreErrors=False):
      """Resolve intents from base image.
      """

      # Base image is always the first to be processed.
      assert intents != None and not intents

      baseimage = self._findBaseImage()
      baseImageCompIDs = baseimage.components
      self._addIntentComponents(baseImageCompIDs.items(), intents,
                                opHistory, source, ignoreErrors)
      return intents

   def _getSolutionConstraintCandidates(self, constraint):
      """Returns a list of candidate components for a solution constraint,
         sorted in version increase order.
      """
      compName = constraint.componentName
      if isinstance(constraint, ComponentConstraintList):
         versionList = sorted(constraint.versionList)
         candidates = []
         notFoundVers = []
         for ver in versionList:
            try:
               c = self.getComponent(compName, ver, ignoreErrors=False)
               candidates.append(c)
            except ComponentNotFoundError:
               notFoundVers.append(ver)
         if notFoundVers:
            # Some versions are not found. This is possible when we connect
            # to an effective component depot where the solution component
            # was pre-chosen.
            log.debug('Versions %s of solution component %s are not found '
                      'in depot.', ', '.join(notFoundVers), compName)
      else:
         # Range constraint
         candidates = sorted(constraint.MatchComponents(
                                self.depotMgr.components),
                             key=lambda c: c.compVersion)
      return candidates

   def _validateSolutionComponents(self, candidates, otherIntents, platforms):
      """Validate a combination of candidate solution components on platforms,
         returns True if the components pass validation, else stop at the first
         failed component and return False.
      """
      # Preliminary final components.
      tempIntents = ComponentCollection()
      tempIntents += otherIntents
      for comp in candidates:
         tempIntents.AddComponent(comp)

      if platforms:
         # ESXIO_ENABLED is True
         log.debug('Validating solution components: %s on platforms: %s',
                   ','.join([c.id for c in candidates]), ','.join(platforms))
         errors = dict()
         for p in platforms:
            # Validate temp combined intents on each platform. Different than
            # _validateCompsForPlatforms() we only scan errors for candidates.
            probs = tempIntents.Validate(self.depotMgr.vibs, tempIntents,
                                         platform=p)
            for comp in candidates:
               errors = probs.GetErrorsByComponent(comp)
               if errors:
                  log.debug('Solution component %s does not pass validation on '
                            'platform %s due to error(s): %s', comp.id, p,
                            errors.keys())
                  return False
         return True
      else:
         # ESXIO_ENABLED is False
         # Determine if all candidates do not cause validation issue.
         problems = tempIntents.Validate(self.depotMgr.vibs)
         for comp in candidates:
            # Warnings (full obsoletes) will be handled when intents are
            # finalized, they are not of concern.
            errors = problems.GetErrorsByComponent(comp)
            if errors:
               log.debug('Solution components %s do not pass validation due to '
                         'errors: %s', [c.id for c in candidates],
                         errors.keys())
               return False
         return True

   def _resolveSolutionIntents(self, source, intents, opHistory,
                               ignoreErrors=False):
      """Resolve intents from solutions.
      """
      def getPlatformsToValidate():
         """Returns a list of platforms to validate solution component with,
            or None to only validate all at once.
         """
         platforms = [GetHostSoftwarePlatform()]
         try:
            comp = intents.GetComponent('ESXi')
            if len(comp.platforms) > 1:
               # If ESXi component is cross-platform use its platforms.
               platforms = list(set([p.productLineID for p in comp.platforms]))
         except KeyError:
            pass
         return platforms

      platforms = getPlatformsToValidate()
      solutionsInfo = self.softwareSpec.get(SOLUTIONS, None)

      if solutionsInfo:
         for solutionName, solutionInfo in solutionsInfo.items():
            solution = self._findSolution(solutionName,
                                          solutionInfo['version'])
            compNames = [c['component'] for c in solutionInfo['components']]

            # Get a map of candidates for each component requested.
            candidatesMap = dict()
            for constraint in solution.componentConstraints:
               if constraint.componentName in compNames:
                  comps = self._getSolutionConstraintCandidates(constraint)
                  if comps:
                     candidatesMap[constraint.componentName] = comps

            missingComps = sorted(set(compNames) - set(candidatesMap.keys()))
            if missingComps:
               msg = ('Component(s) %s of Solution %s are not found in '
                      'depot' % (', '.join(missingComps), solution.releaseID))
               if ignoreErrors:
                  compNames = list(candidatesMap.keys())
                  log.warning(msg)
               else:
                  log.error(msg)
                  raise SolutionComponentNotFound(
                     missingComps,
                     solution.nameSpec.uiString,
                     "Desired components %s of Solution %s are not found in "
                     "depot" % (', '.join(missingComps), solutionName))

            numComps = len(candidatesMap)
            if numComps == 0:
               # Skip to next solution if no component has candidate.
               continue

            # Try all combinations of component candidates until we can validate
            # an image with a combination. The order to try is produced by
            # reverse sorting with the sum of list indexes, thus always prefer
            # components at the highest possible versions.
            # For example, with 2 components each have [1, 2] versions, we get
            # order: [(2, 2), (1, 2), (2, 1), (1, 1)].
            indexLists = [list(range(len(candidatesMap[n]))) for n in compNames]
            combinations = sorted(list(itertools.product(*indexLists)),
                                  key=sum, reverse=True)
            for indexes in combinations:
               compList = [candidatesMap[compNames[i]][indexes[i]]
                           for i in range(numComps)]
               if self._validateSolutionComponents(compList, intents,
                                                   platforms):
                  for comp in compList:
                     intents.AddComponent(comp)
                     opHistory[comp.compNameStr].append(
                        _AddOperation(comp.compVersionStr, source))
                  break
            else:
               if ignoreErrors:
                  log.warning('Cannot find compatible components %s in '
                              'solution %s, not adding any intents',
                              sorted(compNames), solutionName)
               else:
                  if numComps == 1:
                     # Single solution component is a special case that we can
                     # give more information in the error message.
                     cName = compNames[0]
                     # Use the highest versioned component's UI string for
                     # the exception.
                     cUiName = candidatesMap[cName][-1].compNameUiStr
                     versions = sorted(
                        [c.compVersionStr for c in candidatesMap[cName]])
                     raise IncompatibleSolutionComponentError(
                        cUiName,
                        solution.nameSpec.uiString,
                        versions,
                        'No compatible Component %s found in Solution %s, '
                        'candidate versions are: %s'
                        % (cName, solutionName, ', '.join(versions)))
                  else:
                     # With a combination of multiple components, the error
                     # will be simplified so we don't dump lists of versions
                     # on UI.
                     cNames = sorted(compNames)
                     # Use the highest versioned components' UI strings for
                     # the exception.
                     cUiName = [candidatesMap[n][-1].compNameUiStr
                                for n in cNames]
                     raise IncompatibleSolutionCompsError(
                        cUiName,
                        solution.nameSpec.uiString,
                        'No compatible combination of Components %s is found '
                        ' in Solution %s' % (cNames, solutionName))

      return intents

   def resolveSolutionConstraints(self, intents, ignoreErrors=False):
      """Resolve the solution constraints present inside the imageSpec on top
         of intents provided. Typically the currently installed components are
         passed in. Returns a ComponentCollection that contains intents from the
         solution constraints.

         Note: Only the 'solutions' field of the imageSpec is processed
               while resolving the solution constraints.
         Future: We can enhance this to use the rest of the imageSpec
                 when the intent is set to None.
      """
      opHistory = defaultdict(lambda: [])
      # allIntents is basically old components plus new solution intents,
      # obsoletion between them are not considered.
      allIntents = self._resolveSolutionIntents(source=RESOLUTION_TYPE_SOLUTION,
                                                intents=intents,
                                                opHistory=opHistory,
                                                ignoreErrors=ignoreErrors)

      # Use opHistory to form the collection of new intents, caller is
      # responsible to scan existing components with them to sort out what to
      # remove should there be obsoletion.
      newIntents = ComponentCollection()
      for name, actions in opHistory.items():
         for action in actions:
            if not isinstance(action, _AddOperation):
               raise RuntimeError('Unexpected action %s returned when '
                  'resolving solution intents' % action.__class__.__name__)
            newIntents.AddComponent(
               allIntents.GetComponent(name, action.version))
      return newIntents

   def _resolveSingleAddon(self, addon, source, intents, opHistory,
                           ignoreErrors=False):
      """Resolve intents from a single addon or manifest.
      """
      if not addon:
         return

      for name in addon.removedComponents:
         if name in intents:
            del intents[name]
         opHistory[name].append(_RemoveOperation(source))

      self._addIntentComponents(addon.components.items(), intents,
                                opHistory, source, ignoreErrors)

   def _resolveAddonIntents(self, source, intents, opHistory,
                            ignoreErrors=False):
      """Resolve intents from addon.
      """
      self._resolveSingleAddon(self._findAddon(), source, intents,
                               opHistory, ignoreErrors)
      return intents

   def _resolveManifestIntents(self, source, intents, opHistory,
                               ignoreErrors=False):
      """Resolve intents from manifest.
      """
      manifests = self._findManifests()
      if manifests:
         # XXX: in 7.0, only support one Manifest.
         manifest = list(manifests.values())[0]
         self._resolveSingleAddon(manifest, source, intents,
                                  opHistory, ignoreErrors)
      return intents

   def _resolveUserComponents(self, source, intents, opHistory,
                              ignoreErrors=False):
      """Resolve intents from the user components in software spec.
      """
      componentDict = self.softwareSpec.get('components')
      if componentDict:
         self._addIntentComponents(componentDict.items(), intents,
                                   opHistory, source, ignoreErrors)
      return intents


   def _resolveComponentRemovals(self, source, intents, opHistory,
                                 ignoreErrors=False):
      """Resolve user removed components.
         This also populates the removable components stored for effective
         component info.
      """
      if not PERSONALITY_MANAGER_COMPONENT_REMOVAL_ENABLED:
         return intents

      # Reset in case of multiple effective components calls.
      self._removableComps = set()

      removedComps = self.softwareSpec.get('removed_components', []) or []

      # First, handle components that are not found.
      for compName in set(removedComps) - set(opHistory.keys()):
         msg = ("Component to remove: {} does not exist in effective "
                "components.".format(compName))
         if ignoreErrors:
            log.warning(msg)
         else:
            raise RemovedComponentNotFound(compName, msg)

      # Check if components can be removed, subject to these rules:
      # 1. If a component is only included in the addon or the manifest, it can
      #    be removed.
      # 2. If a component is found in the Base Image, it can be removed only if
      #    it is in the allowed Base Image component removal list, even if the
      #    addon or the manifest overrides the Base Image version.
      # 3. If a component is already removed by the addon, it will be skipped.
      # 4. If a component is only included in the user component list, it can
      #    be removed.
      # 5. A component cannot be removed if found in a solution.

      for compName in opHistory:

         # Ignore component already removed by the addon.
         # Such component is also not user removable.
         if isinstance(opHistory[compName][-1], _RemoveOperation):
            log.info("Skip user removed component: %s that is already "
                     "removed.", compName)
            continue

         isCompRemovable = True
         isCompInRemovedList = compName in removedComps

         for op in opHistory[compName]:
            # Go over all components to both determine whether a component is
            # removable and handle removed components in the desired image.
            # Raise/log only when a component is removed in the desired image.

            if (isinstance(op, _AddOperation) and
                  op.source == RESOLUTION_TYPE_BASEIMAGE and
                  compName not in BASE_IMAGE_REMOVABLE_COMPS):
               # Against rule 2: Base Image component that cannot be removed.
               isCompRemovable = False
               if isCompInRemovedList:
                  msg = ("Component to remove: {} is a part of the Base Image "
                         "and is not in the list of components allowed to be "
                         "removed.".format(compName))
                  if ignoreErrors:
                     log.warning(msg)
                  else:
                     raise UnsupportedRemovedComponent(compName, msg)

            if (isinstance(op, _AddOperation) and
                op.source == RESOLUTION_TYPE_SOLUTION):
               # Against rule 5.
               isCompRemovable = False
               if isCompInRemovedList:
                  msg = ("Component to remove: {} is found in a solution and "
                         "cannot be removed.".format(compName))
                  if ignoreErrors:
                     log.warning(msg)
                  else:
                     raise UnsupportedRemovedComponent(compName, msg)

            if (isinstance(op, _RemoveOperation) and
                op.source == RESOLUTION_TYPE_ADDON and isCompInRemovedList):
               # Rule 4.
               log.info("Component to remove: %s is already removed by the "
                        "addon, skipping.", compName)
               continue

            if (isinstance(op, _AddOperation) and
                op.source == RESOLUTION_TYPE_USERCOMP and isCompInRemovedList):
               log.info("Component to remove: %s is found in the user "
                        "component.", compName)

         if isCompRemovable:
            self._removableComps.add(compName)
            if isCompInRemovedList:
               log.info("Removing component: %s per user request.", compName)
               opHistory[compName].append(_RemoveOperation(source))
               del intents[compName]

      return intents

   def _checkDowngradeAndRemovalFromHistory(self, opHistory, ignoreErrors):
      """Check whether there are any downgrade or user removal in the
         operations. Add a warning notfication In case of supported removal or
         downgrade, and an error notification in case of unsupported downgrade.
         Unsupported removal is checked in _resolveComponentRemovals.
         Returns list of warnings.
      """
      # Reset in case of multiple effective components calls.
      self._retainedComps = set()

      warnings = []
      for name in opHistory:
         opsForName = opHistory[name]
         # Start from the most recent operation.
         lastOp = opsForName[-1]
         if isinstance(lastOp, _RemoveOperation):
            if (PERSONALITY_MANAGER_COMPONENT_REMOVAL_ENABLED and
                lastOp.source == RESOLUTION_TYPE_REMOVED_COMP):
               # For user removal, append warning.
               # Loop for last add operation.
               for i in range(len(opsForName) - 2, -1, -1):
                  if isinstance(opsForName[i], _AddOperation):
                     component = self.getComponent(name, opsForName[i].version)
                     msgArgs = (component.compNameUiStr,
                                component.compVersionUiStr,
                                opsForName[i].source)
                     warnings.append(Notification(COMP_REMOVAL_WARNING_ID,
                                                  COMP_REMOVAL_WARNING_ID,
                                                  COMP_REMOVAL_WARNING_MSG,
                                                  COMP_REMOVAL_RES_ID,
                                                  COMP_REMOVAL_RES_MSG,
                                                  msgArgs=msgArgs).toDict())
                     log.warning('Component %s version %s from %s will be '
                                 'removed. In some scenarios, this might cause '
                                 'run time issues even after passing the '
                                 'metadata level validation.' % msgArgs)
                     break
            # The last op is removal so ignore this component.
            continue
         # Loop the older add operations.
         for i in range(len(opsForName) - 2, -1, -1):
            op = opsForName[i]
            if isinstance(op, _RemoveOperation):
               continue
            warning = self._checkDowngrade(name, lastOp.version, op.version,
               lastOp.source, op.source, ignoreErrors)
            if warning:
               warnings.append(warning)
      return warnings

   def _convertIssuesToNotifications(self, problems=None, exceptions=None):
      """Converts problems or excpetions into 'Notification's.
         Return dict of different types of notifications.
      """
      notifications = {'info': [], 'warnings': [], 'errors': []}
      if exceptions:
         # Collect all exceptions during effective component calculation
         # and convert them to notification dicts
         for ex in exceptions:
            notifications['errors'].append(
               Utils.getExceptionNotification(ex).toDict())
      else:
         errors, warnings = problems.ToNotificationLists()
         if warnings:
            notifications['warnings'] = warnings
         if errors:
            notifications['errors'] = errors
         else:
            notifications['info'].append(Notification(VALIDATE_SUCCESS_ID,
                                                      VALIDATE_SUCCESS_ID,
                                                      VALIDATE_SUCCESS_MSG,
                                                      '', '').toDict())
      return notifications

   def _resolveAllIntents(self, ignoreErrors=False, collectExceptions=False,
                          removeObsoletedComp=True):
      """Resolve all the intents specified in Software spec and return
         components as a ComponentCollection.

         Parameters:
            * ignoreErrors      - When set to True, make best efforts to
                                  calculate components and do not raise an
                                  exception.
            * collectExceptions - When set, collects exceptions while
                                  calculating components and return them as a
                                  list. Must not be used with ignoreErrors.
            * removeObsoletedComp - When set, remove components obsoleted by
                                    VIB relations.
                                    See validateAndReturnImageProfile() for
                                    more details.
         Returns:
            * A tuple of ComponentCollection, map of components to its source
              and a collection containing info, warning and error messages.
      """
      assert not(ignoreErrors and collectExceptions), \
             "collectExceptions must not be used with ignoreErrors"
      exceptions = []
      # TODO: Move _checkAddonBaseImageCompatibility call
      #       outside of _resolveAllIntents as part of refactoring
      #       of this code because it acts at level higher than components.
      try:
         self._checkAddonBaseImageCompatibility()
      except Exception as err:
         if collectExceptions:
            exceptions.append(err)
            if isinstance(err, AddonBaseImageMismatchError):
               return None, None, self._convertIssuesToNotifications(
                  None, exceptions)
         elif not ignoreErrors:
            raise err

      intents = ComponentCollection()
      opHistory = defaultdict(lambda: [])
      for source in SoftwareSpecMgr.INTENT_RESOLUTION_MAP:
         calcFunc = SoftwareSpecMgr.INTENT_RESOLUTION_MAP[source]
         if calcFunc:
            try:
               intents = calcFunc(self, source, intents, opHistory,
                                  ignoreErrors)
            except (AddonNotFound, BaseImageNotFound, ComponentNotFoundError,
                    HardwareSupportPackageNotFound,
                    IncompatibleSolutionCompsError,
                    ManifestBaseImageMismatchError,
                    MultipleManifestError, ReleaseUnitSchemaVersionError,
                    RemovedComponentNotFound, SoftwareSpecFormatError,
                    SolutionComponentNotFound, SolutionNotFound,
                    UnsupportedRemovedComponent) as e:
               if collectExceptions:
                  exceptions.append(e)
               else:
                  raise
      try:
         warnings = self._checkDowngradeAndRemovalFromHistory(opHistory,
                                                              ignoreErrors)
      except Exception as e:
         if collectExceptions:
            exceptions.append(e)
         else:
            raise

      if exceptions:
         return intents, opHistory, self._convertIssuesToNotifications(
            None, exceptions)

      compRelProblems = None
      try:
         raiseException = not ignoreErrors and not collectExceptions
         intents, opHistory, compRelProblems = \
            self._validateCompsForPlatforms(intents, opHistory,
                                            removeObsoletedComp,
                                            raiseException)
      except Exception as e:
         if collectExceptions:
            exceptions.append(e)
         elif ignoreErrors:
            log.error(str(e))
         else:
            raise

      notifications = self._convertIssuesToNotifications(compRelProblems,
         exceptions)
      notifications['warnings'].extend(warnings)
      return intents, opHistory, notifications

   def setComponent(self, componentName, componentVersion):
      """Add the specified component to the desired image spec.

         Add or update the component and its version in the desired
         state document.
         Validate the complete software spec.
         Write out the validated softwareSpec to staging area.
         Parameters:
            * componentName: Name of the component to be added or updated.
            * componentVersion: Version of the component.
         Returns: None
         Raises:
            * ComponentNotFoundError: If the caller tries to add/update a
              component that is not found in the in the depot or the currently
              running image.
            * Exceptions raised by the validateAndReturnImageProfile() and
              setStagedSoftwareSpec() functions need to be handled by the
              caller
      """
      # Check if component already set at the desired version.
      if componentName in self.softwareSpec['components']:
         if self.softwareSpec['components'][componentName] == componentVersion:
            return

      # Check if the component is available in the depot
      if not self.depotMgr.components.HasComponent(componentName,
                                                   componentVersion):
         raise ComponentNotFoundError(componentName,
                                      "Unknown component %s version %s"
                                      % (componentName, componentVersion))

      self.softwareSpec['components'][componentName] = componentVersion
      self.validateAndReturnImageProfile()
      StagingArea.setStagedSoftwareSpec(self.softwareSpec)

   def setEsx(self, esxVersion):
      """Set a the version of ESX in the desired image spec.

         Update the version of ESX specified in the desired state
         document.
         Validate the complete softwareSpec.
         Write out the validated softwareSpec to staging area.
         Parameters:
            * esxVersion: A string representing the ESX version.
         Returns:
            * None
         Raises:
            * ComponentNotFoundError: If the ESX version is different from what
              is already installed and is not found in the depot.
            * Exceptions raised by the validateAndReturnImageProfile() and
              setStagedSoftwareSpec() functions need to be handled by the
              caller
      """
      if esxVersion == self.softwareSpec['esx']:
         return

      try:
         self.depotMgr.components.GetComponent('ESXi', esxVersion)
         self.softwareSpec['esx'] = esxVersion
      except KeyError:
         raise ComponentNotFoundError("ESXi",
                                      "Cannot find specified ESX version: %s"
                                      % esxVersion)

      self.validateAndReturnImageProfile()
      StagingArea.setStagedSoftwareSpec(self.softwareSpec)

   def deleteComponent(self, componentName):
      """Delete a component specified in the desired image spec.

         Remove a component with 'componentName' from the desired state
         document. If the componentName is not found in the document then it
         handles the KeyError in 2 ways depending on the validity of the
         componentName.
         (1) If the componentName is valid then the function does not throw
            any error and it returns.
         (2) If the componentName is not found in the depots then it throws the
            ComponentNotFoundError exception.

         After successful delete, it runs the validation on the complete
         document to make sure that a valid image can be created.
         Exceptions thrown by the validator will be moved up the stack and
         the staging area is not updated in this case.
         After successful validation, the softwareSpec in the staging area
         will be replaced with the updated desired state document.

         Parameters:
            * componenName: Name of the component to be deleted.
         Returns:
            * None: In case of success None is returned.
         Raises:
            * ComponentNotFoundError: If the caller tries to delete a component
              that is not found in the SoftwareSpec and also in the depot.
            * Exceptions raised by the validateAndReturnImageProfile() and
              setStagedSoftwareSpec() functions need to be handled by the
              caller
      """
      try:
         del self.softwareSpec.get('components')[componentName]
      except KeyError:
         if componentName in self.depotMgr.components.keys():
            log.info("ComponentName: %s is valid but absent in softwareSpec",
                     componentName)
            return
         else:
            raise ComponentNotFoundError(componentName,
                                         "Unknown component %s" % componentName)
      self.validateAndReturnImageProfile()
      StagingArea.setStagedSoftwareSpec(self.softwareSpec)

   def getComponentVibs(self, comp):
      """Returns a VibCollection of VIBs that belongs to the component.
      """
      vibDict = dict((vibId, self.depotMgr.vibs[vibId])
                      for vibId in comp.vibids)
      return VibCollection(vibDict)

   def _checkAddonBaseImageCompatibility(self):
      """Performs the compatibility check between addon and base image
         comparising SoftwareSpec.

         Returns:
            * returns True if they are compatible else False

         Exception:
            * AddonNotFound: If addon is missing in the depot
            * AddonBaseImageMismatchError: If the addon is incompatible with
                                           the base image
            * SoftwareSpecFormatError: If addon or base image is missing
                                       attributes version and/or name
                                       (only applicable for addon)
      """
      addon = self._findAddon()
      if addon:
         if self._matchBaseImage(addon.supportedBaseImageVersions):
            return
         baseImageVer = self._getBaseImageVersion()
         errMsg = ('Addon %s does not support base image %s' %
                   (addon.releaseID, baseImageVer))
         raise AddonBaseImageMismatchError(addon.releaseID,
                                              baseImageVer, errMsg)

   def validateAndReturnNotifications(self):
      """Validates the image.

         This resolves all the intents specified in the image.
         It tries to provide resolution to each problem.

         Returns:
            * A dict of Notification object having lists of info, warnings
              and errors.
      """
      _, _, notifications = self._resolveAllIntents(collectExceptions=True)
      log.info('Image validation result: %s' % notifications)
      return notifications

   def _getComponentRelationProblems(self, components):
      """Collects all component relation problems and possible resolutions

         Parameters:
            * components - A ComponentCollection object to perform component
                           validation on.

         Returns:
            * A ValidateResult object having a list of ComponentScanProblem
              with possible resolution to each problem.
      """
      # Skip reserved components that do not have reserved VIBs.
      allComponents = self.depotMgr.componentsWithVibs

      # Scan on allComponents to offer resolutions.
      return allComponents.Validate(self.depotMgr.vibs, components)

   def _validateCompsForPlatforms(self, intents, opHistory,
                                  removeObsoletedComp, raiseException):
      """Validate intented components for all platforms, returns intents,
         opHistory and validation errors (set only when raiseException is
         False).
      """

      # Skip reserved components that do not have reserved VIBs.
      allComponents = self.depotMgr.componentsWithVibs

      allProbs = None
      allErrors = None
      fullyObComps = None

      platforms = intents.GetSoftwarePlatforms(baseImageOnly=True)
      for p in platforms:
         probs = allComponents.Validate(self.depotMgr.vibs, intents, platform=p)
         errors, warnings = probs.GetErrorsAndWarnings()

         if allProbs == None:
            allProbs = probs
         else:
            allProbs += probs

         if errors:
            if allErrors == None:
               allErrors = errors
            else:
               allErrors.update(errors)
         elif warnings:
            # In case of full component obsolete, i.e. all VIBs in compA -o->
            # all VIBs in compB despite different component names, and that we
            # are generating the effective image instead of exporting, we need
            # to remove the obsoleted component(s).

            # Sort by ID of problems to yield consistent result.
            obsoletes = sorted(warnings.values(), key=lambda p: p.id)
            platformObComps = set([
               p.replacesComp for p in obsoletes
               if intents.HasComponent(compId=p.replacesComp)])
            if fullyObComps == None:
               fullyObComps = platformObComps
            elif platformObComps != fullyObComps:
               # Fully obsoleted component(s) must be consistent across
               # platforms, otherwise we cannot produce an effective image.
               msg = ("Inconsistent list of fully obsoleted component(s) on "
                      "platform %s compared to other platform(s): '%s' != '%s'"
                      % (p, ','.join(platformObComps), ','.join(fullyObComps)))
               raise ComponentValidationError(msg)

      if allErrors and raiseException:
         msg = ('Validation of components found problems: %s'
                % ','.join([str(prob) for prob in allErrors.values()]))
         raise ComponentValidationError(msg)

      if fullyObComps and removeObsoletedComp:
         # Now remove obsoleted component(s) from intents/opHistory.
         log.info('Removing fully obsoleted component(s) %s from the image.',
                  ','.join(fullyObComps))
         for cId in fullyObComps:
            comp = intents.GetComponent(compId=cId)
            del opHistory[comp.compNameStr]
            intents.RemoveComponent(cId)

      return intents, opHistory, allProbs

   def validateAndReturnImageProfile(self,
                                     name="VMware Lifecycle Manager Generated Image",
                                     creator="VMware, Inc.",
                                     checkAcceptance=True,
                                     removeObsoletedComp=True,
                                     coreCompCheck=True):
      """Returns the validated Image profile after validation.

         Creates the ImageProfile after validation and returns it if the
         ImageProfile.Validate() succeeds. This is to make sure that a valid
         image can be created.

         Parameters:
            name: A friendly string identifying the profile to end users.
            creator: A string identifying the organization or person who
                     created or modified this profile.
            checkAcceptance: If True, validate the Acceptance Level of
                             each VIB. If the validation fails, an
                             exception is raised. Defaults to True.
            removeObsoletedComp: If True, components obsoleted by VIB relations
                                 will be removed. Otherwise the components are
                                 kept in the image profile. Caution: should be
                                 used only when exporting a depot and not for
                                 effective components, host scan/apply and ISO.
            coreCompCheck: If True, checks for existence of the core components.
         Returns:
            * Returns an ImageProfile object in case of successful validation.
         Raises:
            ComponentValidationError: If validation of the softwareSpec fails.
            AddonBaseImageMismatchError: If addon is not compatible with
                                         the base image in the software spec.
      """
      finalComponents, opHistory, _ = self._resolveAllIntents(
                                       removeObsoletedComp=removeObsoletedComp)

      if len(finalComponents.GetComponentIds()) == 0:
         raise ComponentValidationError("Validation failed. Zero components"
                                        " found.")

      finalVibs = finalComponents.GetVibCollection(self.depotMgr.vibs)

      baseImage = self._getBaseImage()
      baseImageID = baseImage.releaseID if baseImage else None

      addon = self._getAddon()
      addonID = addon.releaseID if addon else None

      solutions = self._getSolutions()
      solutionIDs = set(solutions.keys())

      manifests = self._getManifests()
      manifestIDs = set(manifests.keys()) if manifests else set()

      reservedCIDs = set()
      if baseImage:
         reservedCIDs.update(
            set(baseImage.CollectReservedComponents(finalComponents)))
      if addon:
         reservedCIDs.update(
            set(addon.CollectReservedComponents(finalComponents)))

      if manifests:
         for manifest in manifests.values():
            reservedCIDs.update(
               set(manifest.CollectReservedComponents(finalComponents)))

      reservedComps = ComponentCollection()
      for cName, version in reservedCIDs:
         comp = self.depotMgr.components.GetComponent(cName, version)
         reservedComps.AddComponent(comp)
      reservedVibs = reservedComps.GetVibCollection(self.depotMgr.vibs)

      # Add removed components metadata.
      removedCompNames = []
      if PERSONALITY_MANAGER_COMPONENT_REMOVAL_ENABLED:
         removedCompNames = [name for name, actions in opHistory.items()
            if isinstance(actions[-1], _RemoveOperation) and
               actions[-1].source == RESOLUTION_TYPE_REMOVED_COMP]

      # Create the image profile.
      profile = ImageProfile(
         name,
         creator,
         vibIDs=list(finalVibs.keys()),
         vibs=finalVibs,
         componentIDs=set(finalComponents.GetComponentIds()),
         components=finalComponents,
         baseimageID=baseImageID,
         baseimage=baseImage,
         addonID=addonID,
         addon=addon,
         solutionIDs=solutionIDs,
         solutions=solutions,
         manifestIDs=manifestIDs,
         manifests=manifests,
         reservedComponentIDs=list(reservedCIDs),
         reservedComponents=reservedComps,
         reservedVibIDs=set(reservedVibs.keys()),
         reservedVibs=reservedVibs,
         removedComponents=removedCompNames)

      if IS_ESX:
         # When this logic is running in ESX(scan/apply), set the acceptance
         # level of the desired image profile to that of the host and evaluate
         # the VIBs against that.
         hostAcl = HostImage().GetHostAcceptance()

         if hostAcl in ArFileVib.ACCEPTANCE_LEVELS:
            profile.acceptancelevel = hostAcl
         else:
            # hostAcl can be invalid or empty
            logging.warning("Invalid host acceptance level '%s', setting image "
                            "profile acceptance using VIBs", hostAcl)
            profile.SetDefaultAcceptance()
      else:
         # On VC or in a build, set image profile with the default acceptance
         # level, i.e. community when such VIB exists, else partner.
         profile.SetDefaultAcceptance()

      log.info("Created ImageProfile with the final set of vibs and "
               "components, acceptance level '%s'", profile.acceptancelevel)

      # Run validation on the IP to make sure that a valid image can be created.
      # * Turn off VIB dependency/conflict/obsolete checks as we have already
      #   done component-based validations in _resolveAllIntents().
      # * Turn off file conflict check when removeObsoletedComp is set during
      #   export, as obsoleted VIB can conflict with the obsoleter. We merely
      #   use the image profile as a vehicle to ship all needed components in
      #   export.
      problems = profile.Validate(nodeps=True,
                                  noconflicts=True,
                                  allowobsoletes=True,
                                  noacceptance=not checkAcceptance,
                                  allowfileconflicts=not removeObsoletedComp,
                                  coreCompCheck=coreCompCheck)
      if len(problems):
         problems = [str(x) for x in list(problems)]
         raise ComponentValidationError('Final validation failed with these'
                                        ' errors: "%s"' % (problems))
      log.info("Validation succeeded!")

      return profile

   def _checkDowngrade(self, name, version, version1, source, source1,
                       ignoreErrors):
      """Check whether it is a downgrade.
         Returns a warning 'Notification' if image customization is allowed else
         None.
         Throws an instance of ComponentDowngradeError if ignoreErrors is
         False and an unsupported downgrade is found.
      """
      if VibVersion.fromstring(version) < VibVersion.fromstring(version1):
         component, component1 = self.getComponent(name, version), \
            self.getComponent(name, version1)
         esxioComp = component.HasPlatform(SoftwarePlatform.PRODUCT_ESXIO_ARM)
         msgArgs = (component.compNameUiStr, component.compVersionUiStr,
            component1.compVersionUiStr, source1)
         msg = ('Component %s retained at %s instead of %s from %s. In rare '
                'scenarios, it might cause run time issues even after passing '
                'the metadata level validation.' % (name, version, version1,
                 source1))

         if (source == RESOLUTION_TYPE_USERCOMP and
             ((not esxioComp and not component.hasConfigSchema) or
              ignoreErrors)):
            # Allow downgrade/retention by a user component if it is not an
            # ESXio component and does not have config schema, or when
            # ignoreErrors is True.
            log.warning(msg)
            self._retainedComps.add(name)
            return Notification(COMP_RETENTION_WARNING_ID,
               COMP_RETENTION_WARNING_ID, COMP_RETENTION_WARNING_MSG,
               COMP_RETENTION_RES_ID, COMP_RETENTION_RES_MSG,
               msgArgs=msgArgs).toDict()
         msg = 'Component %s downgraded from %s to %s.' % (name, version1,
                                                           version)

         if ignoreErrors:
            # Ignore release unit component downgrade errors.
            log.warning(msg)
         elif esxioComp:
            msg += " Operation is not supported for ESXio components."
            raise ESXioComponentDowngradeError([name], msg)
         elif component.hasConfigSchema:
            msg = ("Component '%s' cannot be downgraded as it contains a "
                   "config schema." % component.compNameUiStr)
            raise ConfigComponentDowngradeError([component.compNameUiStr], msg)
         else:
            raise ComponentDowngradeError([name], msg)

   def _createOverride(self, comp, source1, source2, ignoreErrors):
      """Create component override info object.
      """
      comp = self.getComponent(comp[0], comp[1], ignoreErrors)
      version, uiVersion = _getComponentVersions(comp)
      note = _createOverrideNote(source1, source2)
      return _ComponentOverrideInfo(version, uiVersion, source2, note)

   def _createCompInfo(self, comp, compSource, ignoreErrors, retained=False,
                       removable=False, removed=False):
      """Create effective component info object.
            retained: True if the component has been retained at a lower
                      version.
            removable: True if the component can be removed by the user.
            removed: True if the component has been removed by the user.
      """
      comp = self.getComponent(comp[0], comp[1], ignoreErrors)
      note = _createSourceNote(compSource)
      _, uiName = _getComponentNames(comp)
      version, uiVersion = _getComponentVersions(comp)
      vendor = comp.vendor if isinstance(comp, Bulletin) else ''

      if removed:
         # Component is removed.
         userAction = IMAGE_CUSTOMIZATION_ACTION_REMOVED
         userDesc = _LocalizableMessage(IMAGE_CUSTOMIZATION_RM_COMP_ID,
                                        IMAGE_CUSTOMIZATION_RM_COMP_MSG)
         # For a removed component, the version will be empty.
         version, uiVersion = '', ''
      elif retained:
         # Component is retained/downgraded.
         userAction = IMAGE_CUSTOMIZATION_ACTION_RETAINED
         userDesc = _LocalizableMessage(IMAGE_CUSTOMIZATION_RT_COMP_ID,
                                        IMAGE_CUSTOMIZATION_RT_COMP_MSG)
      else:
         userAction = None
         userDesc = None

      return _EffectiveComponentInfo(version, uiName, uiVersion, vendor,
                                     compSource, note, [], removable,
                                     userAction, userDesc)

   def _createRemovedCompOverride(self, compPair, compSource, ignoreErrors):
      """Creates component override info for a removed component.
      """
      compName, compVer = compPair
      comp = self.getComponent(compName, compVer, ignoreErrors)
      version, uiVersion = _getComponentVersions(comp)
      note = _LocalizableMessage(USER_RM_COMP_OVERRIDE_ID,
                                 USER_RM_COMP_OVERRIDE_MSG)
      return _ComponentOverrideInfo(version, uiVersion, compSource, note)

   def AddReservedComponent(self, name, op, sourceMapVAPI,
                            ignoreErrors, reservedComp, removable=False,
                            removed=False):
      """ Add a reserved component to the out parameter 'reservedComp'.
            removable: True of the component can be removed by the user.
            removed: True if the component has been removed by the user.
      """
      oldComp = (name, op.version)
      oldCompSource = sourceMapVAPI[op.source]
      compInfo = self._createCompInfo(oldComp, oldCompSource, ignoreErrors,
                                      removable=removable, removed=removed)
      if removed:
         # When a component has been removed by user, a special overridden
         # component is added to contain the removed version.
         compInfo.details.overridden_components.append(
            self._createRemovedCompOverride(oldComp, oldCompSource,
                                            ignoreErrors))
      reservedComp[name].append(compInfo)

   def generateEffectiveComponent(self, ignoreErrors=False,
                                  removeObsoletedComp=True,
                                  keepRemovedComps=False):
      """Generate the effective and reserved component information list.
         Parameters:
            ignoreErrors: If True, do not raise an exception and generate
                          effective components on a best-effort basis.
            removeObsoletedComp: If True, components replaced via VIB relations
                                 will be removed.
            keepRemovedComps: If True, add the last version of a removed
                              component back as if it is not removed. This is
                              used for the effective component
                              listwithremovedcomponents VAPI.
      """
      _, opHistory, _ = self._resolveAllIntents(
                              ignoreErrors=ignoreErrors,
                              removeObsoletedComp=removeObsoletedComp)

      # Map from resolution step to component source type.
      sourceMapVAPI = {RESOLUTION_TYPE_BASEIMAGE: SOURCE_TYPE_BASEIMAGE,
                       RESOLUTION_TYPE_ADDON: SOURCE_TYPE_ADDON,
                       RESOLUTION_TYPE_MANIFEST: SOURCE_TYPE_MANIFEST,
                       RESOLUTION_TYPE_USERCOMP: SOURCE_TYPE_USER,
                       RESOLUTION_TYPE_SOLUTION: SOURCE_TYPE_SOLUTION}

      effComp = {}
      reservedComp = defaultdict(lambda: [])
      for name in opHistory:
         opsForName = opHistory[name]
         # Start from the most recent operation.
         lastOp = opsForName[-1]

         # Whether component is removed by user.
         compRemoved = False

         if isinstance(lastOp, _RemoveOperation):
            # Component removed by addon or user.

            compRemoved = (PERSONALITY_MANAGER_COMPONENT_REMOVAL_ENABLED and
                           lastOp.source == RESOLUTION_TYPE_REMOVED_COMP)

            # Add reserved components.
            # Even with keepRemovedComps reserved components will be added for a
            # removed component when it is in a release unit.
            for i in range(len(opsForName) - 2, -1, -1):
               if (isinstance(opsForName[i], _AddOperation) and
                   opsForName[i].source != RESOLUTION_TYPE_USERCOMP):
                  # A component is reserved only if it is added by a release
                  # unit.
                  self.AddReservedComponent(name, opsForName[i],
                     sourceMapVAPI, ignoreErrors, reservedComp,
                     removable=name in self._removableComps,
                     removed=compRemoved)

            if compRemoved and keepRemovedComps:
               # With keepRemovedComps, locate the last add operation for a
               # removed component. This determines the source of the component
               # for effective components.
               for i in range(len(opsForName) - 2, -1, -1):
                  if isinstance(opsForName[i], _AddOperation):
                     lastOp = opsForName[i]
                     break
            else:
               # Ignore a removed component in effective components.
               continue

         # For a removed component, source is changed to user removed.
         compSource = sourceMapVAPI[lastOp.source] if not compRemoved \
            else SOURCE_USER_REMOVED

         comp = (name, lastOp.version)
         # The last op is add; so add an effective component.
         effComp[name] = self._createCompInfo(comp, compSource, ignoreErrors,
            retained=name in self._retainedComps,
            removable=name in self._removableComps, removed=compRemoved)

         overrdComps = effComp[name].details.overridden_components

         if compRemoved:
            # When a component has been removed by user but keepRemovedComps is
            # set:
            # Skip adding any reserved component;
            # A special overridden component is added to contain the removed
            # version. UI uses this information to display a striked-out
            # version.
            overrdComps.append(
               self._createRemovedCompOverride(comp,
                                               sourceMapVAPI[lastOp.source],
                                               ignoreErrors))
            continue

         # Loop the older add operations as override.
         for i in range(len(opsForName) - 2, -1, -1):
            op = opsForName[i]
            if isinstance(op, _RemoveOperation):
               continue

            oldComp = (name, op.version)
            oldCompSource = sourceMapVAPI[op.source]
            overrdComps.append(self._createOverride(oldComp, compSource,
                                  oldCompSource, ignoreErrors))

            if (not isinstance(lastOp, _AddOperation) or
                lastOp.version != op.version):
               # Add reserved component info for the overridden component,
               # except:
               # - The last operation is a removal (already handled above).
               # - The component is repeated after removal/override, such as in
               #   case of a user component that was removed by the addon, or a
               #   component that was overridden and then retained.
               self.AddReservedComponent(name, op, sourceMapVAPI,
                                         ignoreErrors, reservedComp,
                                         removable=name in self._removableComps)
      return effComp, reservedComp

   def export(self, exportFormats, exportLocation, exportFileNameHash,
              allowPartialDepot=False):
      '''Export an iso image (with or without installer), a depot or
         an offline bundle.

         Parameters:
            exportFormats: The export formats.
            exportLocation: Export directory.
            exportFileNameHash: Hash string for file name generation.
            allowPartialDepot: Whether allow export when VIB files missing.
         Exception:
            ValueError: when exportFormats are invalid or exportLocation is not
                        a directory.
            MissingVibError: when some of the reserved VIBs in the ImageProfile
                             are not found in the depot.
         Returns:
            The map from format to exported content.
      '''
      exportFileNameHash = '_' + exportFileNameHash
      msg = None
      unknownFormats = set(exportFormats) - set(EXPORT_FORMATS)
      if not os.path.isdir(exportLocation):
         msg = 'Export location %s is not a directory.' % exportLocation
      elif unknownFormats:
         msg = 'Invalid export format(s): %s' % ', '.join(unknownFormats)
      elif (FORMAT_ISO_IMAGE in exportFormats and
          FORMAT_ISO_IMAGE_INSTALLER in exportFormats):
         msg = 'Installer iso and iso cannot be exported at the same time.'

      if msg:
         log.error(msg)
         raise ValueError(msg)

      exportMap = {}

      if (FORMAT_ISO_IMAGE in exportFormats or
          FORMAT_ISO_IMAGE_INSTALLER in exportFormats):
         incInstaller = FORMAT_ISO_IMAGE_INSTALLER in exportFormats

         # Solutions are managed by appliances such as vCenter. So don't export
         # them into ISO image. Remove solutions from software spec, generate
         # image profile and then add back to recover software spec.
         hasSolution = SOLUTIONS in self.softwareSpec
         solutionSection = self.softwareSpec.get(SOLUTIONS, None)
         if hasSolution:
            del self.softwareSpec[SOLUTIONS]
         isoProfile = deepcopy(self.validateAndReturnImageProfile())
         if hasSolution:
            self.softwareSpec[SOLUTIONS] = solutionSection

         # Get platform(s) of the VIBs.
         platforms = isoProfile.GetSoftwarePlatforms(fillDefaultValue=False,
                                                     baseEsxOnly=True)
         # If more than 1 platform: assume it is a unified ESXi image; make an
         # ISO with embeddedEsx as the main platform.
         # If exactly 1 platform: use it as the main platform for the ISO.
         # If no platform: assume it is a legacy ESXi image that has no platform
         # information in VIBs; make an ISO with embeddedEsx as the main
         # platform.
         platformOfIso = next(iter(platforms)) if len(platforms) == 1 \
                         else SoftwarePlatform.PRODUCT_EMBEDDEDESX
         try:
            esxioDepot = CreatePartialOfflineDepot(isoProfile,
               SoftwarePlatform.PRODUCT_ESXIO_ARM) if incInstaller else None

            iso = EsxIsoImage.EsxIsoImage(isoProfile)
            isoImageFile = FORMAT_ISO_IMAGE + exportFileNameHash + '.iso'
            isoImagePath = os.path.join(exportLocation, isoImageFile)
            iso.Write(isoImagePath, checkacceptance=False,
                      installer=incInstaller,
                      esxiodepot=esxioDepot,
                      platform=platformOfIso)
            exportMap[FORMAT_ISO_IMAGE] = isoImageFile
            _CreateLock(isoImagePath)
         finally:
            if esxioDepot and os.path.isfile(esxioDepot):
               os.unlink(esxioDepot)

      if not (FORMAT_DEPOT in exportFormats or FORMAT_BUNDLE in exportFormats):
         return exportMap

      # Depot and offline bundle will contain solutions (when exist) in 7.0.
      # They will also contain components that are obsoleted by VIB relations,
      # this is to allow host scan/apply to re-generate effective components.
      profile = self.validateAndReturnImageProfile(removeObsoletedComp=False)

      if FORMAT_DEPOT in exportFormats:
         depotPath = os.path.join(exportLocation, FORMAT_DEPOT +
                                  exportFileNameHash)
         DepotFromImageProfile(profile, depotPath, vendor='VMware, Inc.',
                               vendorcode='vmw',
                               allowPartialDepot=allowPartialDepot,
                               generateRollupBulletin=False)
         exportMap[FORMAT_DEPOT] = FORMAT_DEPOT + exportFileNameHash
         _CreateLock(depotPath)

      if FORMAT_BUNDLE in exportFormats:
         with tempfile.TemporaryDirectory() as tmpDir:
            vibs = profile.GetKnownVibs()
            configSchemas = self.depotMgr.GetVibConfigSchemas(vibs)
            vibExports = self.depotMgr.GetVibExports(vibs)
            DepotFromImageProfile(profile, tmpDir, vendor='VMware, Inc.',
                                  vendorcode='vmw',
                                  allowPartialDepot=allowPartialDepot,
                                  generateRollupBulletin=False,
                                  configSchemas=configSchemas,
                                  vibExports=vibExports)
            bundleFile = FORMAT_BUNDLE + exportFileNameHash + '.zip'
            bundlePath = os.path.join(exportLocation, bundleFile)
            with zipfile.ZipFile(bundlePath, 'w', zipfile.ZIP_DEFLATED) as z:
               prefixLen = len(tmpDir)
               for root, _, files in os.walk(tmpDir):
                  for f in files:
                     src = os.path.join(root, f)
                     dst = src[prefixLen:]
                     z.write(src, dst)
         exportMap[FORMAT_BUNDLE] = bundleFile
         _CreateLock(bundlePath)
      return exportMap

   def generateEffectiveVibs(self, effComps=None, reservedComps=None):
      """Generate the effective and reserved VIB information list.

         Parameter:
            effComps, reservedComps: List of the effective and reserved
                     components to generate vib list. By default set to None,
                     which will force the interface to compute effective and
                     reserved component list including obseleted components.
      """
      if not effComps or not reservedComps:
         effComps, reservedComps = \
            self.generateEffectiveComponent(removeObsoletedComp=False)

      vibList = []
      for name, compInfo in effComps.items():
         # Version can be empty only when it is a user removed component.
         # Use the version present in overridden_components instead.
         comp = self.getComponent(name, compInfo.version or
            compInfo.details.overridden_components[0].version)
         origVibs = comp.GetVibCollection(self.depotMgr.vibs)
         for vib in origVibs.values():
            vibList.append(self._getVibInfoDict(vib, name, "effective"))

      for name, resCompsInfo in reservedComps.items():
         for compInfo in resCompsInfo:
            # Version can be empty only when it is a user removed component.
            # Use the version present in overridden_components instead.
            comp = self.getComponent(name, compInfo.version or
               compInfo.details.overridden_components[0].version)
            origVibs = comp.GetVibCollection(self.depotMgr.vibs)
            for vib in origVibs.values():
               vibList.append(self._getVibInfoDict(vib, name, "reserved"))
      return vibList

   def generateEffectiveVibsForScan(self):
      """Generate the effective VIB information list for scan operation.
      """
      effComps, _ = \
            self.generateEffectiveComponent(removeObsoletedComp=False)

      patcherComps = SCAN_COMP_NAMES
      patcherCompsNotFound = list(SCAN_COMP_NAMES)

      # Allow missing esxio-update component, which is not shipped in ESXi 7.0.
      # TODO: remove this exception when 7.0 is out of upgrade support.
      patcherCompsNotFound.remove(ESXIO_UPDATE_COMP_NAME)

      # Get all the Quick Patch and patch-the-patcher vibs from
      # effective components.
      vibList = []
      baseimage = self._findBaseImage()

      for name, compInfo in effComps.items():
         # Remove this component from NotFound list
         if name in patcherComps and name in patcherCompsNotFound:
            patcherCompsNotFound.remove(name)

         # If base image is not Quick Patch compatible,
         # skip the component which is not patch-the-patcher component.
         if name not in patcherComps and not baseimage.isQuickPatch:
            continue

         comp = self.getComponent(name, compInfo.version)
         origVibs = comp.GetVibCollection(self.depotMgr.vibs)
         for vib in origVibs.values():
            if vib.isQuickPatchVib or name in patcherComps:
               vibList.append(self._getVibInfoDict(vib, name, "effective"))

      if patcherCompsNotFound:
         raise ComponentNotFoundError(' '.join(patcherCompsNotFound),
               "Could not find the patch-the-patcher component %s for scan",
               ' '.join(patcherCompsNotFound))

      return vibList

   def _getVibInfoDict(self, vib, compName, vibType):
      """Formulate the dictionary based on the vib.
      """
      csTag = vib.GetConfigSchemaTag()
      csTag = csTag.schemaId if csTag else None
      vibDict = dict(id=vib.id, relativePath=vib.relativepath,
                     compName=compName, configSchemaId=csTag,
                     type=vibType)
      return vibDict

   def generateDriverComponentMap(self, effComps=None):
      """Generate the Driver to effective Component mapping list.
         If the effective components are provided, it will generate effective
         driver-components associated with it else compute the effective
         component to generate effective vib list.

         This function generates the Driver to Effective Component
         mapping by using below method.

         Get Effective Component List
         For each "component" in "Effective Component" List
            Get VIB Collection
            For each VIB in VIB Collection
               Get filelist from VIB
               For each file in filelist
                  Check vmkmod is present in file
                  if vmkmod is present
                     get the drivername after vmkmod/
                     make tuple "driver(name, version),
                                 component(name, version)"
         Collect All Driver, Component Info and Add to List

         Parameter:
           effComps: List of the effective components to generate effective
                     driver-component mapping. By default set to None, which
                     will force the interface to compute effective component
                     list excluding obseleted components.
      """
      if not effComps:
         effComps, _ = self.generateEffectiveComponent(removeObsoletedComp=True)

      driverCompList = []
      for name, compInfo in effComps.items():
         comp = self.getComponent(name, compInfo.version)
         origVibs = comp.GetVibCollection(self.depotMgr.vibs)
         for vib in origVibs.values():
            for fname in vib.filelist:
               if "vmkmod/" in fname:
                  _, driver = fname.split('vmkmod/')
                  # Get the correct driver version
                  # e.g 17.00.02.00-1vmw
                  dVer = vib.version.version.versionstring
                  dRel = vib.version.release.versionstring
                  driverVer = dVer+"-"+(dRel.split('.')[0])
                  driverCompList.append(
                     dict(driverName=driver,
                          driverVersion=driverVer,
                          compName=name,
                          compVersion=compInfo.version
                         ))
      return driverCompList


   def CalculateMicroDepots(self):
      """ Calculate the micro depots that contains all the image related
          objects in the software spec.
      """
      # Obsoleted components need to be included for host to perform scan.
      imageProfile = \
         self.validateAndReturnImageProfile(removeObsoletedComp=False)
      return self.depotMgr.CalculateMicroDepots(imageProfile)

   def generateEffectiveImage(self):
      """Generate the effective image containing effective components,
         vibs, driver-component map, and micro-depot paths.
      """
      effComps, reservedComps = \
         self.generateEffectiveComponent(removeObsoletedComp=True)
      vibs = self.generateEffectiveVibs(effComps=effComps,
                                        reservedComps=reservedComps)
      scanVibs = self.generateEffectiveVibsForScan()

      return dict(components=effComps, vibs=vibs,
                  driverComponents=self.generateDriverComponentMap(effComps),
                  microDepots=self.CalculateMicroDepots(),
                  scanVibs=scanVibs)

   def getCompVersionsFromSolution(self, solutionName):
      """ Get the solution component version dict.
      """
      solutionVersion = ""
      componentVersionDict = {}

      # Get the solutionVersion from SoftwareSpec
      solutionsInfo = self.softwareSpec.get(SOLUTIONS, None)
      if solutionsInfo:
         for solutionID, solutionInfo in solutionsInfo.items():
            if solutionID == solutionName:
               solutionVersion = solutionInfo['version']
      if not solutionVersion:
         raise SolutionNotFound(solutionName, solutionVersion,
                                'The solution (%s) is not found' %
                                solutionName)

      # Get the component version list given the solutionName
      # and the solutionVersion
      solutions = self.depotMgr.solutions
      if solutions.HasSolution(solutionName, solutionVersion):
         solution = solutions.GetSolution(solutionName, solutionVersion)
         compDict = solution.MatchComponents(self.depotMgr.components)
         for compName, compList in compDict.items():
            # Add the version from the compList to versionList
            versionList = []
            for comp in compList:
               versionList.append(comp.compVersionStr)
            componentVersionDict[compName] = versionList

      else:
         raise SolutionNotFound(solutionName, solutionVersion,
                                'The solution (%s, %s) not found' %
                                (solutionName, solutionVersion))

      return componentVersionDict

   # Registration of intent calculation functions.
   #
   # Intent calculation is split into multiple steps for base image, addon,
   # user components, solution, and amy postprocessing if required. Each step is
   # implemented with an intent calculation function that accepts the following
   # arguments:
   #     A software spec manager instance
   #     The current step name
   #     The intents from previous step:
   #        A component collection with components
   #     The operation history:
   #        A map from component name to the history list of add/remove
   #        operations on this component
   #        An in-out parameter
   #     A bool parameter to indicate whether ignore errors
   #        If false, raise exception on errors
   # and returns the followings:
   #     The intents from this step:
   #        A component collection with components (for image profile creation)
   #
   # Now the order is:
   #    Base image establishes the initial intent component list
   #    Addon adds, overrides, and removes components
   #    User components are added into intents or overrides some intents
   #    Solutions add agent components.
   #    User removed components are processed.
   #
   INTENT_RESOLUTION_MAP = \
      OrderedDict([(RESOLUTION_TYPE_BASEIMAGE, _resolveBaseImageIntents),
                   (RESOLUTION_TYPE_ADDON, _resolveAddonIntents),
                   (RESOLUTION_TYPE_MANIFEST, _resolveManifestIntents),
                   (RESOLUTION_TYPE_USERCOMP, _resolveUserComponents),
                   (RESOLUTION_TYPE_SOLUTION, _resolveSolutionIntents),
                   (RESOLUTION_TYPE_REMOVED_COMP, _resolveComponentRemovals)])
