class ServiceException(Exception):
class ErrInstanceAlreadyExists(ServiceException):
class ErrInstanceDoesNotExist(ServiceException):
class ErrInstanceLimitMet(ServiceException):
class ErrPlanQuotaExceeded(ServiceException):
class ErrServiceQuotaExceeded(ServiceException):
class ErrBindingAlreadyExists(ServiceException):
class ErrBindingDoesNotExist(ServiceException):
class ErrInvalidParameters(ServiceException):
class ErrAsyncRequired(ServiceException):
class ErrPlanChangeNotSupported(ErrInvalidParameters):
class ErrAppGuidNotProvided(ServiceException):
class ErrConcurrentInstanceAccess(ServiceException):
class ErrBadRequest(ServiceException):
    def __init__(self):
    def __init__(self):
    def __init__(self):
    def __init__(self):
    def __init__(self):
    def __init__(self):
    def __init__(self):
    def __init__(self, msg):
    def __init__(self):
    def __init__(self):
    def __init__(self):
    def __init__(self):
    def __init__(self, msg='Malformed or missing data'): def ensure_list(x: Union[Iterable, Any]):
    import collections
    if isinstance(x, collections.abc.Iterable):
        return x
    else:
        return [x] class ServiceBroker:
    """
    Provides a service. This covers catalog, provision, update, bind, unbind, deprovision and last operation.
    """

    def catalog(self) -> Union[Service, List[Service]]:
        """
        Returns the services information which is provided by this broker.

        :return: Service or list of services
        """
        raise NotImplementedError()

    def provision(self,
                  instance_id: str,
                  details: ProvisionDetails,
                  async_allowed: bool,
                  **kwargs
                  ) -> ProvisionedServiceSpec:
        """
        Further readings `CF Broker API#Provisioning <https://docs.cloudfoundry.org/services/api.html#provisioning>`_

        :param instance_id: Instance id provided by the platform
        :param details: Details about the service to create
        :param async_allowed: Client allows async creation
        :param kwargs: May contain additional information, improves compatibility with upstream versions
        :rtype: ProvisionedServiceSpec
        :raises ErrInstanceAlreadyExists: If instance already exists
        :raises ErrAsyncRequired: If async is required but not supported
        """
        raise NotImplementedError()

    def update(self,
               instance_id: str,
               details: UpdateDetails,
               async_allowed: bool,
               **kwargs
               ) -> UpdateServiceSpec:
        """
        Further readings `CF Broker API#Update <https://docs.cloudfoundry.org/services/api.html#updating_service_instance>`_

        :param instance_id: Instance id provided by the platform
        :param details: Details about the service to update
        :param async_allowed: Client allows async creation
        :param kwargs: May contain additional information, improves compatibility with upstream versions
        :rtype: UpdateServiceSpec
        :raises ErrAsyncRequired: If async is required but not supported
        """
        raise NotImplementedError()

    def deprovision(self,
                    instance_id: str,
                    details: DeprovisionDetails,
                    async_allowed: bool,
                    **kwargs
                    ) -> DeprovisionServiceSpec:
        """
        Further readings `CF Broker API#Deprovisioning <https://docs.cloudfoundry.org/services/api.html#deprovisioning>`_

        :param instance_id: Instance id provided by the platform
        :param details: Details about the service to delete
        :param async_allowed: Client allows async creation
        :param kwargs: May contain additional information, improves compatibility with upstream versions
        :rtype: DeprovisionServiceSpec
        :raises ErrInstanceDoesNotExist: If instance does not exists
        :raises ErrAsyncRequired: If async is required but not supported
        """
        raise NotImplementedError()

    def bind(self,
             instance_id: str,
             binding_id: str,
             details: BindDetails,
             async_allowed: bool,
             **kwargs
             ) -> Binding:
        """
        Further readings `CF Broker API#Binding <https://docs.cloudfoundry.org/services/api.html#binding>`_

        :param instance_id: Instance id provided by the platform
        :param binding_id: Binding id provided by the platform
        :param details: Details about the binding to create
        :param async_allowed: Client allows async binding
        :param kwargs: May contain additional information, improves compatibility with upstream versions
        :rtype: Binding
        :raises ErrBindingAlreadyExists: If binding already exists
        :raises ErrAppGuidNotProvided: If AppGuid is required but not provided
        """
        raise NotImplementedError()

    def unbind(self,
               instance_id: str,
               binding_id: str,
               details: UnbindDetails,
               async_allowed: bool,
               **kwargs
               ) -> UnbindSpec:
        """
        Further readings `CF Broker API#Unbinding <https://docs.cloudfoundry.org/services/api.html#unbinding>`_

        :param instance_id: Instance id provided by the platform
        :param binding_id: Binding id provided by the platform
        :param details: Details about the binding to delete
        :param async_allowed: Client allows async unbind
        :param kwargs: May contain additional information, improves compatibility with upstream versions
        :rtype: UnbindDetails
        :raises ErrBindingAlreadyExists: If binding already exists
        """
        raise NotImplementedError()

    def get_instance(self,
                     instance_id: str,
                     **kwargs
                     ) -> GetInstanceDetailsSpec:
        """
        Further readings `CF Broker API#FetchServiceInstance <https://github.com/openservicebrokerapi/servicebroker/blob/v2.14/spec.md#fetching-a-service-instance>`_
        Must be implemented if `"instances_retrievable" :true` is declared for a service in `catalog`.

        :param instance_id: Instance id provided by the platform
        :param kwargs: May contain additional information, improves compatibility with upstream versions
        :rtype: GetInstanceDetailsSpec
        :raises ErrInstanceDoesNotExist: If instance does not exists
        :raises ErrConcurrentInstanceAccess: If instance is being updated
        """
        raise NotImplementedError()

    def get_binding(self,
                    instance_id: str,
                    binding_id: str,
                    **kwargs
                    ) -> GetBindingSpec:
        """
                Further readings `CF Broker API#FetchServiceBinding <https://github.com/openservicebrokerapi/servicebroker/blob/v2.14/spec.md#fetching-a-service-binding>`_
                Must be implemented if `"bindings_retrievable" :true` is declared for a service in `catalog`.

                :param instance_id: Instance id provided by the platform
                :param binding_id: Instance id provided by the platform
                :param kwargs: May contain additional information, improves compatibility with upstream versions
                :rtype: GetBindingSpec
                :raises ErrBindingDoesNotExist: If binding does not exist
                """
        raise NotImplementedError()

    def last_operation(self,
                       instance_id: str,
                       operation_data: Optional[str],
                       service_id: Optional[str],
                       plan_id: Optional[str],
                       **kwargs
                       ) -> LastOperation:
        """
        Further readings `CF Broker API#LastOperation <https://docs.cloudfoundry.org/services/api.html#polling>`_

        :param instance_id: Instance id provided by the platform
        :param operation_data: Operation data received from async operation
        :param service_id: service identifier
        :param plan_id: plan identifier
        :param kwargs: May contain additional information, improves compatibility with upstream versions
        :rtype: LastOperation
        """
        raise NotImplementedError()

    def last_binding_operation(self,
                               instance_id: str,
                               binding_id: str,
                               operation_data: Optional[str],
                               service_id: Optional[str],
                               plan_id: Optional[str],
                               **kwargs
                               ) -> LastOperation:
        """
        Further readings `CF Broker API#LastOperationForBindings <https://github.com/openservicebrokerapi/servicebroker/blob/v2.14/spec.md#polling-last-operation-for-service-bindings>`_
        Must be implemented if `Provision`, `Update`, or `Deprovision` are async.

        :param instance_id: Instance id provided by the platform
        :param binding_id: Binding id provided by the platform
        :param operation_data: Operation data received from async operation
        :param service_id: service identifier
        :param plan_id: plan identifier
        :param kwargs: May contain additional information, improves compatibility with upstream versions
        :rtype: LastOperation
        """
        raise NotImplementedError() class Service:
    def __init__(self,
                 id: str,
                 name: str,
                 description: str,
                 bindable: bool,
                 plans: List[ServicePlan],
                 tags: List[str] = None,
                 requires: List[str] = None,
                 metadata: ServiceMetadata = None,
                 dashboard_client: ServiceDashboardClient = None,
                 plan_updateable: bool = False,
                 instances_retrievable: bool = False,
                 bindings_retrievable: bool = False,
                 **kwargs
                 ):
        """
        :param requires:  syslog_drain, route_forwarding or volume_mount
        """
        self.id = id
        self.name = name
        self.description = description
        self.bindable = bindable
        self.plans = plans
        self.tags = tags
        self.requires = requires
        self.metadata = metadata
        self.dashboard_client = dashboard_client
        self.plan_updateable = plan_updateable
        self.instances_retrievable = instances_retrievable
        self.bindings_retrievable = bindings_retrievable

        self.__dict__.update(kwargs) class ProvisionDetails:
    def __init__(self,
                 service_id: str,
                 plan_id: str,
                 organization_guid: str = None,
                 space_guid: str = None,
                 parameters: dict = None,
                 context: dict = None,
                 **kwargs):
        self.service_id = service_id
        self.plan_id = plan_id
        self.organization_guid = organization_guid
        self.space_guid = space_guid
        self.parameters = parameters
        self.context = context

        # Usage context information
        if isinstance(context, dict) and 'organization_guid' in context:
            if organization_guid is not None and context['organization_guid'] != organization_guid:
                raise TypeError('organization_guid does not match with context.organization_guid')
            self.organization_guid = context['organization_guid']
        if isinstance(context, dict) and 'space_guid' in context:
            if space_guid is not None and context['space_guid'] != space_guid:
                raise TypeError('space_guid does not match with context.space_guid')
            self.space_guid = context['space_guid']

        if DISABLE_SPACE_ORG_GUID_CHECK:
            pass
        elif None in (self.organization_guid, self.space_guid):
            raise TypeError('Organization and space guid are required.')

        # HTTP contextual data
        self.authorization_username = None  #: username of HTTP Basic Auth
        self.originating_identity = None  #: decoded X-Broker-Originating-Identity HTTP Header class ProvisionedServiceSpec:
    def __init__(self,
                 state: ProvisionState = ProvisionState.SUCCESSFUL_CREATED,
                 dashboard_url: str = None,
                 operation: str = None
                 ):
        self.state = state
        self.dashboard_url = dashboard_url
        self.operation = operation

    @property
    def is_async(self):
        return self.state == ProvisionState.IS_ASYNC class UpdateDetails:
    def __init__(self,
                 service_id: str,
                 plan_id: str = None,
                 parameters: dict = None,
                 previous_values: dict = None,
                 context: dict = None,
                 **kwargs
                 ):
        self.service_id = service_id
        self.plan_id = plan_id
        self.parameters = parameters
        self.previous_values = PreviousValues(**previous_values) if previous_values else None
        self.context = context
        # HTTP contextual data
        self.authorization_username = None  #: username of HTTP Basic Auth
        self.originating_identity = None  #: decoded X-Broker-Originating-Identity HTTP Header class UpdateServiceSpec:
    def __init__(self,
                 is_async: bool,
                 operation: Optional[str] = None,
                 dashboard_url: Optional[str] = None
                 ):
        self.is_async = is_async
        self.operation = operation
        self.dashboard_url = dashboard_url class DeprovisionDetails:
    def __init__(self,
                 service_id: str,
                 plan_id: str
                 ):
        self.service_id = service_id
        self.plan_id = plan_id
        # HTTP contextual data
        self.authorization_username = None  #: username of HTTP Basic Auth
        self.originating_identity = None  #: decoded X-Broker-Originating-Identity HTTP Header class DeprovisionServiceSpec:
    def __init__(self,
                 is_async: bool,
                 operation: str = None
                 ):
        self.is_async = is_async
        self.operation = operation class BindDetails:
    def __init__(self,
                 service_id: str,
                 plan_id: str,
                 app_guid: str = None,
                 bind_resource: dict = None,
                 parameters: dict = None,
                 context: dict = None,
                 **kwargs
                 ):
        self.app_guid = app_guid
        self.plan_id = plan_id
        self.service_id = service_id
        self.parameters = parameters
        self.bind_resource = BindResource(**bind_resource) if bind_resource else None
        self.context = context
        # HTTP contextual data
        self.authorization_username = None  #: username of HTTP Basic Auth
        self.originating_identity = None  #: decoded X-Broker-Originating-Identity HTTP Header class Binding:
    def __init__(self,
                 state=BindState.SUCCESSFUL_BOUND,
                 credentials: dict = None,
                 syslog_drain_url: str = None,
                 route_service_url: str = None,
                 volume_mounts: List[VolumeMount] = None,
                 operation: Optional[str] = None
                 ):
        self.state = state
        self.credentials = credentials
        self.syslog_drain_url = syslog_drain_url
        self.route_service_url = route_service_url
        self.volume_mounts = volume_mounts
        self.operation = operation class UnbindDetails:
    def __init__(self,
                 service_id: str,
                 plan_id: str
                 ):
        self.plan_id = plan_id
        self.service_id = service_id
        # HTTP contextual data
        self.authorization_username = None  #: username of HTTP Basic Auth
        self.originating_identity = None  #: decoded X-Broker-Originating-Identity HTTP Header class LastOperation:
    def __init__(self,
                 state: OperationState,
                 description: str = None
                 ):
        self.state = state
        self.description = description
