########################################################################
# Copyright (C) 2010-2021 VMware, Inc.                                 #
# All Rights Reserved                                                  #
########################################################################

class EsxupdateError(Exception):
   description = ('Abstract base class for esxupdate exceptions.  Each '
                  'exception type has a class variable, errno, which is an '
                  'error code unique to the related error, and description, '
                  'which is a string description of the exception.')
   # Same errno as any non EsxupdateError exception, should be overridden if
   # an inheriting class can be raised in esxupdate/PatchManager.
   errno = 99
   attrs = tuple()

   def __init__(self, *args, **kwargs):
      # 'self' here is likely to be an instance of a child class, hence attrs
      # will actually contain something.
      arglen = len(args) + len(kwargs)
      if arglen > len(self.attrs):
         raise TypeError('%s expected %s arguments, got %s' %
                         (self.__class__, len(self.attrs), arglen))
      alreadyset = list()
      for i in range(len(args)):
         setattr(self, self.attrs[i], args[i])
         alreadyset.append(self.attrs[i])
      for k, v in kwargs.items():
         if k not in self.attrs:
            raise TypeError("%s got an unexpected keyword argument '%s'" %
                            (self.__class__, k))
         if k in alreadyset:
            raise TypeError("%s got multiple values for keyword argument '%s'" %
                            (self.__class__, k))
         setattr(self, k, v)
      # Note:  Built-in Exception() class does not normally accept kw args.
      Exception.__init__(self, *(list(args) + list(kwargs.values())))

   def __str__(self):
      if self.attrs:
         # Print validation issues when problemset is present
         if 'problemset' in self.__dict__.keys() and self.problemset:
            return '\n'.join([str(p) for p in self.problemset])
         else:
            return Exception.__str__(self)
      else:
         return self.description

class NotRootError(EsxupdateError):
   description = 'The current user is not UID 0 (root).'
   errno = 1

class LockingError(EsxupdateError):
   description = 'Another instance of esxupdate is currently running.'
   errno = 3
   attrs = ('msg',)

class MetadataDownloadError(EsxupdateError):
   description = 'Failed to download metadata.'
   errno = 4
   attrs = ('url', 'localfile', 'msg')

class MetadataFormatError(EsxupdateError):
   description = 'The format of the metadata is invalid.'
   errno = 5
   attrs = ('filename', 'msg')

class VibDownloadError(EsxupdateError):
   description = 'Failed to download VIB.'
   errno = 7
   attrs = ('url', 'localfile', 'msg')

class VibFormatError(EsxupdateError):
   description = 'The format of the VIB is invalid.'
   errno = 6
   attrs = ('filename', 'msg')

class VibValidationError(EsxupdateError):
   description = "An error occurred validating VIB contents."
   errno = 8
   attrs = ("filename", "errors", "msg", "problemset",)

class VibXmlSchemaLoadError(EsxupdateError):
   description = "Failed to load xml schema for vib validation."
   attrs = ("filename", "msg")

class VibIOError(EsxupdateError):
   description = 'Error reading or writing VIB data.'
   errno = 9
   attrs = ('filename', 'msg')

class VibHwPlatformError(EsxupdateError):
   description = "VIB vendor/model is not compatible with the host hardware."
   errno = 49
   attrs = ('msg')

class FileIOError(EsxupdateError):
   description = 'Unable to create, write or read a file as expected.'
   errno = 10
   attrs = ('filename', 'msg')

class DatabaseFormatError(EsxupdateError):
   description = 'The format of the database is invalid.'
   errno = 11
   attrs = ('filename', 'msg')

class DatabaseIOError(EsxupdateError):
   description = 'Unable to read or write to database.'
   errno = 12
   attrs = ('filename', 'msg')

class ReleaseUnitIOError(EsxupdateError):
   description = 'Unable to read or write base image or addon JSON file.'
   errno = 41
   attrs = ('filename', 'msg')

class ProfileFormatError(EsxupdateError):
   description = 'The format of the image profile is invalid.'
   errno = 2
   attrs = ('profilename', 'msg')

