# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright 2012 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.

import abc

from oslo.config import cfg

from neutron.api import extensions
from neutron.api.v2 import attributes as attr
from neutron.api.v2 import base
from neutron.common import exceptions as qexception
from neutron import manager
from neutron.plugins.common import constants
from neutron.services.service_base import ServicePluginBase


# Loadbalancer Exceptions
class VipNotFound(qexception.NotFound):
    message = _("Vip %(vip_id)s could not be found")


class VipExists(qexception.NeutronException):
    message = _("Another Vip already exists for pool %(pool_id)s")


class PoolNotFound(qexception.NotFound):
    message = _("Pool %(pool_id)s could not be found")


class MemberNotFound(qexception.NotFound):
    message = _("Member %(member_id)s could not be found")


class HealthMonitorNotFound(qexception.NotFound):
    message = _("Health_monitor %(monitor_id)s could not be found")


class PoolMonitorAssociationNotFound(qexception.NotFound):
    message = _("Monitor %(monitor_id)s is not associated "
                "with Pool %(pool_id)s")


class PoolMonitorAssociationExists(qexception.Conflict):
    message = _('health_monitor %(monitor_id)s is already associated '
                'with pool %(pool_id)s')


class StateInvalid(qexception.NeutronException):
    message = _("Invalid state %(state)s of Loadbalancer resource %(id)s")


class PoolInUse(qexception.InUse):
    message = _("Pool %(pool_id)s is still in use")


class PoolStatsNotFound(qexception.NotFound):
    message = _("Statistics of Pool %(pool_id)s could not be found")


class ProtocolMismatch(qexception.BadRequest):
    message = _("Protocol %(vip_proto)s does not match "
                "pool protocol %(pool_proto)s")