from logging import getLogger
from typing import List, Optional, Set
from openbrokerapi import errors
from openbrokerapi.helper import ensure_list
from openbrokerapi.service_broker import (
    ServiceBroker,
    Service,
    ProvisionDetails,
    ProvisionedServiceSpec,
    UpdateDetails,
    UpdateServiceSpec,
    DeprovisionDetails,
    DeprovisionServiceSpec,
    BindDetails,
    Binding,
    UnbindDetails,
    LastOperation)


logger = getLogger(__name__)


class Router(ServiceBroker):
    def __init__(self, *service_brokers: ServiceBroker):
        self._service_brokers = service_brokers

    @staticmethod
    def _service_ids(broker: ServiceBroker) -> Set[str]:
        return {service.id for service in ensure_list(broker.catalog())}

    def _get_provider_by_id(self, service_id: str) -> ServiceBroker:
        for broker in self._service_brokers:
            if service_id in self._service_ids(broker):
                return broker
        raise KeyError('Service {} not found'.format(service_id))

    @staticmethod
    def add_service_id_to_async_response(response, service_id: str):
        if response.is_async:
            if response.operation is None:
                response.operation = service_id
            else:
                response.operation = ' '.join((service_id, response.operation))

    def catalog(self) -> List[Service]:
        services = []
        for broker in self._service_brokers:
            services.extend(ensure_list(broker.catalog()))
        return services

    def provision(self,
                  instance_id: str,

                  details: ProvisionDetails,