# Copyright 2011 OpenStack Foundation
# All Rights Reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.
"""I totally stole most of this from melange, thx guys!!!"""


import re

from oslo_concurrency import processutils
from oslo_log import log as logging

from trove.common import base_exception as openstack_exception
from trove.common.i18n import _


ClientConnectionError = openstack_exception.ClientConnectionError
ProcessExecutionError = processutils.ProcessExecutionError
DatabaseMigrationError = openstack_exception.DatabaseMigrationError
LOG = logging.getLogger(__name__)
wrap_exception = openstack_exception.wrap_exception


def safe_fmt_string(text):
    return re.sub(r'%([0-9]+)', r'\1', text)


class TroveError(openstack_exception.OpenstackException):
    """Base exception that all custom trove app exceptions inherit from."""
    internal_message = None

    def __init__(self, message=None, **kwargs):
        if message is not None:
            self.message = message
        if self.internal_message is not None:
            try:
                LOG.error(safe_fmt_string(self.internal_message), kwargs)
            except Exception:
                LOG.error(self.internal_message)
        self.message = safe_fmt_string(self.message)
        super(TroveError, self).__init__(**kwargs)


class DBConstraintError(TroveError):

    message = _("Failed to save %(model_name)s because: %(error)s.")


class InvalidRPCConnectionReuse(TroveError):

    message = _("Invalid RPC Connection Reuse.")


class InvalidValue(TroveError):
    message = _("The value is not allowed: %(value)s.")


class NotFound(TroveError):

    message = _("Resource %(uuid)s cannot be found.")


class BadRequest(TroveError):

    message = _("The server could not comply with the request since it is "
                "either malformed or otherwise incorrect.")


class CapabilityNotFound(NotFound):

    message = _("Capability '%(capability)s' cannot be found.")


class CapabilityDisabled(TroveError):

    message = _("Capability '%(capability)s' is disabled.")


class FlavorNotFound(TroveError):

    message = _("Resource %(uuid)s cannot be found.")


class UserNotFound(NotFound):

    message = _("User %(uuid)s cannot be found on the instance.")


class RootHistoryNotFound(NotFound):

    message = _("Root user has never been enabled on the instance.")


class DatabaseNotFound(NotFound):

    message = _("Database %(uuid)s cannot be found on the instance.")


class ComputeInstanceNotFound(NotFound):

    # internal_message is used for log, stop translating.
    internal_message = ("Cannot find compute instance %(server_id)s for "
                        "instance %(instance_id)s.")

    message = _("Resource %(instance_id)s can not be retrieved.")


class DnsRecordNotFound(NotFound):

    message = _("DnsRecord with name= %(name)s not found.")


class DatastoreNotFound(NotFound):

    message = _("Datastore '%(datastore)s' cannot be found.")


class DatastoreVersionNotFound(NotFound):

    message = _("Datastore version '%(version)s' cannot be found.")


class DatastoresNotFound(NotFound):

    message = _("Datastores cannot be found.")


class DatastoreFlavorAssociationNotFound(NotFound):

    message = _("Flavor %(id)s is not supported for datastore "
                "version %(datastore_version_id)s")


class DatastoreFlavorAssociationAlreadyExists(TroveError):

    message = _("Flavor %(id)s is already associated with "
                "datastore version %(datastore_version_id)s")


class DatastoreVolumeTypeAssociationNotFound(NotFound):

    message = _("The volume type %(id)s is not valid for datastore "
                "version %(datastore_version_id)s.")


class DatastoreVolumeTypeAssociationAlreadyExists(TroveError):

    message = _("Datastore version %(datastore_version_id)s "
                "and volume-type %(id)s mapping already exists.")


class DataStoreVersionVolumeTypeRequired(TroveError):

    message = _("Only specific volume types are allowed for a "
                "datastore version %(datastore_version_id)s. "
                "You must specify a valid volume type.")


class DatastoreVersionNoVolumeTypes(TroveError):

    message = _("No valid volume types could be found for datastore "
                "version %(datastore_version_id)s.")


class DatastoreNoVersion(TroveError):

    message = _("Datastore '%(datastore)s' has no version '%(version)s'.")


class DatastoreVersionInactive(TroveError):

    message = _("Datastore version '%(version)s' is not active.")


class DatastoreVersionAlreadyExists(BadRequest):

    message = _("The datastore version '%(name)s(%(version)s)' already "
                "exists.")


