#!/usr/bin/python
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you 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.


# Creates a tunnel mesh across xenserver hosts
# Enforces broadcast drop rules on ingress GRE tunnels

import cloudstack_pluginlib as lib
import logging
import os
import sys
import subprocess
import time
import XenAPIPlugin

sys.path.append("/opt/xensource/sm/")
import util

from time import localtime as _localtime, asctime as _asctime

xePath = "/opt/xensource/bin/xe"
lib.setup_logging("/var/log/ovstunnel.log")


def block_ipv6_v5(bridge):
    lib.add_flow(bridge, priority=65000, dl_type='0x86dd', actions='drop')


def block_ipv6_v6(bridge):
    lib.add_flow(bridge, priority=65000, proto='ipv6', actions='drop')


block_ipv6_handlers = {
        '5': block_ipv6_v5,
        '6': block_ipv6_v6}


def echo(fn):
    def wrapped(*v, **k):
        name = fn.__name__
        util.SMlog("#### VMOPS enter  %s ####" % name)
        res = fn(*v, **k)
        util.SMlog("#### VMOPS exit  %s ####" % name)
        return res
    return wrapped


@echo
def setup_ovs_bridge(session, args):
    bridge = args.pop("bridge")
    key = args.pop("key")
    xs_nw_uuid = args.pop("xs_nw_uuid")
    cs_host_id = args.pop("cs_host_id")

    res = lib.check_switch()
    if res != "SUCCESS":
        return "FAILURE:%s" % res

    logging.debug("About to manually create the bridge:%s" % bridge)
    # create a bridge with the same name as the xapi network
    # also associate gre key in other config attribute
    res = lib.do_cmd([lib.VSCTL_PATH, "--", "--may-exist", "add-br", bridge,
                                     "--", "set", "bridge", bridge,
                                     "other_config:gre_key=%s" % key])
    logging.debug("Bridge has been manually created:%s" % res)
    # TODO: Make sure xs-network-uuid is set into external_ids
    lib.do_cmd([lib.VSCTL_PATH, "set", "Bridge", bridge,
                            "external_ids:xs-network-uuid=%s" % xs_nw_uuid])
    # Non empty result means something went wrong
    if res:
        result = "FAILURE:%s" % res
    else:
        # Verify the bridge actually exists, with the gre_key properly set
        res = lib.do_cmd([lib.VSCTL_PATH, "get", "bridge",
                                          bridge, "other_config:gre_key"])
        if key in res:
            result = "SUCCESS:%s" % bridge
        else:
            result = "FAILURE:%s" % res
        # Finally note in the xenapi network object that the network has
        # been configured
        xs_nw_uuid = lib.do_cmd([lib.XE_PATH, "network-list",
                                "bridge=%s" % bridge, "--minimal"])
        lib.do_cmd([lib.XE_PATH, "network-param-set", "uuid=%s" % xs_nw_uuid,
                   "other-config:is-ovs-tun-network=True"])
        conf_hosts = lib.do_cmd([lib.XE_PATH, "network-param-get",
                                "uuid=%s" % xs_nw_uuid,
                                "param-name=other-config",
                                "param-key=ovs-host-setup", "--minimal"])
        conf_hosts = cs_host_id + (conf_hosts and ',%s' % conf_hosts or '')
        lib.do_cmd([lib.XE_PATH, "network-param-set", "uuid=%s" % xs_nw_uuid,
                   "other-config:ovs-host-setup=%s" % conf_hosts])

        # BLOCK IPv6 - Flow spec changes with ovs version
        host_list_cmd = [lib.XE_PATH, 'host-list', '--minimal']
        host_list_str = lib.do_cmd(host_list_cmd)
        host_uuid = host_list_str.split(',')[0].strip()
        version_cmd = [lib.XE_PATH, 'host-param-get', 'uuid=%s' % host_uuid,
                                   'param-name=software-version',
                                   'param-key=product_version']
        version = lib.do_cmd(version_cmd).split('.')[0]
        block_ipv6_handlers[version](bridge)
    logging.debug("Setup_ovs_bridge completed with result:%s" % result)
    return result


@echo
def destroy_ovs_bridge(session, args):
    bridge = args.pop("bridge")
    res = lib.check_switch()
    if res != "SUCCESS":
        return res
    res = lib.do_cmd([lib.VSCTL_PATH, "del-br", bridge])
    logging.debug("Bridge has been manually removed:%s" % res)
    if res:
        result = "FAILURE:%s" % res
    else:
        # Note that the bridge has been removed on xapi network object
        xs_nw_uuid = lib.do_cmd([xePath, "network-list",
                                "bridge=%s" % bridge, "--minimal"])
        #FIXME: WOW, this an error
        #lib.do_cmd([xePath,"network-param-set", "uuid=%s" % xs_nw_uuid,
        #                  "other-config:ovs-setup=False"])
        result = "SUCCESS:%s" % bridge

    logging.debug("Destroy_ovs_bridge completed with result:%s" % result)
    return result


