#!/usr/bin/env python
#
# Copyright 2017 Brocade Communications Systems, Inc.  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.
#
# Matthew Geldert (mgeldert@brocade.com), Brocade Communications Systems,Inc.
#

from driver_common import vTMDeviceDriverCommon, logging_wrapper
from neutron_lbaas.common.exceptions import LbaasException
from oslo_config import cfg
from oslo_log import log as logging
from services_director import ServicesDirector
from threading import Thread
from vtm import vTM
from time import sleep, time
from traceback import format_exc

LOG = logging.getLogger(__name__)


class BrocadeDeviceDriverV2(vTMDeviceDriverCommon):
    """
    Services Director Unmanaged Version
    """

    def __init__(self, plugin):
        self.services_director = ServicesDirector(
            "https://{}:{}/api/tmcm/{}".format(
                cfg.CONF.lbaas_settings.service_endpoint_address,
                cfg.CONF.services_director_settings.rest_port,
                cfg.CONF.services_director_settings.api_version
            ),
            cfg.CONF.services_director_settings.username,
            cfg.CONF.services_director_settings.password,
            connectivity_test_url="https://{}:{}/api/tmcm/1.5".format(
                cfg.CONF.lbaas_settings.service_endpoint_address,
                cfg.CONF.services_director_settings.rest_port
            )
        )
        super(BrocadeDeviceDriverV2, self).__init__()
        LOG.info(_("\nBrocade vTM LBaaS module initialized."))

    @logging_wrapper
    def create_loadbalancer(self, lb):
        """
        Ensures a vTM instance is instantiated for the service.
        If the deployment model is PER_LOADBALANCER, a new vTM instance
        will always be spawned by this call.  If the deployemnt model is
        PER_TENANT, a new instance will only be spawned if one does not
        already exist for the tenant.
        """
        self._assert_not_mgmt_network(lb.vip_subnet_id)
        deployment_model = self._get_setting(
            lb.tenant_id, "lbaas_settings", "deployment_model"
        )
        hostname = self._get_hostname(lb)
        if deployment_model == "PER_TENANT":
            if not self.openstack_connector.vtm_exists(
                lb.tenant_id, hostname):
                self._spawn_vtm(hostname, lb)
                sleep(5)
            elif not self.openstack_connector.vtm_has_subnet_port(hostname,lb):
                vtm = self._get_vtm(hostname)
                self._attach_subnet_port(vtm, hostname, lb)
            self.update_loadbalancer(lb, None)
        elif deployment_model == "PER_LOADBALANCER":
            self._spawn_vtm(hostname, lb)

    @logging_wrapper
    def update_loadbalancer(self, lb, old):
        """
        Creates or updates a TrafficIP group for the loadbalancer VIP address.
        The VIP is added to the allowed_address_pairs of the vTM's
        Neutron port to enable it to receive traffic to this address.
        NB. This only function only has a purpose in PER_TENANT deployments!
        """
        deployment_model = self._get_setting(
            lb.tenant_id, "lbaas_settings", "deployment_model"
        )
        if deployment_model == "PER_TENANT":
            hostname = self._get_hostname(lb)
            vtm = self._get_vtm(hostname)
            tip_config = {"properties": {
                "basic": {
                    "enabled": lb.admin_state_up,
                    "ipaddresses": [lb.vip_address],
                    "machines": vtm.get_nodes_in_cluster(),
                    "note": lb.name
                }
            }}
            vtm.tip_group.create(lb.id, config=tip_config)
            self._touch_last_modified_timestamp(vtm)
            if not old or lb.vip_address != old.vip_address:
                port_ids = self.openstack_connector.get_server_port_ids(
                    lb.tenant_id, hostname
                )
                self.openstack_connector.add_ip_to_ports(
                    lb.vip_address, port_ids
                )

    @logging_wrapper
    def delete_loadbalancer(self, lb):
        """
        Deletes the listen IP from a vTM.
        In the case of PER_LOADBALANCER deployments, this involves destroying
        the whole vTM instance. In the case of a PER_TENANT deployment, it
        involves deleting the TrafficIP Group associated with the VIP address.
        When the last TrafficIP Group has been deleted, the instance is
        destroyed.
        """
        deployment_model = self._get_setting(
            lb.tenant_id, "lbaas_settings", "deployment_model"
        )
        hostname = self._get_hostname(lb)
        if deployment_model == "PER_TENANT":
            vtm = self._get_vtm(hostname)
            vtm.tip_group.delete(lb.id)
            self._touch_last_modified_timestamp(vtm)
            if not vtm.tip_group.list():
                LOG.debug(_(
                    "\ndelete_loadbalancer({}): "
                    "last loadbalancer deleted; destroying vTM".format(lb.id)
                ))
                self._destroy_vtm(hostname, lb)
            else:
                # Delete subnet port if subnet no longer required
                if self.openstack_connector.subnet_in_use(lb) is False:
                    self._detach_subnet_port(vtm, hostname, lb)
                # Remove allowed_address_pairs entry from remaining ports
                port_ids = self.openstack_connector.get_server_port_ids(
                    lb.tenant_id, hostname
                )
                self.openstack_connector.delete_ip_from_ports(
                    lb.vip_address, port_ids
                )
        elif deployment_model == "PER_LOADBALANCER":
            self._destroy_vtm(hostname, lb)

#############
# LISTENERS #
#############

    @logging_wrapper
    def update_listener(self, listener, old):
        listen_on_settings = {}
        deployment_model = self._get_setting(
            listener.tenant_id, "lbaas_settings", "deployment_model"
        )
        hostname = self._get_hostname(listener.loadbalancer)
        if deployment_model == "PER_TENANT":
            listen_on_settings['listen_on_traffic_ips'] = [
                listener.loadbalancer.id
            ]
            listen_on_settings['listen_on_any'] = False
        elif deployment_model == "PER_LOADBALANCER":
            listen_on_settings['listen_on_traffic_ips'] = []
            listen_on_settings['listen_on_any'] = True
        vtm = self._get_vtm(hostname)
        super(BrocadeDeviceDriverV2, self).update_listener(
            listener, old, vtm, listen_on_settings
        )
        self._touch_last_modified_timestamp(vtm)

    @logging_wrapper
    def delete_listener(self, listener):
        deployment_model = self._get_setting(
            listener.tenant_id, "lbaas_settings", "deployment_model"
        )
        hostname = self._get_hostname(listener.loadbalancer)
        vtm = self._get_vtm(hostname)