class ProfileValidationError(EsxupdateError):
   description = 'The image profile could not be validated.'
   errno = 32
   attrs = ('profilename', 'msg', 'problemset',)

class ProfileIOError(EsxupdateError):
   description = 'Unable to read or write profile data.'
   attrs = ('msg',)

class ProfileVibRemoval(EsxupdateError):
   description = 'Profile installation led to the removal of VIBs.'
   attrs = ('profile', 'removed', 'msg')

class ImageIOError(EsxupdateError):
   description = 'An error occurred reading or writing an image.'
   attrs = ('filename', 'msg')

class NoMatchError(EsxupdateError):
   """ Raised due to one of the following:
       1. Specified vib name-version, vendor-name-version, vendor-name,
          does not match any VIBs in metadata or database
       2. Specified image profile name does not match any in metadata
       3. Specified image profile matches more than one profile in metadata
       4. Image Profile contains VIB IDs which are not found in the metadata.
   """
   description = 'No matching VIB or image profile was found.'
   errno = 13
   attrs = ('id', 'msg')

class DependencyError(EsxupdateError):
   description = 'There was an error resolving dependencies.'
   errno = 14
   attrs = ('msg', 'problemset',)

class InstallationError(EsxupdateError):
   description = 'The installation transaction failed.'
   errno = 15
   attrs = ('cause', 'vibs', 'msg')
   def __str__(self):
      """When printing, omit cause, and form a string from VIB IDs when given.
      """
      if self.vibs:
         return "%s: %s" % (', '.join(self.vibs), self.msg)
      else:
         return self.msg

class MaintenanceModeError(EsxupdateError):
   description = 'Maintenance mode is not enabled or could not be determined.'
   errno = 18
   attrs = ('msg',)

class VibSignatureError(EsxupdateError):
   """ Base class for all Vib signature errors """
   pass

class VibSigMissingError(VibSignatureError):
   description = 'Vib signature is missing.'
   errno = 20
   attrs = ('vib', 'msg')

class VibSigFormatError(VibSignatureError):
   description = 'pkcs7.sig file is too short or corrupted.'
   errno = 22
   attrs = ('vib', 'msg')

class VibSigInvalidError(VibSignatureError):
   description = 'PKCS7 certificate is not valid (expired, etc) or ' \
                 'the public key is missing.'
   errno = 23
   attrs = ('vib', 'msg')

class VibCertStoreError(VibSignatureError):
   description = 'Cannot import certificate or revocation list into '\
                 'the certificate store because the certs are invalid '\
                 'or missing.'
   errno = 17
   attrs = ('vib', 'msg')

class VibSigDigestError(VibSignatureError):
   description = 'Vib digest does not match the digest in the PKCS7 signature.'
   errno = 24
   attrs = ('vib', 'msg')

class VibSigningError(VibSignatureError):
   description = 'An error occured signing a VIB.'
   attrs = ('vib', 'msg')

class VibPayloadDigestError(VibSignatureError):
   errno = 19
   description = 'An error occured validaing VIB payload contents.'
   attrs = ('vib', 'payload', 'msg')

class BundleFormatError(EsxupdateError):
   description = 'Invalid bundle ZIP archive, or missing metadata.zip inside.'
   errno = 27
   attrs = ('filename', 'msg')

class BundleIOError(EsxupdateError):
   description = 'Error reading or writing Bundle data.'
   errno = 28
   attrs = ('filename', 'msg')

class LiveInstallationError(InstallationError):
   """Live VIB installation error, a sub-class of InstallationError with
      same attributes and __str__ method.
   """
   description = 'Live vib installation failed. An immediate reboot ' \
                 'is required to clear live vib installation failure.'
   errno = 29

class StatelessError(EsxupdateError):
   description = 'Cannot apply rebootrequired operation on stateless host.'
   errno = 30
   attrs = ('msg',)

class HardwareError(EsxupdateError):
   description = 'Hardware requirement does not meet.'
   errno = 31
   attrs = ('msg',)