RESOURCE_ATTRIBUTE_MAP = {
    'vips': {
        'id': {'allow_post': False, 'allow_put': False,
               'validate': {'type:uuid': None},
               'is_visible': True,
               'primary_key': True},
        'tenant_id': {'allow_post': True, 'allow_put': False,
                      'validate': {'type:string': None},
                      'required_by_policy': True,
                      'is_visible': True},
        'name': {'allow_post': True, 'allow_put': True,
                 'validate': {'type:string': None},
                 'default': '',
                 'is_visible': True},
        'description': {'allow_post': True, 'allow_put': True,
                        'validate': {'type:string': None},
                        'is_visible': True, 'default': ''},
        'subnet_id': {'allow_post': True, 'allow_put': False,
                      'validate': {'type:uuid': None},
                      'is_visible': True},
        'address': {'allow_post': True, 'allow_put': False,
                    'default': attr.ATTR_NOT_SPECIFIED,
                    'validate': {'type:ip_address_or_none': None},
                    'is_visible': True},
        'port_id': {'allow_post': False, 'allow_put': False,
                    'validate': {'type:uuid': None},
                    'is_visible': True},
        'protocol_port': {'allow_post': True, 'allow_put': False,
                          'validate': {'type:range': [0, 65535]},
                          'convert_to': attr.convert_to_int,
                          'is_visible': True},
        'protocol': {'allow_post': True, 'allow_put': False,
                     'validate': {'type:values': ['TCP', 'HTTP', 'HTTPS']},
                     'is_visible': True},
        'pool_id': {'allow_post': True, 'allow_put': True,
                    'validate': {'type:uuid': None},
                    'is_visible': True},
        'session_persistence': {'allow_post': True, 'allow_put': True,
                                'convert_to': attr.convert_none_to_empty_dict,
                                'default': {},
                                'validate': {
                                    'type:dict_or_empty': {
                                        'type': {'type:values': ['APP_COOKIE',
                                                                 'HTTP_COOKIE',
                                                                 'SOURCE_IP'],
                                                 'required': True},
                                        'cookie_name': {'type:string': None,
                                                        'required': False}}},
                                'is_visible': True},
        'connection_limit': {'allow_post': True, 'allow_put': True,
                             'default': -1,
                             'convert_to': attr.convert_to_int,
                             'is_visible': True},
        'admin_state_up': {'allow_post': True, 'allow_put': True,
                           'default': True,
                           'convert_to': attr.convert_to_boolean,
                           'is_visible': True},
        'status': {'allow_post': False, 'allow_put': False,
                   'is_visible': True},
        'status_description': {'allow_post': False, 'allow_put': False,
                               'is_visible': True}
    },
    'pools': {
        'id': {'allow_post': False, 'allow_put': False,
               'validate': {'type:uuid': None},
               'is_visible': True,
               'primary_key': True},
        'tenant_id': {'allow_post': True, 'allow_put': False,
                      'validate': {'type:string': None},
                      'required_by_policy': True,
                      'is_visible': True},
        'vip_id': {'allow_post': False, 'allow_put': False,
                   'is_visible': True},
        'name': {'allow_post': True, 'allow_put': True,
                 'validate': {'type:string': None},
                 'default': '',
                 'is_visible': True},
        'description': {'allow_post': True, 'allow_put': True,
                        'validate': {'type:string': None},
                        'is_visible': True, 'default': ''},
        'subnet_id': {'allow_post': True, 'allow_put': False,
                      'validate': {'type:uuid': None},
                      'is_visible': True},
        'protocol': {'allow_post': True, 'allow_put': False,
                     'validate': {'type:values': ['TCP', 'HTTP', 'HTTPS']},
                     'is_visible': True},
        'provider': {'allow_post': True, 'allow_put': False,
                     'validate': {'type:string': None},
                     'is_visible': True, 'default': attr.ATTR_NOT_SPECIFIED},
        'lb_method': {'allow_post': True, 'allow_put': True,
                      'validate': {'type:string': None},
                      'is_visible': True},
        'members': {'allow_post': False, 'allow_put': False,
                    'is_visible': True},
        'health_monitors': {'allow_post': True, 'allow_put': True,
                            'default': None,
                            'validate': {'type:uuid_list': None},
                            'convert_to': attr.convert_to_list,
                            'is_visible': True},
        'health_monitors_status': {'allow_post': False, 'allow_put': False,
                                   'is_visible': True},
        'admin_state_up': {'allow_post': True, 'allow_put': True,
                           'default': True,
                           'convert_to': attr.convert_to_boolean,
                           'is_visible': True},
        'status': {'allow_post': False, 'allow_put': False,
                   'is_visible': True},
        'status_description': {'allow_post': False, 'allow_put': False,
                               'is_visible': True}
    },
    'members': {
        'id': {'allow_post': False, 'allow_put': False,
               'validate': {'type:uuid': None},
               'is_visible': True,
               'primary_key': True},
        'tenant_id': {'allow_post': True, 'allow_put': False,
                      'validate': {'type:string': None},
                      'required_by_policy': True,
                      'is_visible': True},
        'pool_id': {'allow_post': True, 'allow_put': True,
                    'validate': {'type:uuid': None},
                    'is_visible': True},
        'address': {'allow_post': True, 'allow_put': False,
                    'validate': {'type:ip_address': None},
                    'is_visible': True},
        'protocol_port': {'allow_post': True, 'allow_put': False,
                          'validate': {'type:range': [0, 65535]},
                          'convert_to': attr.convert_to_int,
                          'is_visible': True},
        'weight': {'allow_post': True, 'allow_put': True,
                   'default': 1,
                   'validate': {'type:range': [0, 256]},
                   'convert_to': attr.convert_to_int,
                   'is_visible': True},
        'admin_state_up': {'allow_post': True, 'allow_put': True,
                           'default': True,
                           'convert_to': attr.convert_to_boolean,
                           'is_visible': True},
        'status': {'allow_post': False, 'allow_put': False,
                   'is_visible': True},
        'status_description': {'allow_post': False, 'allow_put': False,
                               'is_visible': True}
    },
    'health_monitors': {
        'id': {'allow_post': False, 'allow_put': False,
               'validate': {'type:uuid': None},
               'is_visible': True,
               'primary_key': True},
        'tenant_id': {'allow_post': True, 'allow_put': False,
                      'validate': {'type:string': None},
                      'required_by_policy': True,
                      'is_visible': True},
        'type': {'allow_post': True, 'allow_put': False,
                 'validate': {'type:values': ['PING', 'TCP', 'HTTP', 'HTTPS']},
                 'is_visible': True},
        'delay': {'allow_post': True, 'allow_put': True,
                  'validate': {'type:non_negative': None},
                  'convert_to': attr.convert_to_int,
                  'is_visible': True},
        'timeout': {'allow_post': True, 'allow_put': True,
                    'convert_to': attr.convert_to_int,
                    'is_visible': True},
        'max_retries': {'allow_post': True, 'allow_put': True,
                        'validate': {'type:range': [1, 10]},
                        'convert_to': attr.convert_to_int,
                        'is_visible': True},
        'http_method': {'allow_post': True, 'allow_put': True,
                        'validate': {'type:string': None},
                        'default': 'GET',
                        'is_visible': True},
        'url_path': {'allow_post': True, 'allow_put': True,
                     'validate': {'type:string': None},
                     'default': '/',
                     'is_visible': True},
        'expected_codes': {'allow_post': True, 'allow_put': True,
                           'validate': {
                               'type:regex':
                               '^(\d{3}(\s*,\s*\d{3})*)$|^(\d{3}-\d{3})$'},
                           'default': '200',
                           'is_visible': True},
        'admin_state_up': {'allow_post': True, 'allow_put': True,
                           'default': True,
                           'convert_to': attr.convert_to_boolean,
                           'is_visible': True},
        'status': {'allow_post': False, 'allow_put': False,
                   'is_visible': True},
        'status_description': {'allow_post': False, 'allow_put': False,
                               'is_visible': True},
        'pools': {'allow_post': False, 'allow_put': False,
                  'is_visible': True}
    }
}