class DatastoreVersionsExist(BadRequest):

    message = _("Datastore versions exist for datastore %(datastore)s.")


class DatastoreVersionsInUse(BadRequest):

    message = _("Datastore version is in use by %(resource)s.")


class DatastoreVersionsNoUniqueMatch(TroveError):

    message = _("Multiple datastore versions found for '%(name)s', "
                "use an UUID or specify both the name and version number to "
                "be more specific.")


class DatastoreDefaultDatastoreNotFound(TroveError):

    message = _("Please specify datastore. Default datastore "
                "'%(datastore)s' cannot be found.")


class DatastoreDefaultDatastoreNotDefined(TroveError):

    message = _("Please specify datastore. No default datastore "
                "is defined.")


class DatastoreDefaultVersionNotFound(TroveError):

    message = _("Default version for datastore '%(datastore)s' not found.")


class InvalidDatastoreManager(TroveError):

    message = _("Datastore manager %(datastore_manager)s cannot be found.")


class DatastoreOperationNotSupported(TroveError):

    message = _("The '%(operation)s' operation is not supported for "
                "the '%(datastore)s' datastore.")


class OverLimit(TroveError):

    # internal_message is used for log, stop translating.
    internal_message = ("The server rejected the request due to its size or "
                        "rate.")


class QuotaLimitTooSmall(TroveError):

    message = _("Quota limit '%(limit)s' for '%(resource)s' is too small"
                " - must be at least '-1'.")


class QuotaExceeded(TroveError):

    message = _("Quota exceeded for resources: %(overs)s.")


class VolumeQuotaExceeded(QuotaExceeded):

    message = _("Instance volume quota exceeded.")


class GuestError(TroveError):

    message = _("An error occurred communicating with the guest: "
                "%(original_message)s.")


class GuestTimeout(TroveError):

    message = _("Timeout trying to connect to the Guest Agent.")


class MissingKey(BadRequest):

    message = _("Required element/key - %(key)s was not specified.")


class DatabaseAlreadyExists(BadRequest):

    message = _('A database with the name "%(name)s" already exists.')


class UserAlreadyExists(BadRequest):

    message = _('A user with the name "%(name)s" already exists.')


class InstanceAssignedToConfiguration(BadRequest):

    message = _('A configuration group cannot be deleted if it is '
                'associated with one or more non-terminated instances. '
                'Detach the configuration group from all non-terminated '
                'instances and please try again.')


class UnprocessableEntity(TroveError):

    message = _("Unable to process the contained request.")


class ConfigurationNotSupported(UnprocessableEntity):

    message = _("Configuration groups not supported by the datastore.")


class CannotResizeToSameSize(TroveError):

    message = _("No change was requested in the size of the instance.")


class VolumeAttachmentsNotFound(NotFound):

    message = _("Cannot find the volumes attached to compute "
                "instance %(server_id)s.")


class VolumeCreationFailure(TroveError):

    message = _("Failed to create volume.")


class VolumeSizeNotSpecified(BadRequest):

    message = _("Volume size was not specified.")


class LocalStorageNotSpecified(BadRequest):

    message = _("Local storage not specified in flavor ID: %(flavor)s.")


class LocalStorageNotSupported(TroveError):

    message = _("Local storage support is not enabled.")


class VolumeNotSupported(TroveError):

    message = _("Volume support is not enabled.")


class ReplicationNotSupported(TroveError):

    message = _("Replication is not supported for "
                "the '%(datastore)s' datastore.")


class ReplicationSlaveAttachError(TroveError):

    message = _("Exception encountered attaching slave to new replica source.")


class SlaveOperationNotSupported(TroveError):
    message = _("The '%(operation)s' operation is not supported for slaves in "
                "replication.")


class UnableToDetermineLastMasterGTID(TroveError):
    message = _("Unable to determine last GTID executed on master "
                "(from file %(binlog_file)s).")


class TaskManagerError(TroveError):

    message = _("An error occurred communicating with the task manager: "
                "%(original_message)s.")


class BadValue(TroveError):

    message = _("Value could not be converted: %(msg)s.")


class PollTimeOut(TroveError):

    message = _("Polling request timed out.")


class Forbidden(TroveError):

    message = _("User does not have admin privileges.")


class PolicyNotAuthorized(Forbidden):

    message = _("Policy doesn't allow %(action)s to be performed.")


class InvalidModelError(TroveError):

    message = _("The following values are invalid: %(errors)s.")


class ModelNotFoundError(NotFound):

    message = _("Not Found.")


