#!/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 commands import os import sys import subprocess import time from optparse import OptionParser, OptionGroup, OptParseError, BadOptionError, OptionError, OptionConflictError, OptionValueError from time import localtime as _localtime, asctime as _asctime def setup_ovs_bridge(bridge, key, cs_host_id): res = lib.check_switch() if res != "SUCCESS": #return "FAILURE:%s" % res return 'false' logging.debug("About to manually create the bridge:%s" % bridge) #set gre_key to bridge res = lib.do_cmd([lib.VSCTL_PATH, "set", "bridge", bridge, "other_config:gre_key=%s" % key]) logging.debug("Bridge has been manually created:%s" % res) if res: # result = "FAILURE:%s" % res result = 'false' 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 result = 'true' else: # result = "FAILURE:%s" % res result = 'false' lib.do_cmd([lib.VSCTL_PATH, "set", "bridge", bridge, "other_config:is-ovs-tun-network=True"]) #get list of hosts using this bridge conf_hosts = lib.do_cmd([lib.VSCTL_PATH, "get","bridge", bridge,"other_config:ovs-host-setup"]) #add cs_host_id to list of hosts using this bridge conf_hosts = cs_host_id + (conf_hosts and ',%s' % conf_hosts or '') lib.do_cmd([lib.VSCTL_PATH, "set", "bridge", bridge, "other_config:ovs-host-setup=%s" % conf_hosts]) logging.debug("Setup_ovs_bridge completed with result:%s" % result) return result def destroy_ovs_bridge(bridge): res = lib.check_switch() if res != "SUCCESS": # return res return 'false' 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 result = 'false' else: # result = "SUCCESS:%s" % bridge result = 'true' logging.debug("Destroy_ovs_bridge completed with result:%s" % result) return result def create_tunnel(bridge, remote_ip, key, src_host, dst_host): logging.debug("Entering create_tunnel") res = lib.check_switch() if res != "SUCCESS": logging.debug("Openvswitch running: NO") # return "FAILURE:%s" % res return 'false' # We need to keep the name below 14 characters # src and target are enough - consider a fixed length hash name = "t%s-%s-%s" % (key, src_host, dst_host) # Verify the 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" return 'false' 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" % 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" return 'false' # 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 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" return 'false' 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 return 'true' 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 def destroy_tunnel(bridge, iface_name): 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" return 'true' 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__': logging.basicConfig(filename="/var/log/cloudstack/agent/ovstunnel.log", format="%(asctime)s - %(message)s", level=logging.DEBUG) parser = OptionParser() parser.add_option("--key", dest="key") parser.add_option("--cs_host_id", dest="cs_host_id") parser.add_option("--bridge", dest="bridge") parser.add_option("--remote_ip", dest="remote_ip") parser.add_option("--src_host", dest="src_host") parser.add_option("--dst_host", dest="dst_host") parser.add_option("--iface_name", dest="iface_name") (option, args) = parser.parse_args() if len(args) == 0: logging.debug("No command to execute") sys.exit(1) cmd = args[0] if cmd == "setup_ovs_bridge": setup_ovs_bridge(option.bridge, option.key, option.cs_host_id) elif cmd == "destroy_ovs_bridge": destroy_ovs_bridge(option.bridge) elif cmd == "create_tunnel": create_tunnel(option.bridge, option.remote_ip, option.key, option.src_host, option.dst_host) elif cmd == "destroy_tunnel": destroy_tunnel(option.bridge, option.iface_name) else: logging.debug("Unknown command: " + cmd) sys.exit(1)