SUB_RESOURCE_ATTRIBUTE_MAP = {
    'health_monitors': {
        'parent': {'collection_name': 'pools',
                   'member_name': 'pool'},
        'parameters': {'id': {'allow_post': True, 'allow_put': False,
                              'validate': {'type:uuid': None},
                              'is_visible': True},
                       'tenant_id': {'allow_post': True, 'allow_put': False,
                                     'validate': {'type:string': None},
                                     'required_by_policy': True,
                                     'is_visible': True},
                       }
    }
}


class Loadbalancer(extensions.ExtensionDescriptor):

    @classmethod
    def get_name(cls):
        return "LoadBalancing service"

    @classmethod
    def get_alias(cls):
        return "lbaas"

    @classmethod
    def get_description(cls):
        return "Extension for LoadBalancing service"

    @classmethod
    def get_namespace(cls):
        return "http://wiki.openstack.org/neutron/LBaaS/API_1.0"

    @classmethod
    def get_updated(cls):
        return "2012-10-07T10:00:00-00:00"

    @classmethod
    def get_resources(cls):
        my_plurals = [(key, key[:-1]) for key in RESOURCE_ATTRIBUTE_MAP.keys()]
        my_plurals.append(('health_monitors_status', 'health_monitor_status'))
        attr.PLURALS.update(dict(my_plurals))
        resources = []
        plugin = manager.NeutronManager.get_service_plugins()[
            constants.LOADBALANCER]
        for collection_name in RESOURCE_ATTRIBUTE_MAP:
            # Special handling needed for resources with 'y' ending
            # (e.g. proxies -> proxy)
            resource_name = collection_name[:-1]
            params = RESOURCE_ATTRIBUTE_MAP[collection_name]

            member_actions = {}
            if resource_name == 'pool':
                member_actions = {'stats': 'GET'}

            controller = base.create_resource(
                collection_name, resource_name, plugin, params,
                member_actions=member_actions,
                allow_pagination=cfg.CONF.allow_pagination,
                allow_sorting=cfg.CONF.allow_sorting)

            resource = extensions.ResourceExtension(
                collection_name,
                controller,
                path_prefix=constants.COMMON_PREFIXES[constants.LOADBALANCER],
                member_actions=member_actions,
                attr_map=params)
            resources.append(resource)

        for collection_name in SUB_RESOURCE_ATTRIBUTE_MAP:
            # Special handling needed for sub-resources with 'y' ending
            # (e.g. proxies -> proxy)
            resource_name = collection_name[:-1]
            parent = SUB_RESOURCE_ATTRIBUTE_MAP[collection_name].get('parent')
            params = SUB_RESOURCE_ATTRIBUTE_MAP[collection_name].get(
                'parameters')

            controller = base.create_resource(collection_name, resource_name,
                                              plugin, params,
                                              allow_bulk=True,
                                              parent=parent)

            resource = extensions.ResourceExtension(
                collection_name,
                controller, parent,
                path_prefix=constants.COMMON_PREFIXES[constants.LOADBALANCER],
                attr_map=params)
            resources.append(resource)

        return resources

    @classmethod
    def get_plugin_interface(cls):
        return LoadBalancerPluginBase

    def update_attributes_map(self, attributes):
        super(Loadbalancer, self).update_attributes_map(
            attributes, extension_attrs_map=RESOURCE_ATTRIBUTE_MAP)

    def get_extended_resources(self, version):
        if version == "2.0":
            return RESOURCE_ATTRIBUTE_MAP
        else:
            return {}