class UpdateGuestError(TroveError):

    message = _("Failed to update instances.")


class ConfigNotFound(NotFound):

    message = _("Config file not found.")


class PasteAppNotFound(NotFound):

    message = _("Paste app not found.")


class QuotaNotFound(NotFound):
    message = _("Quota could not be found.")


class TenantQuotaNotFound(QuotaNotFound):
    message = _("Quota for tenant %(tenant_id)s could not be found.")


class QuotaResourceUnknown(QuotaNotFound):
    message = _("Unknown quota resources %(unknown)s.")


class BackupUploadError(TroveError):
    message = _("Unable to upload Backup to swift.")


class BackupDownloadError(TroveError):
    message = _("Unable to download Backup from swift")


class BackupCreationError(TroveError):
    message = _("Unable to create Backup.")


class BackupUpdateError(TroveError):
    message = _("Unable to update Backup table in database.")


class SecurityGroupDeletionError(TroveError):

    message = _("Failed to delete Security Group.")


class SecurityGroupRuleDeletionError(TroveError):

    message = _("Failed to delete Security Group Rule.")


class BackupNotCompleteError(TroveError):

    message = _("Unable to create instance because backup %(backup_id)s is "
                "not completed. Actual state: %(state)s.")


class BackupFileNotFound(NotFound):
    message = _("Backup file in %(location)s was not found in the object "
                "storage.")


class BackupDatastoreMismatchError(TroveError):
    message = _("The datastore from which the backup was taken, "
                "%(datastore1)s, does not match the destination"
                " datastore of %(datastore2)s.")


class BackupTooLarge(TroveError):
    message = _("Backup is too large for given flavor or volume. "
                "Backup size: %(backup_size)s GBs. "
                "Available size: %(disk_size)s GBs.")


class ReplicaCreateWithUsersDatabasesError(TroveError):
    message = _("Cannot create a replica with users or databases.")


class SwiftAuthError(TroveError):
    message = _("Swift account not accessible for tenant %(tenant_id)s.")


class SwiftNotFound(TroveError):
    message = _("Swift is disabled for tenant %(tenant_id)s.")


class SwiftConnectionError(TroveError):
    message = _("Cannot connect to Swift.")


class DatabaseForUserNotInDatabaseListError(TroveError):
    message = _("The request indicates that user %(user)s should have access "
                "to database %(database)s, but database %(database)s is not "
                "included in the initial databases list.")


class DatabaseInitialDatabaseDuplicateError(TroveError):
    message = _("Two or more databases share the same name in the initial "
                "databases list. Please correct the names or remove the "
                "duplicate entries.")


class DatabaseInitialUserDuplicateError(TroveError):
    message = _("Two or more users share the same name and host in the "
                "initial users list. Please correct the names or remove the "
                "duplicate entries.")


class RestoreBackupIntegrityError(TroveError):
    message = _("Current Swift object checksum does not match original "
                "checksum for backup %(backup_id)s.")


class ConfigKeyNotFound(NotFound):
    message = _("%(key)s is not a supported configuration parameter.")


class NoConfigParserFound(NotFound):
    message = _("No configuration parser found for datastore "
                "%(datastore_manager)s.")


class ConfigurationDatastoreNotMatchInstance(TroveError):
    message = _("Datastore Version on Configuration "
                "%(config_datastore_version)s does not "
                "match the Datastore Version on the instance "
                "%(instance_datastore_version)s.")


class ConfigurationParameterDeleted(TroveError):
    message = _("%(parameter_name)s parameter can no longer be "
                "set as of %(parameter_deleted_at)s.")


class ConfigurationParameterAlreadyExists(TroveError):
    message = _("%(parameter_name)s parameter already exists "
                "for datastore version %(datastore_version)s.")


class ConfigurationAlreadyAttached(TroveError):
    message = _("Instance %(instance_id)s already has a "
                "Configuration Group attached: %(configuration_id)s.")


class InvalidInstanceState(TroveError):
    message = _("The operation you have requested cannot be executed because "
                "the instance status is currently: %(status)s.")


class NoServiceEndpoint(TroveError):
    """Could not find requested endpoint in Service Catalog."""
    message = _("Endpoint not found for service_type=%(service_type)s, "
                "endpoint_type=%(endpoint_type)s, "
                "endpoint_region=%(endpoint_region)s.")


class EmptyCatalog(NoServiceEndpoint):
    """The service catalog is empty."""
    message = _("Empty catalog.")


