| #!/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}) |