#!/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 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/cloud/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__ logging.debug("#### VMOPS enter %s ####" % name) res = fn(*v, **k) logging.debug("#### 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]) # enable stp lib.do_cmd([lib.VSCTL_PATH, "set", "Bridge", bridge, "stp_enable=true"]) # 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"]) lib.do_cmd([lib.XE_PATH, "network-param-set", "uuid=%s" % xs_nw_uuid, "other-config:is-ovs_vpc_distributed_vr_network=False"]) 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 # Temporarily no need BLOCK IPv6 # 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 setup_ovs_bridge_for_distributed_routing(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 res = lib.do_cmd([lib.VSCTL_PATH, "--", "--may-exist", "add-br", bridge]) 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, "list", "bridge", bridge]) # 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=False"]) lib.do_cmd([lib.XE_PATH, "network-param-set", "uuid=%s" % xs_nw_uuid, "other-config:is-ovs-vpc-distributed-vr-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]) # add a default flow rule to send broadcast and multi-cast packets to L2 flooding table lib.add_flow(bridge, priority=1000, dl_dst='ff:ff:ff:ff:ff:ff', table=0, actions='resubmit(,2)') lib.add_flow(bridge, priority=1000, nw_dst='224.0.0.0/24', table=0, actions='resubmit(,2)') # add a default flow rule to send uni-cast traffic to L2 lookup table lib.add_flow(bridge, priority=0, table=0, actions='resubmit(,1)') # add a default rule to send unknown mac address to L2 flooding table lib.add_flow(bridge, priority=0, table=1, actions='resubmit(,2)') # add a default rule in L2 flood table to drop packet lib.add_flow(bridge, priority=0, table=2, actions='drop') # add a default rule in egress table to forward packet to L3 lookup table lib.add_flow(bridge, priority=0, table=3, actions='resubmit(,4)') # add a default rule in L3 lookup table to forward packet to L2 lookup table lib.add_flow(bridge, priority=0, table=4, actions='resubmit(,1)') # add a default rule in ingress table to drop in bound packets lib.add_flow(bridge, priority=0, table=5, actions='drop') result = "SUCCESS: successfully setup bridge with flow rules" 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") network_uuid = args.pop("cloudstack-network-id") 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] # find xs network for this bridge, verify is used for ovs tunnel network xs_nw_uuid = lib.do_cmd([lib.XE_PATH, "network-list", "bridge=%s" % bridge, "--minimal"]) ovs_tunnel_network = lib.do_cmd([lib.XE_PATH,"network-param-get", "uuid=%s" % xs_nw_uuid, "param-name=other-config", "param-key=is-ovs-tun-network", "--minimal"]) ovs_vpc_distributed_vr_network = lib.do_cmd([lib.XE_PATH,"network-param-get", "uuid=%s" % xs_nw_uuid, "param-name=other-config", "param-key=is-ovs-vpc-distributed-vr-network", "--minimal"]) if ovs_tunnel_network == 'True': # 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") if ovs_vpc_distributed_vr_network == 'True': # add flow rules for dropping broadcast coming in from tunnel ports lib.add_flow(bridge, priority=1000, in_port=tun_ofport, table=0, dl_dst='ff:ff:ff:ff:ff:ff', actions='drop') lib.add_flow(bridge, priority=1000, in_port=tun_ofport, table=0, nw_dst='224.0.0.0/24', actions='drop') # add flow rule to send the traffic from tunnel ports to L2 switching table only lib.add_flow(bridge, priority=1000, in_port=tun_ofport, table=0, actions='resubmit(,1)') lib.do_cmd([lib.VSCTL_PATH, "set", "interface", name, "options:cloudstack-network-id=%s" % network_uuid]) 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 def is_xcp(session, args): 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() status, output = commands.getstatusoutput("xe host-param-list uuid="+host_uuid+" | grep platform_name") if (status != 0): return "FALSE" platform_cmd = [lib.XE_PATH, 'host-param-get', 'uuid=%s' % host_uuid, 'param-name=software-version', 'param-key=platform_name'] platform = lib.do_cmd(platform_cmd).split('.')[0] return platform def getLabel(session, args): i = 0 pif_list_cmd = [lib.XE_PATH, 'pif-list', '--minimal'] pif_list_str = lib.do_cmd(pif_list_cmd) while True: pif_uuid = pif_list_str.split(',')[i].strip() network_cmd = [lib.XE_PATH, 'pif-param-get', 'uuid=%s' % pif_uuid, 'param-name=network-uuid'] network_uuid = lib.do_cmd(network_cmd).split('.')[0] iface_cmd = [lib.XE_PATH, 'network-param-get', 'uuid=%s' % network_uuid, 'param-name=bridge'] iface = lib.do_cmd(iface_cmd) status,output = commands.getstatusoutput("ifconfig "+iface+" | grep inet") if (status != 0): i += 1 continue label_cmd = [lib.XE_PATH, 'network-param-get', 'uuid=%s' % network_uuid, 'param-name=name-label'] label = lib.do_cmd(label_cmd).split('.')[0] return label return False @echo def configure_ovs_bridge_for_network_topology(session, args): bridge = args.pop("bridge") json_config = args.pop("config") this_host_id = args.pop("host-id") return lib.configure_bridge_for_network_topology(bridge, this_host_id, json_config) @echo def configure_ovs_bridge_for_routing_policies(session, args): bridge = args.pop("bridge") json_config = args.pop("config") return lib.configure_ovs_bridge_for_router_policies(bridge, json_config) 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, "is_xcp": is_xcp, "getLabel": getLabel, "setup_ovs_bridge_for_distributed_routing": setup_ovs_bridge_for_distributed_routing, "configure_ovs_bridge_for_network_topology": configure_ovs_bridge_for_network_topology, "configure_ovs_bridge_for_routing_policies": "configure_ovs_bridge_for_routing_policies"})