class IncompatibleReplicationStrategy(TroveError):
    message = _("Instance with replication strategy %(guest_strategy)s "
                "cannot replicate from instance with replication strategy "
                "%(replication_strategy)s.")


class InsufficientSpaceForReplica(TroveError):
    message = _("The target instance has only %(slave_volume_size)sG free, "
                "but the replication snapshot contains %(dataset_size)sG "
                "of data.")


class InsufficientSpaceForBackup(TroveError):
    message = _("The instance has only %(free)sG free while the estimated "
                "backup size is %(backup_size)sG.")


class ReplicaSourceDeleteForbidden(Forbidden):
    message = _("The replica source cannot be deleted without detaching the "
                "replicas.")


class ModuleTypeNotFound(NotFound):
    message = _("Module type '%(module_type)s' was not found.")


class ModuleAppliedToInstance(BadRequest):

    message = _("A module cannot be deleted or its contents modified if it "
                "has been applied to a non-terminated instance, unless the "
                "module has been marked as 'live_update.' "
                "Please remove the module from all non-terminated "
                "instances and try again.")


class ModuleAlreadyExists(BadRequest):

    message = _("A module with the name '%(name)s' already exists for "
                "datastore '%(datastore)s' and datastore version "
                "'%(ds_version)s'")


class ModuleAccessForbidden(Forbidden):

    message = _("You must be admin to %(action)s a module with these "
                "options. %(options)s")


class ModuleInvalid(Forbidden):

    message = _("The module is invalid: %(reason)s")


class InstanceNotFound(NotFound):
    message = _("Instance '%(instance)s' cannot be found.")


class ClusterNotFound(NotFound):
    message = _("Cluster '%(cluster)s' cannot be found.")


class ClusterFlavorsNotEqual(TroveError):
    message = _("The flavor for each instance in a cluster must be the same.")


class ClusterNetworksNotEqual(TroveError):
    message = _("The network for each instance in a cluster must be the same.")


class NetworkNotFound(TroveError):
    message = _("Network Resource %(uuid)s cannot be found.")


class PublicNetworkNotFound(TroveError):
    message = _("Public network cannot be found.")


class NetworkConflict(BadRequest):
    message = _("User network conflicts with the reserved network.")


class NetworkNotProvided(BadRequest):
    message = _("Instance %(resource)s needs to be specified.")


class SubnetNotFound(BadRequest):
    message = _("Subnet %(subnet_id)s not found in the network "
                "%(network_id)s.")


class ClusterVolumeSizeRequired(TroveError):
    message = _("A volume size is required for each instance in the cluster.")


class ClusterVolumeSizesNotEqual(TroveError):
    message = _("The volume size for each instance in a cluster must be "
                "the same.")


class ClusterNumInstancesNotSupported(TroveError):
    message = _("The number of instances for your initial cluster must "
                "be %(num_instances)s.")


class ClusterNumInstancesNotLargeEnough(TroveError):
    message = _("The number of instances for your initial cluster must "
                "be at least %(num_instances)s.")


class ClusterNumInstancesBelowSafetyThreshold(TroveError):
    message = _("The number of instances in your cluster cannot "
                "safely be lowered below the current level based "
                "on your current fault-tolerance settings.")


class ClusterShrinkMustNotLeaveClusterEmpty(TroveError):
    message = _("Must leave at least one instance in the cluster when "
                "shrinking.")


class ClusterShrinkInstanceInUse(TroveError):
    message = _("Instance(s) %(id)s currently in use and cannot be deleted. "
                "Details: %(reason)s")


class ClusterInstanceOperationNotSupported(TroveError):
    message = _("Operation not supported for instances that are part of a "
                "cluster.")


class ClusterOperationNotSupported(TroveError):

    message = _("The '%(operation)s' operation is not supported for cluster.")


class TroveOperationAuthError(TroveError):
    message = _("Operation not allowed for tenant %(tenant_id)s.")


class ClusterDatastoreNotSupported(TroveError):
    message = _("Clusters not supported for "
                "%(datastore)s-%(datastore_version)s.")


class ImageNotFound(NotFound):

    message = _("Image %(uuid)s cannot be found.")


class ImageNotFoundByTags(NotFound):

    message = _("Failed to retrieve image with tags: %(tags)s.")


class LogAccessForbidden(Forbidden):

    message = _("You must be admin to %(action)s log '%(log)s'.")


class LogsNotAvailable(Forbidden):

    message = _("Log actions are not supported.")