class InstallerNotAppropriate(EsxupdateError):
   description = 'Environment is not appropriate for this type of installer.'
   errno = 36
   attrs = ('installer', 'msg',)

class AcceptanceConfigError(EsxupdateError):
   description = 'Failed to change the acceptance level configuration or ' \
                 'there is an issue with current acceptance setting.'
   errno = 33
   attrs = ('msg', 'problemset',)

class ChecksumVerificationError(EsxupdateError):
   description = 'Error in verifying the checksum of a payload.'
   errno = 34
   attrs = ('msg',)

class BulletinValidationError(EsxupdateError):
   description = "An error occurred validating Bulletin contents."
   attrs = ("msg", )

class AcceptanceGetError(EsxupdateError):
   errno = 37
   description = 'Failed to get the acceptance level configuration.'
   attrs = ('msg',)

class MetadataIOError(EsxupdateError):
   description = 'Error reading metadata.zip'
   errno = 16
   attrs = ('msg',)

class BulletinFormatError(EsxupdateError):
   description = 'The format of the bulletin is invalid'
   errno = 26
   attrs = ('msg',)

class BulletinIOError(EsxupdateError):
   description = 'Error reading or writing Bulletin'
   attrs = ('msg',)

class MetadataBuildError(EsxupdateError):
   description = 'Error in writing a metadata.zip'
   attrs = ('msg',)

class DowngradeError(EsxupdateError):
   description = 'Downgrade to an older version of ESXi is not supported.'
   attrs = ('msg',)

class NormalExit(EsxupdateError):
   description = 'Operation finished successfully.'
   errno = 0
   msg = ''
   attrs = ('msg',)

class NeedsRebootResult(NormalExit):
   description = 'The update completed successfully, ' \
                 'but the system needs to be ' \
                 'rebooted for the changes to be effective.'
   errno = 80
   msg = ''
   attrs = ('msg',)

class HostdRestartResult(NormalExit):
   description = 'Hostd needs to be restarted to complete installation.'
   errno = 81

class HostNotChanged(NormalExit):
   description = 'Host was not updated, no changes required.'
   errno = 82
   attrs = ('msg',)

class LiveInstallOnlyResult(NormalExit):
   description = 'WARNING: Only live system was updated, the change is not persistent.'
   errno = 83

class ComponentFormatError(EsxupdateError):
   description = 'The format of component is invalid'
   errno = 38
   attrs = ('msg',)

class ComponentIOError(EsxupdateError):
   description = 'Error reading or writing component'
   errno = 39
   attrs = ('msg',)

class ComponentValidationError(EsxupdateError):
   description = 'Error validating component XML or VIB relations'
   errno = 40
   attrs = ('msg', )

class MissingComponentError(EsxupdateError):
   description = "Cannot found components in metadata."
   errno = 42
   attrs = ('msg', )

class ComponentDowngradeError(EsxupdateError):
   description = 'There is one or more component downgraded in the operation'
   errno = 43
   attrs = ('components', 'msg')

class ComponentConfigDowngrade(ComponentDowngradeError):
   """Sub-class of ComponentDowngradeError specific to config downgrade
      scenario.
   """
   pass

class ReleaseUnitValidationError(EsxupdateError):
   """ Base class for all release unit validation errors
   """
   pass

class AddonValidationError(ReleaseUnitValidationError):
   description = 'Error validating addon metadata or its components'
   errno = 44
   attrs = ('addonID', 'msg',)

class BaseimageValidationError(ReleaseUnitValidationError):
   description = 'Error validating base image metadata or its components'
   errno = 45
   attrs = ('msg',)

class BaseImageNotFound(EsxupdateError):
   description = 'Missing base image in depot'
   errno = 46
   attrs = ('version', 'msg')

class AddonNotFound(EsxupdateError):
   description = 'Missing addon in depot'
   attrs = ('name', 'version', 'msg')

class SolutionNotFound(EsxupdateError):
   description = 'Missing solution in depot'
   attrs = ('name', 'version', 'msg')

class HardwareSupportPackageNotFound(EsxupdateError):
   description = 'Missing hardware support package in depot'
   attrs = ('manager', 'package', 'version', 'msg')

