"""
Copyright 2019-2021 VMware, Inc.
All rights reserved. -- VMware Confidential
"""
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 (ADD_ON, BASE_IMG, HARDWARE_SUPPORT, SOLUTIONS,
                        VALIDATE_SUCCESS_ID, VALIDATE_SUCCESS_MSG)
from .Utils import Notification

from ..Bulletin import Bulletin, ComponentCollection
from ..Depot import DepotFromImageProfile
from ..Errors import (AddonBaseImageMismatchError, AddonNotFound,
   BaseImageNotFound, ComponentNotFoundError, ComponentDowngradeError,
   ComponentValidationError, HardwareSupportPackageNotFound,
   IncompatibleSolutionComponentError, IncompatibleSolutionCompsError,
   ManifestBaseImageMismatchError, MultipleManifestError,
   SolutionComponentNotFound, SolutionNotFound, SoftwareSpecFormatError)
from ..ImageProfile import ImageProfile
from ..Manifest import (FIRMWARE_ONLY_PREFIX, HardwareSupportInfo,
                        HardwareSupportManager, HardwareSupportPackage,
                        Manifest)
from ..ReleaseCollection import ManifestCollection, SolutionCollection
from ..ReleaseUnit import NameSpec, VersionSpec
from ..Solution import ComponentConstraintList
from ..Version import VibVersion
from ..VibCollection import VibCollection

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'

# 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'
   }

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'
   }

# 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)


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):
      self.display_name = dname
      self.display_version = dver
      self.vendor = vendor
      self.source = src
      self.note = note
      self.overridden_components = ovrd