@echo
def create_tunnel(session, args):
    bridge = args.pop("bridge")
    remote_ip = args.pop("remote_ip")
    gre_key = args.pop("key")
    src_host = args.pop("from")
    dst_host = args.pop("to")

    logging.debug("Entering create_tunnel")

    res = lib.check_switch()
    if res != "SUCCESS":
        logging.debug("Openvswitch running: NO")
        return "FAILURE:%s" % res

    # We need to keep the name below 14 characters
    # src and target are enough - consider a fixed length hash
    name = "t%s-%s-%s" % (gre_key, src_host, dst_host)

    # Verify the xapi bridge to be created
    # NOTE: Timeout should not be necessary anymore
    wait = [lib.VSCTL_PATH, "--timeout=30", "wait-until", "bridge",
                    bridge, "--", "get", "bridge", bridge, "name"]
    res = lib.do_cmd(wait)
    if bridge not in res:
        logging.debug("WARNING:Can't find bridge %s for creating " +
                                  "tunnel!" % bridge)
        return "FAILURE:NO_BRIDGE"
    logging.debug("bridge %s for creating tunnel - VERIFIED" % bridge)
    tunnel_setup = False
    drop_flow_setup = False
    try:
        # Create a port and configure the tunnel interface for it
        add_tunnel = [lib.VSCTL_PATH, "add-port", bridge,
                                  name, "--", "set", "interface",
                                  name, "type=gre", "options:key=%s" % gre_key,
                                  "options:remote_ip=%s" % remote_ip]
        lib.do_cmd(add_tunnel)
        tunnel_setup = True
        # verify port
        verify_port = [lib.VSCTL_PATH, "get", "port", name, "interfaces"]
        res = lib.do_cmd(verify_port)
        # Expecting python-style list as output
        iface_list = []
        if len(res) > 2:
            iface_list = res.strip()[1:-1].split(',')
        if len(iface_list) != 1:
            logging.debug("WARNING: Unexpected output while verifying " +
                                      "port %s on bridge %s" % (name, bridge))
            return "FAILURE:VERIFY_PORT_FAILED"

        # verify interface
        iface_uuid = iface_list[0]
        verify_interface_key = [lib.VSCTL_PATH, "get", "interface",
                                iface_uuid, "options:key"]
        verify_interface_ip = [lib.VSCTL_PATH, "get", "interface",
                               iface_uuid, "options:remote_ip"]

        key_validation = lib.do_cmd(verify_interface_key)
        ip_validation = lib.do_cmd(verify_interface_ip)

        if not gre_key in key_validation or not remote_ip in ip_validation:
            logging.debug("WARNING: Unexpected output while verifying " +
                          "interface %s on bridge %s" % (name, bridge))
            return "FAILURE:VERIFY_INTERFACE_FAILED"
        logging.debug("Tunnel interface validated:%s" % verify_interface_ip)
        cmd_tun_ofport = [lib.VSCTL_PATH, "get", "interface",
                                          iface_uuid, "ofport"]
        tun_ofport = lib.do_cmd(cmd_tun_ofport)
        # Ensure no trailing LF
        if tun_ofport.endswith('\n'):
            tun_ofport = tun_ofport[:-1]
        # add flow entryies for dropping broadcast coming in from gre tunnel
        lib.add_flow(bridge, priority=1000, in_port=tun_ofport,
                         dl_dst='ff:ff:ff:ff:ff:ff', actions='drop')
        lib.add_flow(bridge, priority=1000, in_port=tun_ofport,
                     nw_dst='224.0.0.0/24', actions='drop')
        drop_flow_setup = True
        logging.debug("Broadcast drop rules added")
        return "SUCCESS:%s" % name
    except:
        logging.debug("An unexpected error occured. Rolling back")
        if tunnel_setup:
            logging.debug("Deleting GRE interface")
            # Destroy GRE port and interface
            lib.del_port(bridge, name)
        if drop_flow_setup:
            # Delete flows
            logging.debug("Deleting flow entries from GRE interface")
            lib.del_flows(bridge, in_port=tun_ofport)
        # This will not cancel the original exception
        raise


@echo
def destroy_tunnel(session, args):
    bridge = args.pop("bridge")
    iface_name = args.pop("in_port")
    logging.debug("Destroying tunnel at port %s for bridge %s"
                            % (iface_name, bridge))
    ofport = get_field_of_interface(iface_name, "ofport")
    lib.del_flows(bridge, in_port=ofport)
    lib.del_port(bridge, iface_name)
    return "SUCCESS"


def get_field_of_interface(iface_name, field):
    get_iface_cmd = [lib.VSCTL_PATH, "get", "interface", iface_name, field]
    res = lib.do_cmd(get_iface_cmd)
    return res


if __name__ == "__main__":
    XenAPIPlugin.dispatch({"create_tunnel": create_tunnel,
                           "destroy_tunnel": destroy_tunnel,
                           "setup_ovs_bridge": setup_ovs_bridge,
                           "destroy_ovs_bridge": destroy_ovs_bridge})
