mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
distributed routing mode, and setup flows appropriatley script to handle the VPC topology sent from management server in JSOn format. From the JSON file, reqired configuration (tunnel setup and flow rules setup) is setup on the bridge
405 lines
17 KiB
Python
Executable File
405 lines
17 KiB
Python
Executable File
#!/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"])
|
|
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,
|
|
"--", "set", "bridge", 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, "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_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 egress table to forward packet to L3 lookup table
|
|
lib.add_flow(bridge, priority=0, table=5, actions='drop')
|
|
|
|
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]
|
|
# 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)')
|
|
|
|
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_topology(bridge, this_host_id, 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})
|