class _EffectiveComponentInfo(object):
   """See com.vmware.esx.settings.EffectiveComponentInfo.
   """
   def __init__(self, ver, dname, dver, vendor, src, note, ovrd):
      self.version = ver
      self.details = _EffectiveComponentDetails(dname, dver, vendor, src,
                                                 note, ovrd)


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.")

   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.
      """
      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

      # Did not find a component which matches the input parameters.
      msg = ("Could not find a component with name = %s, version = %s in the"
             " depot.", name, version)
      if ignoreErrors:
         log.warning(msg)
         return name, version
      raise ComponentNotFoundError(name, msg)

   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:
               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):
                  return addon
         raise AddonNotFound(name, version,
                             'The addon (%s, %s) not found' % (name, version))
      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)

      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):
      """Validate a combination of candidate solution components, returns a
         list of validation error (stop at first failed component) or None
         if validation passes.
      """
      # Preliminary final components.
      tempIntents = ComponentCollection()
      tempIntents += otherIntents
      for comp in candidates:
         tempIntents.AddComponent(comp)

      # 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.
      """
      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):
                  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 _checkDowngradeFromHistory(self, opHistory, ignoreErrors):
      """Check whether there are any downgrade in the operations.
      """
      for name in opHistory:
         opsForName = opHistory[name]
         # Start from the most recent operation.
         lastOp = opsForName[-1]
         if isinstance(lastOp, _RemoveOperation):
            # 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
            self._checkDowngrade(name, lastOp.version, op.version, ignoreErrors)

   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,
              component relation problems and a list of exceptions populated
              only with collectExceptions.
      """
      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, 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, SoftwareSpecFormatError,
                    SolutionComponentNotFound, SolutionNotFound) as e:
               if collectExceptions:
                  exceptions.append(e)
               else:
                  raise
      try:
         self._checkDowngradeFromHistory(opHistory, ignoreErrors)
      except Exception as e:
         if collectExceptions:
            exceptions.append(e)
         else:
            raise

      if exceptions:
         return intents, opHistory, None, exceptions

      # Run validate on finalBulletins to check for relation problems.
      compRelProblems = self._getComponentRelationProblems(intents)
      errors, warnings = compRelProblems.GetErrorsAndWarnings()
      if warnings and removeObsoletedComp:
         # In case of full obsolete cases, obsoleted component needs to be
         # removed from effective components.
         # This will be encountered in case where compA -o-> compB, both having
         # different component names.
         # For depot export however, the component needs to be kept in order
         # for effective components calculation to start based off it.

         # Sort by ID of problems to yield consistent result.
         obsoletes = sorted(warnings.values(), key=lambda p: p.id)
         compsRemoved = set()
         for prob in obsoletes:
            if (intents.HasComponent(prob.replacesComp) and prob.comp not in
                compsRemoved):
               # Removing only when the replacement is not removed.
               comp = intents.GetComponent(prob.replacesComp)
               log.info('Removing %s from final inventory due to full'
                        ' obsolete: %s' % (prob.replacesComp, prob.msg))
               del opHistory[comp.compNameStr]
               intents.RemoveComponent(prob.replacesComp)
               compsRemoved.add(prob.replacesComp)
      if errors and not ignoreErrors and not collectExceptions:
         msg = ','.join([str(prob) for prob in errors.values()])
         raise ComponentValidationError('Validation failed with problems: %s'
                                        % msg)

      return intents, opHistory, compRelProblems, exceptions

   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 = {'info': [], 'warnings': [], 'errors': []}
      _, _, problems, exceptions = \
         self._resolveAllIntents(collectExceptions=True)
      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())
      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 validateAndReturnImageProfile(self,
                                     name="VMware Lifecycle Manager Generated Image",
                                     creator="VMware, Inc.",
                                     checkAcceptance=True,
                                     removeObsoletedComp=True):
      """Returns the validated Image profile after validation.

         Creates the ImageProfile after validation and returns it if the
         mageProfile.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.
         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, _, problems, _ = 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)

      # 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)

      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)
      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, ignoreErrors):
      """Check whther it is a downgrade.
      """
      if VibVersion.fromstring(version) < VibVersion.fromstring(version1):
         msg = ('Component %s downgraded from %s to %s' %
                (name, version1, version))
         if ignoreErrors:
            log.warning(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):
      """Create effective component info object.
      """
      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 ''
      return _EffectiveComponentInfo(version, uiName, uiVersion, vendor,
                                     compSource, note, [])

   def AddReservedComponent(self, name, op, sourceMapVAPI,
                            ignoreErrors, reservedComp):
      """ Add a reserved component to the out parameter 'reservedComp'.
      """
      oldComp = (name, op.version)
      oldCompSource = sourceMapVAPI[op.source]
      reservedComp[name].append(self._createCompInfo(oldComp,
                                oldCompSource, ignoreErrors))

   def generateEffectiveComponent(self, ignoreErrors=False,
                                  removeObsoletedComp=True):
      """Generate the effective and reserved component information list.
      """
      _, 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]
         if isinstance(lastOp, _RemoveOperation):
            for i in range(len(opsForName) - 2, -1, -1):
               if isinstance(opsForName[i], _AddOperation):
                  self.AddReservedComponent(name, opsForName[i], sourceMapVAPI,
                                            ignoreErrors, reservedComp)

            # The last op is removal so ignore this component.
            continue
         compSource = sourceMapVAPI[lastOp.source]
         comp = (name, lastOp.version)
         # The last op is add; so add an effective component.
         effComp[name] = self._createCompInfo(comp, compSource, ignoreErrors)
         overrdComps = effComp[name].details.overridden_components

         # 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))

            # Populate the list of reserved components
            self.AddReservedComponent(name, op, sourceMapVAPI,
                                      ignoreErrors, reservedComp)
      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 = self.validateAndReturnImageProfile()
         if hasSolution:
            self.softwareSpec[SOLUTIONS] = solutionSection

         iso = EsxIsoImage.EsxIsoImage(deepcopy(isoProfile))
         isoImageFile = FORMAT_ISO_IMAGE + exportFileNameHash + '.iso'
         isoImagePath = os.path.join(exportLocation, isoImageFile)
         iso.Write(isoImagePath, checkacceptance=False, installer=incInstaller)
         exportMap[FORMAT_ISO_IMAGE] = isoImageFile
         _CreateLock(isoImagePath)

      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:
            configSchemas = \
               self.depotMgr.GetVibConfigSchemas(profile.GetKnownVibs())
            DepotFromImageProfile(profile, tmpDir, vendor='VMware, Inc.',
                                  vendorcode='vmw',
                                  allowPartialDepot=allowPartialDepot,
                                  generateRollupBulletin=False,
                                  configSchemas=configSchemas)
            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():
         comp = self.getComponent(name, compInfo.version)
         origVibs = comp.GetVibCollection(self.depotMgr.vibs)
         for vib in origVibs.values():
            csTag = vib.GetConfigSchemaTag()
            csTag = (csTag.schemaId if csTag else None)
            vibList.append(dict(id=vib.id, relativePath=vib.relativepath,
                                compName=name, configSchemaId=csTag,
                                type="effective"))

      for name, resCompsInfo in reservedComps.items():
         for compInfo in resCompsInfo:
            comp = self.getComponent(name, compInfo.version)
            origVibs = comp.GetVibCollection(self.depotMgr.vibs)
            for vib in origVibs.values():
               csTag = vib.GetConfigSchemaTag()
               csTag = (csTag.schemaId if csTag else None)
               vibList.append(dict(id=vib.id, relativePath=vib.relativepath,
                                   compName=name, configSchemaId=csTag,
                                   type="reserved"))
      return vibList

   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)

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

   # 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.
   #
   INTENT_RESOLUTION_MAP = \
      OrderedDict([(RESOLUTION_TYPE_BASEIMAGE, _resolveBaseImageIntents),
                   (RESOLUTION_TYPE_ADDON, _resolveAddonIntents),
                   (RESOLUTION_TYPE_MANIFEST, _resolveManifestIntents),
                   (RESOLUTION_TYPE_USERCOMP, _resolveUserComponents),
                   (RESOLUTION_TYPE_SOLUTION, _resolveSolutionIntents)])
