adding KVM support for GRE controller Signed-off-by: Hugo Trippaers <[email protected]>
Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/3056b1a9 Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/3056b1a9 Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/3056b1a9 Branch: refs/heads/sdnextensions Commit: 3056b1a9f8be155d0a66117d8335d6c3a23dcad0 Parents: a0a8183 Author: tuna <[email protected]> Authored: Tue Sep 17 10:27:49 2013 +0700 Committer: Hugo Trippaers <[email protected]> Committed: Mon Oct 7 13:55:18 2013 +0200 ---------------------------------------------------------------------- plugins/hypervisors/kvm/pom.xml | 5 + .../kvm/resource/LibvirtComputingResource.java | 265 ++++++++++++++++++- .../vm/hypervisor/kvm/cloudstack_pluginlib.py | 219 +++++++++++++++ scripts/vm/network/vnet/ovstunnel.py | 182 +++++++++++++ 4 files changed, 658 insertions(+), 13 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cloudstack/blob/3056b1a9/plugins/hypervisors/kvm/pom.xml ---------------------------------------------------------------------- diff --git a/plugins/hypervisors/kvm/pom.xml b/plugins/hypervisors/kvm/pom.xml index 1babe7c..9d055f1 100644 --- a/plugins/hypervisors/kvm/pom.xml +++ b/plugins/hypervisors/kvm/pom.xml @@ -41,6 +41,11 @@ <version>${cs.libvirt-java.version}</version> </dependency> <dependency> + <groupId>org.apache.cloudstack</groupId> + <artifactId>cloud-plugin-network-ovs</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> <groupId>com.ceph</groupId> <artifactId>rados</artifactId> <version>${cs.rados-java.version}</version> http://git-wip-us.apache.org/repos/asf/cloudstack/blob/3056b1a9/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java ---------------------------------------------------------------------- diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index 914017c..c9e34c5 100755 --- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -63,8 +63,8 @@ import org.apache.cloudstack.utils.qemu.QemuImg; import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat; import org.apache.cloudstack.utils.qemu.QemuImgException; import org.apache.cloudstack.utils.qemu.QemuImgFile; -import org.apache.log4j.Logger; import org.apache.commons.io.FileUtils; +import org.apache.log4j.Logger; import org.libvirt.Connect; import org.libvirt.Domain; import org.libvirt.DomainBlockStats; @@ -121,6 +121,13 @@ import com.cloud.agent.api.NetworkRulesSystemVmCommand; import com.cloud.agent.api.NetworkRulesVmSecondaryIpCommand; import com.cloud.agent.api.NetworkUsageAnswer; import com.cloud.agent.api.NetworkUsageCommand; +import com.cloud.agent.api.OvsCreateTunnelAnswer; +import com.cloud.agent.api.OvsCreateTunnelCommand; +import com.cloud.agent.api.OvsDestroyBridgeCommand; +import com.cloud.agent.api.OvsDestroyTunnelCommand; +import com.cloud.agent.api.OvsFetchInterfaceAnswer; +import com.cloud.agent.api.OvsFetchInterfaceCommand; +import com.cloud.agent.api.OvsSetupBridgeCommand; import com.cloud.agent.api.PingCommand; import com.cloud.agent.api.PingRoutingCommand; import com.cloud.agent.api.PingRoutingWithNwGroupsCommand; @@ -266,7 +273,7 @@ import com.cloud.vm.VirtualMachineName; **/ @Local(value = { ServerResource.class }) public class LibvirtComputingResource extends ServerResourceBase implements -ServerResource { + ServerResource { private static final Logger s_logger = Logger .getLogger(LibvirtComputingResource.class); @@ -282,6 +289,7 @@ ServerResource { private String _ovsPvlanDhcpHostPath; private String _ovsPvlanVmPath; private String _routerProxyPath; + private String _ovsTunnelPath; private String _host; private String _dcId; private String _pod; @@ -289,6 +297,7 @@ ServerResource { private int _migrateSpeed; private long _hvVersion; + private long _kernelVersion; private KVMHAMonitor _monitor; private final String _SSHKEYSPATH = "/root/.ssh"; private final String _SSHPRVKEYPATH = _SSHKEYSPATH + File.separator @@ -597,6 +606,11 @@ ServerResource { "Unable to find the security_group.py"); } + _ovsTunnelPath = Script.findScript(networkScriptsDir, "ovstunnel.py"); + if (_ovsTunnelPath == null) { + throw new ConfigurationException("Unable to find the ovstunnel.py"); + } + _routerProxyPath = Script.findScript("scripts/network/domr/", "router_proxy.sh"); if (_routerProxyPath == null) { @@ -846,6 +860,11 @@ ServerResource { storageProcessor.configure(name, params); storageHandler = new StorageSubsystemCommandHandlerBase(storageProcessor); + String unameKernelVersion = Script.runSimpleBashScript("uname -r"); + String[] kernelVersions = unameKernelVersion.split("[\\.\\-]"); + _kernelVersion = Integer.parseInt(kernelVersions[0]) * 1000 * 1000 + + Integer.parseInt(kernelVersions[1]) * 1000 + + Integer.parseInt(kernelVersions[2]); return true; } @@ -1069,7 +1088,7 @@ ServerResource { return vnetId; } - private void passCmdLine(String vmName, String cmdLine) + private boolean passCmdLine(String vmName, String cmdLine) throws InternalErrorException { final Script command = new Script(_patchViaSocketPath, _timeout, s_logger); String result; @@ -1077,8 +1096,10 @@ ServerResource { command.add("-p", cmdLine.replaceAll(" ", "%")); result = command.execute(); if (result != null) { - throw new InternalErrorException(result); + s_logger.debug("passcmd failed:" + result); + return false; } + return true; } boolean isDirectAttachedNetwork(String type) { @@ -1256,7 +1277,17 @@ ServerResource { return this.storageHandler.handleStorageCommands((StorageSubSystemCommand)cmd); } else if (cmd instanceof PvlanSetupCommand) { return execute((PvlanSetupCommand) cmd); - } else { + } else if (cmd instanceof OvsFetchInterfaceCommand) { + return execute((OvsFetchInterfaceCommand) cmd); + } else if (cmd instanceof OvsSetupBridgeCommand) { + return execute((OvsSetupBridgeCommand) cmd); + } else if (cmd instanceof OvsDestroyBridgeCommand) { + return execute((OvsDestroyBridgeCommand) cmd); + } else if (cmd instanceof OvsCreateTunnelCommand) { + return execute((OvsCreateTunnelCommand) cmd); + } else if (cmd instanceof OvsDestroyTunnelCommand) { + return execute((OvsDestroyTunnelCommand) cmd); + } else { s_logger.warn("Unsupported command "); return Answer.createUnsupportedCommandAnswer(cmd); } @@ -1265,6 +1296,188 @@ ServerResource { } } + // Tuna added + private OvsFetchInterfaceAnswer execute(OvsFetchInterfaceCommand cmd) { + String label = cmd.getLabel(); + s_logger.debug("Will look for network with name-label:" + label); + try { + String ipadd = Script.runSimpleBashScript("ifconfig " + label + " | grep 'inet addr:' | cut -d: -f2 | awk '{ print $1}'"); + String mask = Script.runSimpleBashScript("ifconfig " + label + " | grep 'inet addr:' | cut -d: -f4"); + String mac = Script.runSimpleBashScript("ifconfig " + label + " | grep HWaddr | awk -F \" \" '{print $5}'"); + return new OvsFetchInterfaceAnswer(cmd, true, "Interface " + label + + " retrieved successfully", ipadd, mask, mac); + + } catch (Exception e) { + s_logger.warn("Caught execption when fetching interface", e); + return new OvsFetchInterfaceAnswer(cmd, false, "EXCEPTION:" + + e.getMessage()); + } + + } + + private Answer execute(OvsSetupBridgeCommand cmd) { + findOrCreateTunnelNetwork(cmd.getKey()); + configureTunnelNetwork(cmd.getNetworkId(), cmd.getHostId(), + cmd.getKey()); + s_logger.debug("OVS Bridge configured"); + return new Answer(cmd, true, null); + } + + private Answer execute(OvsDestroyBridgeCommand cmd) { + destroyTunnelNetwork(cmd.getKey()); + s_logger.debug("OVS Bridge destroyed"); + return new Answer(cmd, true, null); + } + + private synchronized void destroyTunnelNetwork(int key) { + try { + findOrCreateTunnelNetwork(key); + String bridge = "OVSTunnel" + key; + Script cmd = new Script(_ovsTunnelPath, _timeout, s_logger); + cmd.add("destroy_ovs_bridge"); + cmd.add("--bridge", bridge); + String result = cmd.execute(); + String[] res = result.split(":"); + if (res.length != 2 || !res[0].equalsIgnoreCase("SUCCESS")) { + // TODO: Should make this error not fatal? + // Can Concurrent VM shutdown/migration/reboot events can cause + // this method + // to be executed on a bridge which has already been removed? + throw new CloudRuntimeException("Unable to remove OVS bridge " + + bridge + ":" + res); + } + return; + } catch (Exception e) { + s_logger.warn("destroyTunnelNetwork failed:", e); + return; + } + } + + private boolean networkExist(String nwName) { + Script.runSimpleBashScript("ifconfig " + nwName); + String result = Script.runSimpleBashScript("echo $?"); + return result.equals("0"); + } + + private synchronized boolean findOrCreateTunnelNetwork(long key) { + try { + String nwName = "OVSTunnel" + key; + if (networkExist(nwName)) { + return true; + } + // if not found, create a new one + Map<String, String> otherConfig = new HashMap<String, String>(); + otherConfig.put("ovs-host-setup", ""); + Script.runSimpleBashScript("ovs-vsctl -- --may-exist add-br " + + nwName + " -- set bridge " + nwName + + " other_config:ovs_host_setup=\" \""); + s_logger.debug("### KVM network for tunnels created:" + nwName); + } catch (Exception e) { + s_logger.warn("createTunnelNetwork failed", e); + } + return true; + } + + private synchronized boolean configureTunnelNetwork(long networkId, + long hostId, int key) { + try { + findOrCreateTunnelNetwork(key); + String nwName = "OVSTunnel" + key; + String configuredHosts = Script + .runSimpleBashScript("ovs-vsctl get bridge " + nwName + + " other_config:ovs_host_setup"); + boolean configured = false; + if (configuredHosts != null) { + String hostIdsStr[] = configuredHosts.split(","); + for (String hostIdStr : hostIdsStr) { + if (hostIdStr.equals(((Long) hostId).toString())) { + configured = true; + break; + } + } + } + if (!configured) { + Script cmd = new Script(_ovsTunnelPath, _timeout, s_logger); + cmd.add("setup_ovs_bridge"); + cmd.add("--key", String.valueOf(key)); + cmd.add("--cs_host_id", ((Long) hostId).toString()); + cmd.add("--bridge", nwName); + String result = cmd.execute(); + String[] res = result.split(":"); + if (res.length != 2 || !res[0].equalsIgnoreCase("SUCCESS")) { + throw new CloudRuntimeException( + "Unable to pre-configure OVS bridge " + nwName + + " for network ID:" + networkId + " - " + + res); + } + } + } catch (Exception e) { + s_logger.warn("createandConfigureTunnelNetwork failed", e); + return false; + } + return true; + } + + private OvsCreateTunnelAnswer execute(OvsCreateTunnelCommand cmd) { + String bridge = "OVSTunnel" + cmd.getKey(); + try { + if (!findOrCreateTunnelNetwork(cmd.getKey())) { + s_logger.debug("Error during bridge setup"); + return new OvsCreateTunnelAnswer(cmd, false, + "Cannot create network", bridge); + } + + configureTunnelNetwork(cmd.getNetworkId(), cmd.getFrom(), + cmd.getKey()); + Script command = new Script(_ovsTunnelPath, _timeout, s_logger); + command.add("create_tunnel"); + command.add("--bridge", bridge); + command.add("--remote_ip", cmd.getRemoteIp()); + command.add("--key", cmd.getKey().toString()); + command.add("--src_host", cmd.getFrom().toString()); + command.add("--dst_host", cmd.getTo().toString()); + + String result = command.execute(); + String[] res = result.split(":"); + if (res.length == 2 && res[0].equalsIgnoreCase("SUCCESS")) { + return new OvsCreateTunnelAnswer(cmd, true, result, res[1], + bridge); + } else { + return new OvsCreateTunnelAnswer(cmd, false, result, bridge); + } + } catch (Exception e) { + s_logger.debug("Error during tunnel setup"); + s_logger.warn("Caught execption when creating ovs tunnel", e); + return new OvsCreateTunnelAnswer(cmd, false, e.getMessage(), bridge); + } + } + + private Answer execute(OvsDestroyTunnelCommand cmd) { + try { + if (!findOrCreateTunnelNetwork(cmd.getKey())) { + s_logger.warn("Unable to find tunnel network for GRE key:" + + cmd.getKey()); + return new Answer(cmd, false, "No network found"); + } + + String bridge = "OVSTunnel" + cmd.getKey(); + Script command = new Script(_ovsTunnelPath, _timeout, s_logger); + command.add("destroy_tunnel"); + command.add("--bridge", bridge); + command.add("--iface_name", cmd.getInPortName()); + String result = command.execute(); + if (result.equalsIgnoreCase("SUCCESS")) { + return new Answer(cmd, true, result); + } else { + return new Answer(cmd, false, result); + } + } catch (Exception e) { + s_logger.warn("caught execption when destroy ovs tunnel", e); + return new Answer(cmd, false, e.getMessage()); + } + } + // end Tuna added + private CheckNetworkAnswer execute(CheckNetworkCommand cmd) { List<PhysicalNetworkSetupInfo> phyNics = cmd .getPhysicalNetworkInfoList(); @@ -2930,7 +3143,8 @@ ServerResource { } } - private Answer execute(RebootCommand cmd) { + + private Answer execute(RebootCommand cmd) { synchronized (_vms) { _vms.put(cmd.getVmName(), State.Starting); @@ -3011,7 +3225,8 @@ ServerResource { } } - protected Answer execute(StopCommand cmd) { + + protected Answer execute(StopCommand cmd) { final String vmName = cmd.getVmName(); State state = null; @@ -3178,8 +3393,8 @@ ServerResource { if (vmTO.getMinRam() != vmTO.getMaxRam()){ grd.setMemBalloning(true); - grd.setCurrentMem((long)vmTO.getMinRam()/1024); - grd.setMemorySize((long)vmTO.getMaxRam()/1024); + grd.setCurrentMem(vmTO.getMinRam()/1024); + grd.setMemorySize(vmTO.getMaxRam()/1024); } else{ grd.setMemorySize(vmTO.getMaxRam() / 1024); @@ -3268,7 +3483,8 @@ ServerResource { } } - protected synchronized StartAnswer execute(StartCommand cmd) { + + protected synchronized StartAnswer execute(StartCommand cmd) { VirtualMachineTO vmSpec = cmd.getVirtualMachine(); vmSpec.setVncAddr(cmd.getHostIp()); String vmName = vmSpec.getName(); @@ -3317,8 +3533,31 @@ ServerResource { } // pass cmdline info to system vms + // if (vmSpec.getType() != VirtualMachine.Type.User) { + // passCmdLine(vmName, vmSpec.getBootArgs() ); + // } + // merge with master branch + // pass cmdline info to system vms if (vmSpec.getType() != VirtualMachine.Type.User) { - passCmdLine(vmName, vmSpec.getBootArgs() ); + if ((_kernelVersion < 2006034) && (conn.getVersion() < 1001000)) { + // CLOUDSTACK-2823: try passCmdLine some times if kernel < + // 2.6.34 and qemu <1.1.0 on hypervisor + // (for instance, CentOS 6.4) + // wait for 5 minutes at most + for (int count = 0; count < 30; count++) { + boolean succeed = passCmdLine(vmName, vmSpec.getBootArgs()); + if (succeed) { + break; + } + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + s_logger.trace("Ignoring InterruptedException.", e); + } + } + } else { + passCmdLine(vmName, vmSpec.getBootArgs()); + } } state = State.Running; @@ -4589,7 +4828,7 @@ ServerResource { bytes_rd += blockStats.rd_bytes; bytes_wr += blockStats.wr_bytes; } - + if (oldStats != null) { long deltaiord = io_rd - oldStats._io_rd; if (deltaiord > 0) @@ -4604,7 +4843,7 @@ ServerResource { if (deltabyteswr > 0) stats.setDiskWriteKBs(deltabyteswr / 1024); } - + /* save to Hashmap */ vmStats newStat = new vmStats(); newStat._usedTime = info.cpuTime; http://git-wip-us.apache.org/repos/asf/cloudstack/blob/3056b1a9/scripts/vm/hypervisor/kvm/cloudstack_pluginlib.py ---------------------------------------------------------------------- diff --git a/scripts/vm/hypervisor/kvm/cloudstack_pluginlib.py b/scripts/vm/hypervisor/kvm/cloudstack_pluginlib.py new file mode 100644 index 0000000..f886aa3 --- /dev/null +++ b/scripts/vm/hypervisor/kvm/cloudstack_pluginlib.py @@ -0,0 +1,219 @@ +# 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. + +# cloudstack_pluginlib for openvswitch on KVM hypervisor + +import ConfigParser +import logging +import os +import subprocess + +from time import localtime, asctime + +DEFAULT_LOG_FORMAT = "%(asctime)s %(levelname)8s [%(name)s] %(message)s" +DEFAULT_LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S" +DEFAULT_LOG_FILE = "/var/log/cloudstack_plugins.log" + +PLUGIN_CONFIG_PATH = "/usr/share/cloudstack-common/scripts/vm/hypervisor/xenserver/cloudstack_plugins.conf" +OVSDB_PID_PATH = "/var/run/openvswitch/ovsdb-server.pid" +OVSDB_DAEMON_PATH = "ovsdb-server" +OVS_PID_PATH = "/var/run/openvswitch/ovs-vswitchd.pid" +OVS_DAEMON_PATH = "ovs-vswitchd" +VSCTL_PATH = "/usr/bin/ovs-vsctl" +OFCTL_PATH = "/usr/bin/ovs-ofctl" + +class PluginError(Exception): + """Base Exception class for all plugin errors.""" + def __init__(self, *args): + Exception.__init__(self, *args) + + +def setup_logging(log_file=None): + debug = False + verbose = False + log_format = DEFAULT_LOG_FORMAT + log_date_format = DEFAULT_LOG_DATE_FORMAT + # try to read plugin configuration file + if os.path.exists(PLUGIN_CONFIG_PATH): + config = ConfigParser.ConfigParser() + config.read(PLUGIN_CONFIG_PATH) + try: + options = config.options('LOGGING') + if 'debug' in options: + debug = config.getboolean('LOGGING', 'debug') + if 'verbose' in options: + verbose = config.getboolean('LOGGING', 'verbose') + if 'format' in options: + log_format = config.get('LOGGING', 'format') + if 'date_format' in options: + log_date_format = config.get('LOGGING', 'date_format') + if 'file' in options: + log_file_2 = config.get('LOGGING', 'file') + except ValueError: + # configuration file contained invalid attributes + # ignore them + pass + except ConfigParser.NoSectionError: + # Missing 'Logging' section in configuration file + pass + + root_logger = logging.root + if debug: + root_logger.setLevel(logging.DEBUG) + elif verbose: + root_logger.setLevel(logging.INFO) + else: + root_logger.setLevel(logging.WARNING) + formatter = logging.Formatter(log_format, log_date_format) + + log_filename = log_file or log_file_2 or DEFAULT_LOG_FILE + + logfile_handler = logging.FileHandler(log_filename) + logfile_handler.setFormatter(formatter) + root_logger.addHandler(logfile_handler) + + +def do_cmd(cmd): + """Abstracts out the basics of issuing system commands. If the command + returns anything in stderr, a PluginError is raised with that information. + Otherwise, the output from stdout is returned. + """ + + pipe = subprocess.PIPE + logging.debug("Executing:%s", cmd) + proc = subprocess.Popen(cmd, shell=False, stdin=pipe, stdout=pipe, + stderr=pipe, close_fds=True) + ret_code = proc.wait() + err = proc.stderr.read() + if ret_code: + logging.debug("The command exited with the error code: " + + "%s (stderr output:%s)" % (ret_code, err)) + raise PluginError(err) + output = proc.stdout.read() + if output.endswith('\n'): + output = output[:-1] + return output + + +def _is_process_run(pidFile, name): + try: + fpid = open(pidFile, "r") + pid = fpid.readline() + fpid.close() + except IOError, e: + return -1 + + pid = pid[:-1] + ps = os.popen("ps -ae") + for l in ps: + if pid in l and name in l: + ps.close() + return 0 + + ps.close() + return -2 + + +def _is_tool_exist(name): + if os.path.exists(name): + return 0 + return -1 + + +def check_switch(): + global result + + ret = _is_process_run(OVSDB_PID_PATH, OVSDB_DAEMON_PATH) + if ret < 0: + if ret == -1: + return "NO_DB_PID_FILE" + if ret == -2: + return "DB_NOT_RUN" + + ret = _is_process_run(OVS_PID_PATH, OVS_DAEMON_PATH) + if ret < 0: + if ret == -1: + return "NO_SWITCH_PID_FILE" + if ret == -2: + return "SWITCH_NOT_RUN" + + if _is_tool_exist(VSCTL_PATH) < 0: + return "NO_VSCTL" + + if _is_tool_exist(OFCTL_PATH) < 0: + return "NO_OFCTL" + + return "SUCCESS" + + +def _build_flow_expr(**kwargs): + is_delete_expr = kwargs.get('delete', False) + flow = "" + if not is_delete_expr: + flow = "hard_timeout=%s,idle_timeout=%s,priority=%s"\ + % (kwargs.get('hard_timeout', '0'), + kwargs.get('idle_timeout', '0'), + kwargs.get('priority', '1')) + in_port = 'in_port' in kwargs and ",in_port=%s" % kwargs['in_port'] or '' + dl_type = 'dl_type' in kwargs and ",dl_type=%s" % kwargs['dl_type'] or '' + dl_src = 'dl_src' in kwargs and ",dl_src=%s" % kwargs['dl_src'] or '' + dl_dst = 'dl_dst' in kwargs and ",dl_dst=%s" % kwargs['dl_dst'] or '' + nw_src = 'nw_src' in kwargs and ",nw_src=%s" % kwargs['nw_src'] or '' + nw_dst = 'nw_dst' in kwargs and ",nw_dst=%s" % kwargs['nw_dst'] or '' + proto = 'proto' in kwargs and ",%s" % kwargs['proto'] or '' + ip = ('nw_src' in kwargs or 'nw_dst' in kwargs) and ',ip' or '' + flow = (flow + in_port + dl_type + dl_src + dl_dst + + (ip or proto) + nw_src + nw_dst) + return flow + + +def add_flow(bridge, **kwargs): + """ + Builds a flow expression for **kwargs and adds the flow entry + to an Open vSwitch instance + """ + flow = _build_flow_expr(**kwargs) + actions = 'actions' in kwargs and ",actions=%s" % kwargs['actions'] or '' + flow = flow + actions + addflow = [OFCTL_PATH, "add-flow", bridge, flow] + do_cmd(addflow) + + +def del_flows(bridge, **kwargs): + """ + Removes flows according to criteria passed as keyword. + """ + flow = _build_flow_expr(delete=True, **kwargs) + # out_port condition does not exist for all flow commands + out_port = ("out_port" in kwargs and + ",out_port=%s" % kwargs['out_port'] or '') + flow = flow + out_port + delFlow = [OFCTL_PATH, 'del-flows', bridge, flow] + do_cmd(delFlow) + + +def del_all_flows(bridge): + delFlow = [OFCTL_PATH, "del-flows", bridge] + do_cmd(delFlow) + + normalFlow = "priority=0 idle_timeout=0 hard_timeout=0 actions=normal" + add_flow(bridge, normalFlow) + + +def del_port(bridge, port): + delPort = [VSCTL_PATH, "del-port", bridge, port] + do_cmd(delPort) http://git-wip-us.apache.org/repos/asf/cloudstack/blob/3056b1a9/scripts/vm/network/vnet/ovstunnel.py ---------------------------------------------------------------------- diff --git a/scripts/vm/network/vnet/ovstunnel.py b/scripts/vm/network/vnet/ovstunnel.py new file mode 100644 index 0000000..67ef89b --- /dev/null +++ b/scripts/vm/network/vnet/ovstunnel.py @@ -0,0 +1,182 @@ +#!/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 time import localtime as _localtime, asctime as _asctime + +lib.setup_logging("/var/log/ovstunnel.log") + +def setup_ovs_bridge(bridge, key, cs_host_id): + + res = lib.check_switch() + if res != "SUCCESS": + return "FAILURE:%s" % res + + 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 + 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 + + 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 + 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: + result = "SUCCESS:%s" % bridge + + logging.debug("Destroy_ovs_bridge completed with result:%s" % result) + return result + +def create_tunnel(bridge, remote_ip, gre_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 + + # 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 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 + +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" + +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