class LoadBalancerPluginBase(ServicePluginBase):
    __metaclass__ = abc.ABCMeta

    def get_plugin_name(self):
        return constants.LOADBALANCER

    def get_plugin_type(self):
        return constants.LOADBALANCER

    def get_plugin_description(self):
        return 'LoadBalancer service plugin'

    @abc.abstractmethod
    def get_vips(self, context, filters=None, fields=None):
        pass

    @abc.abstractmethod
    def get_vip(self, context, id, fields=None):
        pass

    @abc.abstractmethod
    def create_vip(self, context, vip):
        pass

    @abc.abstractmethod
    def update_vip(self, context, id, vip):
        pass

    @abc.abstractmethod
    def delete_vip(self, context, id):
        pass

    @abc.abstractmethod
    def get_pools(self, context, filters=None, fields=None):
        pass

    @abc.abstractmethod
    def get_pool(self, context, id, fields=None):
        pass

    @abc.abstractmethod
    def create_pool(self, context, pool):
        pass

    @abc.abstractmethod
    def update_pool(self, context, id, pool):
        pass

    @abc.abstractmethod
    def delete_pool(self, context, id):
        pass

    @abc.abstractmethod
    def stats(self, context, pool_id):
        pass

    @abc.abstractmethod
    def create_pool_health_monitor(self, context, health_monitor, pool_id):
        pass

    @abc.abstractmethod
    def get_pool_health_monitor(self, context, id, pool_id, fields=None):
        pass

    @abc.abstractmethod
    def delete_pool_health_monitor(self, context, id, pool_id):
        pass

    @abc.abstractmethod
    def get_members(self, context, filters=None, fields=None):
        pass

    @abc.abstractmethod
    def get_member(self, context, id, fields=None):
        pass

    @abc.abstractmethod
    def create_member(self, context, member):
        pass

    @abc.abstractmethod
    def update_member(self, context, id, member):
        pass

    @abc.abstractmethod
    def delete_member(self, context, id):
        pass

    @abc.abstractmethod
    def get_health_monitors(self, context, filters=None, fields=None):
        pass

    @abc.abstractmethod
    def get_health_monitor(self, context, id, fields=None):
        pass

    @abc.abstractmethod
    def create_health_monitor(self, context, health_monitor):
        pass

    @abc.abstractmethod
    def update_health_monitor(self, context, id, health_monitor):
        pass

    @abc.abstractmethod
    def delete_health_monitor(self, context, id):
        pass