class ComponentNotFoundError(EsxupdateError):
   description = 'Missing component in depot'
   attrs = ('name', 'msg')

class IncompatibleSolutionCompsError(EsxupdateError):
   description = ('No compatible combination of multiple solution components '
                  'is found.')
   attrs = ('compNames', 'solutionName', 'msg')

# IncompatibleSolutionComponentError is a special singular case of
# IncompatibleSolutionCompsError with more information.
class IncompatibleSolutionComponentError(IncompatibleSolutionCompsError):
   description = 'Compatible solution component missing in depot'
   attrs = ('name', 'solutionName', 'compVersions', 'msg')

class SolutionComponentNotFound(EsxupdateError):
   description = 'Missing solution components in depot'
   attrs = ('compNames', 'solutionName', 'msg')

class DepotConnectError(EsxupdateError):
   description = 'Unable to connect to depot'
   attrs = ('causes', 'msg')

class ManifestValidationError(ReleaseUnitValidationError):
   description = 'Error validating manifest JSON or its components'
   attrs = ('manifestID', 'msg')

class ManifestConflictError(ReleaseUnitValidationError):
   description = 'Two manifests have a conflict'
   attrs = ('manifestID', 'newManifestID', 'msg')

class MissingVibError(EsxupdateError):
   description = 'Cannot find VIBs in metadata.'
   errno = 47
   attrs = ('vibIDs', 'msg')

class AddonBaseImageMismatchError(EsxupdateError):
   description = 'Addon and baseimage are incompatible'
   attrs = ('addonId', 'baseImageVersion', 'msg')

class ManifestBaseImageMismatchError(EsxupdateError):
   description = ('Hardware support package and baseimage are incompatible')
   attrs = ('manager', 'package', 'version', 'baseImageVersion', 'msg')

class MultipleManifestError(EsxupdateError):
   description = ('Multiple hardware support packages are in software spec.')
   attrs = ('msg', )

class SoftwareSpecFormatError(EsxupdateError):
   description = 'Corrupted or invalid software specification'
   attrs = ('field', 'msg')

class ReleaseUnitConflictError(EsxupdateError):
   description = 'Unequal release units share the same releaseID'
   attrs = ('releaseID', 'releaseType', 'msg')

class MetadataNotFoundError(EsxupdateError):
   description = 'Expected metadata objects are missing in the depots.'
   attrs = ('missingObjs', 'msg')

# Below are inner cause classes of InstallationError/LiveInstallationError.

class ServiceDisableError(EsxupdateError):
   description = 'Failed to disable service during live remediation.'
   attrs = ('component', 'service', 'msg')

class ServiceEnableError(EsxupdateError):
   description = 'Failed to enable service during live remediation.'
   attrs = ('component', 'service', 'msg')

class ComponentUnmountError(EsxupdateError):
   description = 'Failed to unmount a tardisk.'
   attrs = ('component', 'msg')

class MemoryReserveError(EsxupdateError):
   description = 'Failed to reserve memory'
   attrs = ('reserveSize', 'msg')

# Host seeding related Errors

class VibRecreateError(EsxupdateError):
   description = 'Failed to re-create a VIB for depot extraction.'
   attrs = ('vib', 'msg')

class ReservedVibExtractError(EsxupdateError):
   description = 'Failed to add a reserved VIB during depot extraction.'
   attrs = ('vib', 'msg')

class VibChecksumError(EsxupdateError):
   description = ('Checksum of VIB does not match expected during depot '
                  'extraction.')
   attrs = ('vib', 'msg')

class DataStorageNotFound(EsxupdateError):
   description = 'No OSData storage partition is available to extract depot.'
   errno = 48
   attrs = ('msg',)

class SoftwareInfoExtractError(EsxupdateError):
   description = ('An error occurred while extracting image info on the host.')
   attrs = ('msg',)

# For vCenter CPP side error handling.
class DepotNotFoundError(EsxupdateError):
   description = ('Depot not found error.')
   attrs = ('msg', 'urls')
