diff --git a/api/src/com/cloud/network/ovs/OvsCreateGreTunnelCommand.java b/api/src/com/cloud/network/ovs/OvsCreateGreTunnelCommand.java new file mode 100644 index 00000000000..3cec9f69295 --- /dev/null +++ b/api/src/com/cloud/network/ovs/OvsCreateGreTunnelCommand.java @@ -0,0 +1,26 @@ +package com.cloud.network.ovs; + +import com.cloud.agent.api.Command; + +public class OvsCreateGreTunnelCommand extends Command { + String remoteIp; + String key; + + @Override + public boolean executeInSequence() { + return true; + } + + public OvsCreateGreTunnelCommand(String remoteIp, String key) { + this.remoteIp = remoteIp; + this.key = key; + } + + public String getRemoteIp() { + return remoteIp; + } + + public String getKey() { + return key; + } +} diff --git a/api/src/com/cloud/network/ovs/OvsSetTagAndFlowCommand.java b/api/src/com/cloud/network/ovs/OvsSetTagAndFlowCommand.java new file mode 100644 index 00000000000..0ed7a67bfa5 --- /dev/null +++ b/api/src/com/cloud/network/ovs/OvsSetTagAndFlowCommand.java @@ -0,0 +1,26 @@ +package com.cloud.network.ovs; + +import com.cloud.agent.api.Command; + +public class OvsSetTagAndFlowCommand extends Command { + String vlans; + String vmName; + + @Override + public boolean executeInSequence() { + return true; + } + + public String getVlans() { + return vlans; + } + + public String getVmName() { + return vmName; + } + + public OvsSetTagAndFlowCommand(String vmName, String vlans) { + this.vmName = vmName; + this.vlans = vlans; + } +} diff --git a/core/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java b/core/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java index d7aa0500dd9..66bec2dcb60 100644 --- a/core/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java +++ b/core/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java @@ -155,9 +155,12 @@ import com.cloud.host.Host.Type; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.network.HAProxyConfigurator; import com.cloud.network.LoadBalancerConfigurator; +import com.cloud.network.Networks; import com.cloud.network.Networks.BroadcastDomainType; import com.cloud.network.Networks.IsolationType; import com.cloud.network.Networks.TrafficType; +import com.cloud.network.ovs.OvsCreateGreTunnelCommand; +import com.cloud.network.ovs.OvsSetTagAndFlowCommand; import com.cloud.resource.ServerResource; import com.cloud.storage.Storage; import com.cloud.storage.Storage.ImageFormat; @@ -442,6 +445,10 @@ public abstract class CitrixResourceBase implements ServerResource { return execute((CheckSshCommand)cmd); } else if (cmd instanceof SecurityIngressRulesCmd) { return execute((SecurityIngressRulesCmd) cmd); + } else if (cmd instanceof OvsCreateGreTunnelCommand) { + return execute((OvsCreateGreTunnelCommand)cmd); + } else if (cmd instanceof OvsSetTagAndFlowCommand) { + return execute((OvsSetTagAndFlowCommand)cmd); } else { return Answer.createUnsupportedCommandAnswer(cmd); } @@ -466,6 +473,92 @@ public abstract class CitrixResourceBase implements ServerResource { throw new CloudRuntimeException("Unsupported network type: " + type); } + /** + * This is a tricky to create network in xenserver. + * if you create a network then create bridge by brctl or openvswitch yourself, + * then you will get an expection that is "REQUIRED_NETWROK" when you start a + * vm with this network. The soultion is, create a vif of dom0 and plug it in + * network, xenserver will create the bridge on behalf of you + * @throws XmlRpcException + * @throws XenAPIException + */ + private void enableXenServerNetwork(Connection conn, Network nw, + String vifNameLabel, String networkDesc) throws XenAPIException, XmlRpcException { + /* Make sure there is a physical bridge on this network */ + VIF dom0vif = null; + Pair vm = getControlDomain(conn); + VM dom0 = vm.first(); + Set vifs = dom0.getVIFs(conn); + if (vifs.size() != 0) { + for (VIF vif : vifs) { + Map otherConfig = vif.getOtherConfig(conn); + if (otherConfig != null) { + String nameLabel = otherConfig.get("nameLabel"); + if ((nameLabel != null) && nameLabel.equalsIgnoreCase(vifNameLabel)) { + dom0vif = vif; + } + } + } + } + /* create temp VIF0 */ + if (dom0vif == null) { + s_logger.debug("Can't find a vif on dom0 for " + networkDesc + ", creating a new one"); + VIF.Record vifr = new VIF.Record(); + vifr.VM = dom0; + vifr.device = getLowestAvailableVIFDeviceNum(conn, dom0); + if (vifr.device == null) { + s_logger.debug("Failed to create " + networkDesc + ", no vif available"); + return; + } + Map config = new HashMap(); + config.put("nameLabel", vifNameLabel); + vifr.otherConfig = config; + vifr.MAC = "FE:FF:FF:FF:FF:FF"; + vifr.network = nw; + dom0vif = VIF.create(conn, vifr); + dom0vif.plug(conn); + } else { + s_logger.debug("already have a vif on dom0 for " + networkDesc); + if (!dom0vif.getCurrentlyAttached(conn)) { + dom0vif.plug(conn); + } + } + } + + private Network setupvSwitchNetwork(Connection conn) { + try { + Network vswitchNw = null; + + if (_host.vswitchNetwork == null) { + Network.Record rec = new Network.Record(); + String nwName = Networks.BroadcastScheme.VSwitch.toString(); + Set networks = Network.getByNameLabel(conn, nwName); + + if (networks.size() == 0) { + rec.nameDescription = "vswitch network for " + nwName; + rec.nameLabel = nwName; + vswitchNw = Network.create(conn, rec); + } else { + vswitchNw = networks.iterator().next(); + } + + enableXenServerNetwork(conn, vswitchNw, "vswitch", + "vswicth network"); + _host.vswitchNetwork = vswitchNw.getUuid(conn); + } else { + vswitchNw = Network.getByUuid(conn, _host.vswitchNetwork); + enableXenServerNetwork(conn, vswitchNw, "vswitch", + "vswicth network"); + } + + return vswitchNw; + } catch (Exception e) { + e.printStackTrace(); + } + + return null; + } + protected Network getNetwork(Connection conn, NicTO nic) throws XenAPIException, XmlRpcException { Pair network = getNativeNetworkForTraffic(conn, nic.getType()); if (nic.getBroadcastUri() != null && nic.getBroadcastUri().toString().contains("untagged")) { @@ -477,7 +570,9 @@ public abstract class CitrixResourceBase implements ServerResource { return enableVlanNetwork(conn, vlan, network.first(), network.second()); } else if (nic.getBroadcastType() == BroadcastDomainType.Native || nic.getBroadcastType() == BroadcastDomainType.LinkLocal) { return network.first(); - } + } else if (nic.getBroadcastType() == BroadcastDomainType.Vswitch) { + return setupvSwitchNetwork(conn); + } throw new CloudRuntimeException("Unable to support this type of network broadcast domain: " + nic.getBroadcastUri()); } @@ -3725,6 +3820,78 @@ public abstract class CitrixResourceBase implements ServerResource { return Boolean.valueOf(callHostPlugin(conn, "vmops", "can_bridge_firewall", "host_uuid", _host.uuid)); } + //TODO: it's better to move more stuff at host plugin side + private Answer execute(OvsSetTagAndFlowCommand cmd) { + Connection conn = getConnection(); + try { + Set vms = VM.getByNameLabel(conn, cmd.getVmName()); + VM vm = vms.iterator().next(); + Set vifs = vm.getVIFs(conn); + String domId = vm.getDomid(conn).toString(); + String nwName = Networks.BroadcastScheme.VSwitch.toString(); + Network nw = getNetworkByName(conn, nwName); + assert nw!= null : "Why there is no vswith network ???"; + String bridge = nw.getBridge(conn); + + /*If VM is domainRouter, this will try to set flow and tag on its + * none guest network nic. don't worry, it will fail sciently at host + * plugin side + */ + for (VIF vif : vifs) { + String vifName = "vif" + domId + vif.getDevice(conn); + String result = callHostPlugin(conn, "vmops", "vlanRemapUtils", "op", "createFlow", "bridge", + bridge, "vifName", vifName, "mac", + vif.getMAC(conn), "remap", cmd.getVlans(), + "ip", "placeholder now"); + s_logger.debug("set flow for " + vifName + " on " + cmd.getVmName() + " " + result); + + } + + /* + if (result.equalsIgnoreCase("SUCCESS")) { + return new Answer(cmd, true, "Set flow for " + cmd.getVmName() + + " success, vlans:" + cmd.getVlans()); + } else { + return new Answer(cmd, false, "Set flow for " + cmd.getVmName() + + " failed, vlans:" + cmd.getVlans()); + } + */ + return new Answer(cmd, true, "Set flow for " + cmd.getVmName() + + " success, vlans:" + cmd.getVlans()); + } catch (Exception e) { + e.printStackTrace(); + } + + return new Answer(cmd, false, "Set flow for " + cmd.getVmName() + + " failed, vlans:" + cmd.getVlans()); + } + + private Answer execute(OvsCreateGreTunnelCommand cmd) { + Connection conn = getConnection(); + try { + String nwName = Networks.BroadcastScheme.VSwitch.toString(); + Network nw = getNetworkByName(conn, nwName); + if (nw == null) { + nw = setupvSwitchNetwork(conn); + } + + String result = callHostPlugin(conn, "vmops", "vlanRemapUtils", + "op", "createGRE", "bridge", nw.getBridge(conn), + "remoteIP", cmd.getRemoteIp(), "greKey", cmd.getKey()); + if (result.equalsIgnoreCase("SUCCESS")) { + return new Answer(cmd, true, "create gre tunnel to " + + cmd.getRemoteIp() + " success"); + } else { + return new Answer(cmd, false, "create gre tunnel to " + + cmd.getRemoteIp() + " failed"); + } + } catch (Exception e) { + e.printStackTrace(); + } + + return new Answer(cmd, false, "create gre tunnel to " + cmd.getRemoteIp() + " failed"); + } + private Answer execute(SecurityIngressRulesCmd cmd) { Connection conn = getConnection(); if (s_logger.isTraceEnabled()) { @@ -5501,6 +5668,7 @@ public abstract class CitrixResourceBase implements ServerResource { public String publicNetwork; public String privateNetwork; public String linkLocalNetwork; + public String vswitchNetwork; public String storageNetwork1; public String storageNetwork2; public String guestNetwork; diff --git a/scripts/vm/hypervisor/xenserver/vlanRemapUtils.py b/scripts/vm/hypervisor/xenserver/vlanRemapUtils.py new file mode 100644 index 00000000000..60147055872 --- /dev/null +++ b/scripts/vm/hypervisor/xenserver/vlanRemapUtils.py @@ -0,0 +1,483 @@ +#!/usr/bin/python + +# Create flows on Open vSwitch +# +# @param: bridge name, refer to a network created by xenserver +# @param: our MAC +# @param: vlan ID + +import os +import sys +from os.path import exists as _exists +from time import localtime as _localtime, asctime as _asctime + +vSwitchDBPidFile = "/var/run/openvswitch/ovsdb-server.pid" +vSwitchDBDaemonName = "ovsdb-server" +vSwitchPidFile = "/var/run/openvswitch/ovs-vswitchd.pid" +vsctlPath = "/usr/bin/ovs-vsctl" +vSwitchDaemonName = "ovs-vswitchd" + +logFile = "/var/log/cloud/management/vlanRemapUtils.log" +fLog = None + +global result + +errors = \ + {"NO_DB_PID_FILE" : "NO_DB_PID_FILE", \ + "DB_NOT_RUN" : "DB_NOT_RUN", \ + "NO_SWITCH_PID_FILE" : "NO_SWITCH_PID_FILE", \ + "SWITCH_NOT_RUN" : "SWITCH_NOT_RUN", \ + "NO_VSCTL" : "NO_VSCTL", \ + "COMMAND_FAILED" : "COMMAND_FAILED", \ + + "ERR_ARGS_NUM" : "ERR_ARGS_NUM", \ + "ERROR_OP" : "ERROR_OP", \ + "SUCCESS" : "SUCCESS", \ + } + +def openLog (): + global fLog + + try: + if fLog == None: + fLog = open (logFile, "a") + except IOError, e: + #print e + pass + +def log (str): + global fLog + + if fLog != None: + str = "[%s]:" % _asctime (_localtime()) + str + "\n" + fLog.write (str) + +def closeLog (): + global fLog + + if fLog != None: + fLog.close () + +def isProcessRun (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 isToolExist (name): + if _exists (name): + return 0 + return -1 + + +def checkvSwitch (): + global result + + ret = isProcessRun (vSwitchDBPidFile, vSwitchDBDaemonName); + if ret < 0: + if ret == -1: result = errors["NO_DB_PID_FILE"] + if ret == -2: result = errors["DB_NOT_RUN"] + return -1 + + ret = isProcessRun (vSwitchPidFile, vSwitchDaemonName) + if ret < 0: + if ret == -1: result = errors["NO_SWITCH_PID_FILE"] + if ret == -2: result = errors["SWITCH_NOT_RUN"] + return -1 + + if isToolExist (vsctlPath) < 0: + result = errors["NO_VSCTL"] + return -1 + + return 0 + +def doCmd (cmds, lines=False): + cmd = "" + for i in cmds: + cmd += " " + cmd += i + + log ("do command '%s'" % cmd) + f = os.popen (cmd) + if lines == True: + res = f.readlines () + else: + res = f.readline () + res = res[:-1] + f.close () + + if lines == False: + log ("command output '%s'" % res) + return res + +######################## GRE creation utils ########################## +# UUID's format is 8-4-4-4-12 +def isUUID (uuid): + list = uuid.split ("-") + + if len (list) != 5: + return -1 + + if len (list[0]) != 8 or len (list[1]) != 4 \ + or len (list[2]) != 4 or len (list[3]) != 4 \ + or len (list[4]) != 12: + return -1 + + return 0 + +#FIXME: better check method +def checkGREInterface (bridge, remoteIP, greKey): + listIfaces = [vsctlPath, "list interface"] + res = doCmd (listIfaces, True) + + start = False + num = 0 + keyStr = "key=%s" % greKey + uuid = '' + for i in res: + if "_uuid" in i: + (x, uuid) = i.split(":") + uuid = strip(uuid) + + if "options" in i and remoteIP in i and keyStr in i: + log("WARNING: GRE tunnel for remote_ip=%s key=%s already here" % \ + (remoteIP, greKey)) + return -1 + + return 0 + + +def createGRE (bridge, remoteIP, greKey): + global result + + name = "%sgre" % bridge + if checkGREInterface(bridge, remoteIP, greKey) < 0: + return 0 + + createInterface = [vsctlPath, "create interface", "name=%s" % name, \ + 'type=gre options:"remote_ip=%s key=%s"' % (remoteIP, greKey)] + ifaceUUID = doCmd (createInterface) + if isUUID (ifaceUUID) < 0: + result = errors["COMMAND_FAILED"]; + return -1 + + createPort = [vsctlPath, "create port", "name=%s" % name, \ + "interfaces=[%s]" % ifaceUUID] + portUUID = doCmd (createPort) + if isUUID (portUUID) < 0: + result = errors["COMMAND_FAILED"]; + return -1 + + addBridge = [vsctlPath, "add bridge %s" % bridge, "port %s" % portUUID] + doCmd (addBridge) + return 0 +######################## End GRE creation utils ########################## + +######################## Flow creation utils ########################## +def getPortsOnBridge(bridge): + listBr = [vsctlPath, "list br", bridge] + res = doCmd(listBr, True) + + for i in res: + if "ports" in i: + (x, str) = i.split(":") + str = str.lstrip().rstrip() + str = str[1:] + str = str[:-1] + return str.split(",") + return None + +def getFieldOfPort(nameOruuid, field): + listport = [vsctlPath, "list port", nameOruuid] + res = doCmd(listport, True) + + for i in res: + if field in i: + (x, r) = i.split(":") + return r.lstrip().rstrip() + return None + +def getFieldOfInterface(nameOruuid, field): + listIface = [vsctlPath, "list interface", nameOruuid] + res = doCmd(listIface, True) + + for i in res: + if field in i: + (x, r) = i.split(":") + return r.lstrip().rstrip() + return None + +def strip(str, direction="default"): + str = str.lstrip().rstrip() + if direction == "left": + return str[1:] + if direction == "right": + return str[:-1] + if direction == "both": + str = str[1:] + str = str[:-1] + return str + return str + +def getVifPort(bridge, vifName): + portUuids = getPortsOnBridge(bridge) + if portUuids == None: + log("No ports on bridge %s" % bridge) + return -1 + + for i in portUuids: + name = getFieldOfPort(i, "name") + if name == None: + log("WARNING: no name found for %s" % name) + continue + + name = strip(name, "both") + if name == vifName: + return getFieldOfInterface(vifName, "ofport") + return None + +def getInterfacesOnPort(nameOruuid): + listPort = [vsctlPath, "list port", nameOruuid] + res = doCmd(listPort, True) + + for i in res: + if "interfaces" in i: + (x, str) = i.split(":") + str = strip(str, "both") + return str.split(",") + return None + +def getOfPortsByType(bridge, askGre): + portUuids = getPortsOnBridge(bridge) + if portUuids == None: + log("WARNING:No ports on bridge %s" % bridge) + return -1 + + OfPorts = [] + for i in portUuids: + ifaces = getInterfacesOnPort(i) + if ifaces == None: + log("WARNING:No interfaces on port %s" % i) + continue + + for j in ifaces: + if j == '[]': + log("WARNING:invalid interface [] on port %s" % i) + continue + + type = getFieldOfInterface(j, "type") + type = strip(type) + if askGre == True and type == "gre": + ofport = getFieldOfInterface(j, "ofport") + if ofport != '[]':OfPorts.append(strip(ofport)) + elif askGre == False and type != "gre": + ofport = getFieldOfInterface(j, "ofport") + if ofport != '[]' and ofport != "65534":OfPorts.append(strip(ofport)) + return OfPorts + +def getNoneGreOfPort(bridge): + return getOfPortsByType(bridge, False) + +def getGreOfPorts(bridge): + return getOfPortsByType(bridge, True) + +def findInPort(): + listIface = [vsctlPath, "list interface"] + res = doCmd (listIface, True) + + inport = [] + port = "" + for i in res: + if "ofport" in i: + (x, port) = i.split(":") + port = port.lstrip().rstrip() + + if "type" in i: + (x, type) = i.split(":") + type = type.lstrip().rstrip() + if type == "gre": + inport.append (port) + return inport + + +def formatFlow(inPort, vlan, mac, outPut): + flow = "in_port=%s dl_vlan=%s dl_dst=%s idle_timeout=0 hard_timeout=0 \ + actions=strip_vlan,output:%s" % (inPort, vlan, mac, outPut) + return flow + +def delFlow(mac): + param = "dl_dst=%s" % mac + delFlow = ["ovs-ofctl del-flows %s" % bridge, '"%s"' % param] + doCmd(delFlow) + +def delARPFlow(vlan): + param = "dl_type=0x0806 dl_vlan=%s" % vlan + delFlow = ["ovs-ofctl del-flows %s" % bridge, '"%s"' % param] + doCmd(delFlow) + +def formatARPFlow(bridge, inPort, vlan, ports): + outputs = '' + for i in ports: + str = "output:%s," % i + outputs += str + + outputs = outputs[:-1] + flow = "in_port=%s dl_vlan=%s dl_type=0x0806 idle_timeout=0 hard_timeout=0 \ + actions=strip_vlan,%s" % (inPort, vlan, outputs) + return flow + +def createFlow (bridge, vifName, mac, ip, vlan, remap): + inport = getGreOfPorts(bridge) + if len(inport) == 0: + log("WARNING: no inport found") + return -1 + + output = getVifPort(bridge, vifName) + if output == None: + log("WARNING: cannot find ofport for %s" % vifName) + return -1 + if output == '[]': + log("WARNING: ofport is [] for %s" % vifName) + return -1 + + #del old flow here, if any, but in normal there should be no old flow + #maybe we need add check here + delFlow(mac) + + #set remap here, remap has format e.g. [1,22,200,13,16] + remap = strip(remap, "both") + log("") + log("Create flow for remap") + noneGreOfPorts = getNoneGreOfPort(bridge) + isARP = True + if len(noneGreOfPorts) == 0: + log("WARNING: no none GRE ofports found, no ARP flow will be created") + isARP = False + + for j in remap.split(","): + delARPFlow(j) + for i in inport: + flow = formatFlow(i, j, mac, output) + param = bridge + ' "%s"' % flow + addflow = ["ovs-ofctl add-flow", param] + doCmd (addflow) + + if isARP == True: + flow = formatARPFlow(bridge, i, j, noneGreOfPorts) + param = bridge + ' "%s"' % flow + addflow = ["ovs-ofctl add-flow", param] + doCmd (addflow) + + return 0 +######################## End Flow creation utils ########################## + +def setTag(vifName, vlan): + log("") + log("Set tag") + setTagCmd = [vsctlPath, "set port", vifName, "tag=%s"%vlan] + doCmd (setTagCmd) + return 0 + +def doCreateGRE(bridge, remoteIP, key): + if createGRE(bridge, remoteIP, key) < 0: + log("WARNING: create GRE tunnel on %s for %s failed" % (bridge, \ + remoteIP)) + else: + log("WARNING: create GRE tunnel on %s for %s success" % (bridge, \ + remoteIP)) + +def doCreateFlow (bridge, vifName, mac, ip, vlan, remap): + setTag (vifName, vlan) + if createFlow(bridge, vifName, mac, ip, vlan, remap) < 0: + log ("Create flow failed(bridge=%s, vifName=%s, mac=%s, ip=%s, vlan=%s,\ +remap=%s" % (bridge, vifName, mac, ip, vlan, remap)) + else: + log ("Create flow success(bridge=%s, vifName=%s, mac=%s, ip=%s, vlan=%s,\ +remap=%s" % (bridge, vifName, mac, ip, vlan, remap)) + +def doDeleteFlow(bridge, vifName, mac, remap): + delFlow(mac) + log("Delete flows for %s" % mac) + + remap = strip(remap, "both") + + # remove our port from arp flow + inport = getGreOfPorts(bridge) + if len(inport) == 0: + return + + mine = getVifPort(bridge, vifName) + noneGreOfPorts = getNoneGreOfPort(bridge) + noneGreOfPorts.remove(mine) + log("Delete ARP flows for(vifname=%s, ofport=%s)" % (vifName, mine)) + + for j in remap.split(","): + delARPFlow(j) + for i in inport: + flow = formatARPFlow(bridge, i, j, noneGreOfPorts) + param = bridge + ' "%s"' % flow + addflow = ["ovs-ofctl add-flow", param] + doCmd (addflow) + +def checkArgNum(num): + if len (sys.argv) < num: + result = errors["ERR_ARGS_NUM"] + print result + log ("Error number of arguments") + sys.exit (-1) + +if __name__ == "__main__": + global result + + openLog () + + + if checkvSwitch () < 0: + print result + log ("Check switch failed, reason = '%s'" % result) + sys.exit (-1) + + op = sys.argv[1] + if op == "createGRE": + checkArgNum(5) + bridge = sys.argv[2] + remoteIP = sys.argv[3] + key = sys.argv[4] + doCreateGRE(bridge, remoteIP, key) + elif op == "createFlow": + checkArgNum(8) + bridge = sys.argv[2] + vifName = sys.argv[3] + mac = sys.argv[4] + vlan = sys.argv[5] + remap = sys.argv[6] + ip = sys.argv[7] + doCreateFlow(bridge, vifName, mac, ip, vlan, remap) + elif op == "deleteFlow": + checkArgNum(6) + bridge = sys.argv[2] + vifName = sys.argv[3] + mac = sys.argv[4] + remap = sys.argv[5] + doDeleteFlow(bridge, vifName, mac, remap) + else: + log("WARNING: get an unkown op %s" % op) + result=errors["ERROR_OP"] + print result + sys.exit(-1) + + result = errors["SUCCESS"] + closeLog () + print result diff --git a/scripts/vm/hypervisor/xenserver/vmops b/scripts/vm/hypervisor/xenserver/vmops index 380bfbc1d2d..a49fb2ef2c6 100755 --- a/scripts/vm/hypervisor/xenserver/vmops +++ b/scripts/vm/hypervisor/xenserver/vmops @@ -128,6 +128,39 @@ def ipassoc(session, args): return txt +@echo +def vlanRemapUtils(session, args): + cmd = [] + cmd.insert(0, "python") + cmd.insert(1, "/opt/xensource/bin/vlanRemapUtils.py") + + op = args.pop("op") + cmd.insert(2, op) + if op == "createGRE": + cmd.insert(3, args.pop("bridge")) + cmd.insert(4, args.pop("remoteIP")) + cmd.insert(5, args.pop("greKey")) + elif op == "createFlow": + cmd.insert(3, args.pop("bridge")) + cmd.insert(4, args.pop("vifName")) + cmd.insert(5, args.pop("mac")) + cmd.insert(6, args.pop("vlan")) + cmd.insert(7, args.pop("remap")) + cmd.insert(8, args.pop("ip")) + elif op == "deleteFlow": + cmd.insert(3, args.pop("bridge")) + cmd.insert(4, args.pop("vifName")) + cmd.insert(5, args.pop("mac")) + cmd.insert(6, args.pop("remap")) + + try: + txt = util.pread2(cmd) + except: + util.SMlog("vlanRemapUtils failed") + txt = 'failed' + + return txt + @echo def vm_data(session, args): router_ip = args.pop('routerIP') @@ -519,7 +552,6 @@ def default_ebtables_rules(vm_name, vif, vm_ip, vm_mac): util.SMlog("Failed to program default ebtables OUT rules") return 'false' - @echo def default_network_rules_systemvm(session, args): vm_name = args.pop('vmName') @@ -1032,5 +1064,5 @@ def network_rules(session, args): if __name__ == "__main__": - XenAPIPlugin.dispatch({"pingtest": pingtest, "setup_iscsi":setup_iscsi, "gethostvmstats": gethostvmstats, "getvncport": getvncport, "getgateway": getgateway, "preparemigration": preparemigration, "setIptables": setIptables, "pingdomr": pingdomr, "pingxenserver": pingxenserver, "ipassoc": ipassoc, "vm_data": vm_data, "savePassword": savePassword, "saveDhcpEntry": saveDhcpEntry, "setFirewallRule": setFirewallRule, "setLoadBalancerRule": setLoadBalancerRule, "createFile": createFile, "deleteFile": deleteFile, "networkUsage": networkUsage, "network_rules":network_rules, "can_bridge_firewall":can_bridge_firewall, "default_network_rules":default_network_rules, "destroy_network_rules_for_vm":destroy_network_rules_for_vm, "default_network_rules_systemvm":default_network_rules_systemvm, "get_rule_logs_for_vms":get_rule_logs_for_vms, "setLinkLocalIP":setLinkLocalIP, "lt2p_vpn":lt2p_vpn, "cleanup_rules":cleanup_rules}) + XenAPIPlugin.dispatch({"pingtest": pingtest, "setup_iscsi":setup_iscsi, "gethostvmstats": gethostvmstats, "getvncport": getvncport, "getgateway": getgateway, "preparemigration": preparemigration, "setIptables": setIptables, "pingdomr": pingdomr, "pingxenserver": pingxenserver, "ipassoc": ipassoc, "vm_data": vm_data, "savePassword": savePassword, "saveDhcpEntry": saveDhcpEntry, "setFirewallRule": setFirewallRule, "setLoadBalancerRule": setLoadBalancerRule, "createFile": createFile, "deleteFile": deleteFile, "networkUsage": networkUsage, "network_rules":network_rules, "can_bridge_firewall":can_bridge_firewall, "default_network_rules":default_network_rules, "destroy_network_rules_for_vm":destroy_network_rules_for_vm, "default_network_rules_systemvm":default_network_rules_systemvm, "get_rule_logs_for_vms":get_rule_logs_for_vms, "setLinkLocalIP":setLinkLocalIP, "lt2p_vpn":lt2p_vpn, "vlanRemapUtils":vlanRemapUtils,"cleanup_rules":cleanup_rules}) diff --git a/server/src/com/cloud/agent/manager/Commands.java b/server/src/com/cloud/agent/manager/Commands.java index 50a9aa1a997..100ea6d0268 100644 --- a/server/src/com/cloud/agent/manager/Commands.java +++ b/server/src/com/cloud/agent/manager/Commands.java @@ -60,6 +60,10 @@ public class Commands { addCommand(null, cmd); } + public void addCommand(int index, Command cmd) { + _cmds.add(index, cmd); + } + public Answer getAnswer(String id) { int i = _ids.indexOf(id); return i == -1 ? null : _answers[i]; diff --git a/server/src/com/cloud/configuration/Config.java b/server/src/com/cloud/configuration/Config.java index e7f2becfbc7..603b528dbf5 100644 --- a/server/src/com/cloud/configuration/Config.java +++ b/server/src/com/cloud/configuration/Config.java @@ -60,6 +60,7 @@ public enum Config { NetworkThrottlingRate("Network", ManagementServer.class, Integer.class, "network.throttling.rate", "200", "Default data transfer rate in megabits per second allowed.", null), GuestDomainSuffix("Network", AgentManager.class, String.class, "guest.domain.suffix", "cloud-test.cloud.internal", "Default domain name for vms inside virtualized networks fronted by router", null), DirectNetworkNoDefaultRoute("Network", ManagementServer.class, Boolean.class, "direct.network.no.default.route", "false", "Direct Network Dhcp Server should not send a default route", "true/false"), + OvsNetwork("Network", ManagementServer.class, Boolean.class, "open.vswitch.network", "true", "enable/disable open vswitch network", null), //VPN RemoteAccessVpnPskLength("Network", AgentManager.class, Integer.class, "remote.access.vpn.psk.length", "24", "The length of the ipsec preshared key (minimum 8, maximum 256)", null), diff --git a/server/src/com/cloud/configuration/DefaultComponentLibrary.java b/server/src/com/cloud/configuration/DefaultComponentLibrary.java index d0c1b05bdd3..717b5d17936 100644 --- a/server/src/com/cloud/configuration/DefaultComponentLibrary.java +++ b/server/src/com/cloud/configuration/DefaultComponentLibrary.java @@ -72,6 +72,9 @@ import com.cloud.network.dao.NetworkRuleConfigDaoImpl; import com.cloud.network.dao.RemoteAccessVpnDaoImpl; import com.cloud.network.dao.VpnUserDaoImpl; import com.cloud.network.lb.LoadBalancingRulesManagerImpl; +import com.cloud.network.ovs.OvsNetworkManagerImpl; +import com.cloud.network.ovs.dao.VlanMappingDaoImpl; +import com.cloud.network.ovs.dao.VlanMappingDirtyDaoImpl; import com.cloud.network.router.VirtualNetworkApplianceManagerImpl; import com.cloud.network.rules.RulesManagerImpl; import com.cloud.network.rules.dao.PortForwardingRulesDaoImpl; @@ -236,6 +239,8 @@ public class DefaultComponentLibrary implements ComponentLibrary { addDao("PortForwardingRulesDao", PortForwardingRulesDaoImpl.class); addDao("UsageEventDao", UsageEventDaoImpl.class); addDao("ClusterDetailsDao", ClusterDetailsDaoImpl.class); + addDao("VlanMappingDao", VlanMappingDaoImpl.class); + addDao("VlanMappingDirtyDao", VlanMappingDirtyDaoImpl.class); } Map> _managers = new HashMap>(); @@ -290,6 +295,7 @@ public class DefaultComponentLibrary implements ComponentLibrary { addManager("LoadBalancingRulesManager", LoadBalancingRulesManagerImpl.class); addManager("RulesManager", RulesManagerImpl.class); addManager("RemoteAccessVpnManager", RemoteAccessVpnManagerImpl.class); + addManager("OvsNetworkManager", OvsNetworkManagerImpl.class); } protected List> addAdapterChain(Class interphace, List>> adapters) { diff --git a/server/src/com/cloud/network/element/OvsElement.java b/server/src/com/cloud/network/element/OvsElement.java new file mode 100644 index 00000000000..2f036e53c0b --- /dev/null +++ b/server/src/com/cloud/network/element/OvsElement.java @@ -0,0 +1,94 @@ +package com.cloud.network.element; + +import java.util.List; +import java.util.Map; + +import javax.ejb.Local; + +import com.cloud.deploy.DeployDestination; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.Network; +import com.cloud.network.PublicIpAddress; +import com.cloud.network.Network.Capability; +import com.cloud.network.Network.Provider; +import com.cloud.network.Network.Service; +import com.cloud.network.ovs.OvsNetworkManager; +import com.cloud.network.rules.FirewallRule; +import com.cloud.offering.NetworkOffering; +import com.cloud.utils.component.AdapterBase; +import com.cloud.utils.component.Inject; +import com.cloud.vm.NicProfile; +import com.cloud.vm.ReservationContext; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachineProfile; + +@Local(value=NetworkElement.class) +public class OvsElement extends AdapterBase implements NetworkElement { + @Inject OvsNetworkManager _ovsNetworkMgr; + + @Override + public Map> getCapabilities() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Provider getProvider() { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean implement(Network network, NetworkOffering offering, + DeployDestination dest, ReservationContext context) + throws ConcurrentOperationException, ResourceUnavailableException, + InsufficientCapacityException { + // TODO Auto-generated method stub + return true; + } + + @Override + public boolean prepare(Network network, NicProfile nic, + VirtualMachineProfile vm, + DeployDestination dest, ReservationContext context) + throws ConcurrentOperationException, ResourceUnavailableException, + InsufficientCapacityException { + _ovsNetworkMgr.CheckAndUpdateDhcpFlow(network); + return true; + } + + @Override + public boolean release(Network network, NicProfile nic, + VirtualMachineProfile vm, + ReservationContext context) throws ConcurrentOperationException, + ResourceUnavailableException { + // TODO Auto-generated method stub + return true; + } + + @Override + public boolean shutdown(Network network, ReservationContext context) + throws ConcurrentOperationException, ResourceUnavailableException { + // TODO Auto-generated method stub + return true; + } + + @Override + public boolean applyIps(Network network, + List ipAddress) + throws ResourceUnavailableException { + // TODO Auto-generated method stub + return true; + } + + @Override + public boolean applyRules(Network network, + List rules) + throws ResourceUnavailableException { + // TODO Auto-generated method stub + return true; + } + +} diff --git a/server/src/com/cloud/network/ovs/OvsNetworkManager.java b/server/src/com/cloud/network/ovs/OvsNetworkManager.java new file mode 100644 index 00000000000..8ea881cdeef --- /dev/null +++ b/server/src/com/cloud/network/ovs/OvsNetworkManager.java @@ -0,0 +1,27 @@ +package com.cloud.network.ovs; + +import com.cloud.agent.manager.Commands; +import com.cloud.deploy.DeployDestination; +import com.cloud.network.Network; +import com.cloud.uservm.UserVm; +import com.cloud.utils.component.Manager; +import com.cloud.vm.State; +import com.cloud.vm.UserVmVO; +import com.cloud.vm.VirtualMachineProfile; + +public interface OvsNetworkManager extends Manager { + public boolean isOvsNetworkEnabled(); + + public long askVlanId(long accountId, long hostId); + + public String getVlanMapping(long accountId); + + public void CheckAndCreateTunnel(Commands cmds, + VirtualMachineProfile profile, DeployDestination dest); + + public void applyDefaultFlow(Commands cmds, + VirtualMachineProfile profile, DeployDestination dest); + + public void CheckAndUpdateDhcpFlow(Network nw); + public void handleVmStateTransition(UserVm userVm, State vmState); +} diff --git a/server/src/com/cloud/network/ovs/OvsNetworkManagerImpl.java b/server/src/com/cloud/network/ovs/OvsNetworkManagerImpl.java new file mode 100644 index 00000000000..28e8741b584 --- /dev/null +++ b/server/src/com/cloud/network/ovs/OvsNetworkManagerImpl.java @@ -0,0 +1,304 @@ +package com.cloud.network.ovs; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import javax.ejb.Local; +import javax.naming.ConfigurationException; + +import org.apache.log4j.Logger; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.manager.Commands; +import com.cloud.configuration.Config; +import com.cloud.configuration.dao.ConfigurationDao; +import com.cloud.deploy.DeployDestination; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.network.Network; +import com.cloud.network.NetworkVO; +import com.cloud.network.Networks.BroadcastDomainType; +import com.cloud.network.Networks.TrafficType; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.ovs.dao.VlanMappingDao; +import com.cloud.network.ovs.dao.VlanMappingDirtyDao; +import com.cloud.network.ovs.dao.VlanMappingVO; +import com.cloud.uservm.UserVm; +import com.cloud.utils.Pair; +import com.cloud.utils.component.Inject; +import com.cloud.utils.db.Transaction; +import com.cloud.vm.DomainRouterVO; +import com.cloud.vm.NicVO; +import com.cloud.vm.State; +import com.cloud.vm.UserVmVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.dao.DomainRouterDao; +import com.cloud.vm.dao.NicDao; +import com.cloud.vm.dao.UserVmDao; + +@Local(value={OvsNetworkManager.class}) +public class OvsNetworkManagerImpl implements OvsNetworkManager { + private static final Logger s_logger = Logger.getLogger(OvsNetworkManagerImpl.class); + @Inject ConfigurationDao _configDao; + @Inject VlanMappingDao _vlanMappingDao; + @Inject UserVmDao _userVmDao; + @Inject HostDao _hostDao; + @Inject AgentManager _agentMgr; + @Inject NicDao _nicDao; + @Inject NetworkDao _networkDao; + @Inject VlanMappingDirtyDao _vlanMappingDirtyDao; + @Inject DomainRouterDao _routerDao; + String _name; + boolean _isEnabled; + + @Override + public boolean configure(String name, Map params) + throws ConfigurationException { + _name = name; + _isEnabled = _configDao.getValue(Config.OvsNetwork.key()).equalsIgnoreCase("true") ? true : false; + + return true; + } + + @Override + public boolean start() { + // TODO Auto-generated method stub + return true; + } + + @Override + public boolean stop() { + // TODO Auto-generated method stub + return true; + } + + @Override + public String getName() { + // TODO Auto-generated method stub + return _name; + } + + @Override + public boolean isOvsNetworkEnabled() { + // TODO Auto-generated method stub + return _isEnabled; + } + + @Override + public long askVlanId(long accountId, long hostId) { + assert _isEnabled : "Who call me ??? while OvsNetwokr is not enabled!!!"; + final Transaction txn = Transaction.currentTxn(); + txn.start(); + + List mappings = _vlanMappingDao.listByAccountIdAndHostId(accountId, hostId); + long vlan = 0; + + if (mappings.size() !=0) { + assert mappings.size() == 1 : "We should only have one vlan for an account on a host"; + txn.commit(); + vlan = mappings.get(0).getVlan(); + s_logger.debug("Already has an Vlan " + vlan + " on host " + hostId + + " for account " + accountId + ", use it!"); + return vlan; + } + + mappings = _vlanMappingDao.listByHostId(hostId); + if (mappings.size() > 0) { + ArrayList vlans = new ArrayList(); + for (VlanMappingVO vo : mappings) { + vlans.add(new Long(vo.getVlan())); + } + + // Find first available vlan + int i; + for (i=0; i<4096; i++) { + if (!vlans.contains(new Long(i))) { + vlan = i; + break; + } + } + assert i!=4096 : "Terrible, vlan exhausted on this server!!!"; + } + + VlanMappingVO newVlan = new VlanMappingVO(accountId, hostId, vlan); + _vlanMappingDao.persist(newVlan); + _vlanMappingDirtyDao.markDirty(accountId); + txn.commit(); + return 0; + } + + @Override + public String getVlanMapping(long accountId) { + assert _isEnabled : "Who call me ??? while OvsNetwokr is not enabled!!!"; + final Transaction txn = Transaction.currentTxn(); + txn.start(); + + List ours = _vlanMappingDao.listByAccountId(accountId); + txn.commit(); + + ArrayListvlans = new ArrayList(); + for (VlanMappingVO vo : ours) { + vlans.add(new Long(vo.getVlan())); + } + + StringBuffer buf = new StringBuffer(); + for (Long i : vlans) { + buf.append("/"); + buf.append(i.toString()); + buf.append("/"); + } + return buf.toString(); + } + + @Override + public void CheckAndCreateTunnel(Commands cmds, VirtualMachineProfile profile, + DeployDestination dest) { + if (!_isEnabled) { + return; + } + + UserVmVO userVm = profile.getVirtualMachine(); + if (userVm.getType() != VirtualMachine.Type.User) { + return; + } + + long hostId = dest.getHost().getId(); + long accountId = userVm.getAccountId(); + List vms = _userVmDao.listByAccountIdAndHostId(accountId, hostId); + if (vms.size() != 0) { + s_logger.debug("Already has GRE tunnel for account " + accountId + + " for host " + hostId); + return; + } + + vms = _userVmDao.listByAccountId(accountId); + ListremoteHostIds = new ArrayList(); + for (UserVmVO v : vms) { + Long rh = v.getHostId(); + if (rh == null || rh.longValue() == hostId) { + continue; + } + + if (!remoteHostIds.contains(rh)) { + remoteHostIds.add(rh); + } + } + + try { + String myIp = dest.getHost().getPrivateIpAddress(); + for (Long i : remoteHostIds) { + HostVO rHost = _hostDao.findById(i.longValue()); + cmds.addCommand( + 0, new OvsCreateGreTunnelCommand(rHost.getPrivateIpAddress(), "1")); + _agentMgr.send(i.longValue(), new OvsCreateGreTunnelCommand(myIp, "1")); + s_logger.debug("Ask host " + i.longValue() + " to create gre tunnel to " + hostId); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + private String parseVlanAndMapping(String uri) { + String sub = uri.substring(BroadcastDomainType.Vswitch.scheme().length() + "://".length() - 1); + return sub; + } + + @Override + public void applyDefaultFlow(Commands cmds, + VirtualMachineProfile profile, DeployDestination dest) { + if (!_isEnabled) { + return; + } + + UserVmVO userVm = profile.getVirtualMachine(); + VirtualMachine.Type vmType = userVm.getType(); + if (vmType != VirtualMachine.Type.User + && vmType != VirtualMachine.Type.DomainRouter) { + return; + } + + List nics = _nicDao.listBy(userVm.getId()); + if (nics.size() == 0) + return; + + NicVO nic = null; + if (vmType == VirtualMachine.Type.DomainRouter) { + for (NicVO n : nics) { + NetworkVO network = _networkDao.findById(n.getNetworkId()); + if (network.getTrafficType() == TrafficType.Guest) { + nic = n; + break; + } + } + } else { + nic = nics.get(0); + } + + assert nic!=null : "Why there is no guest network nic???"; + String vlans = parseVlanAndMapping(nic.getBroadcastUri().toASCIIString()); + cmds.addCommand(new OvsSetTagAndFlowCommand(userVm.getName(), vlans)); + } + + @Override + public void CheckAndUpdateDhcpFlow(Network nw) { + if (!_isEnabled) { + return; + } + + DomainRouterVO router = _routerDao.findByNetworkConfiguration(nw.getId()); + if (router == null) { + return; + } + + long accountId = nw.getAccountId(); + if (!_vlanMappingDirtyDao.isDirty(accountId)) { + return; + } + + try { + String vlans = getVlanMapping(accountId); + _agentMgr.send(router.getHostId(), new OvsSetTagAndFlowCommand( + router.getName(), vlans)); + s_logger.debug("ask router " + router.getName() + " on host " + + router.getHostId() + " update vlan map to " + vlans); + } catch (Exception e) { + e.printStackTrace(); + } + } + + protected void handleVmStarted(UserVm userVm) { + scheduleFlowUpdateToHosts(affectedVms, true, null); + } + + protected void handleVmStopped(UserVm userVm) { + scheduleFlowUpdateToHosts(affectedVms, true, null); + } + + @Override + public void handleVmStateTransition(UserVm userVm, State vmState) { + if (!_isEnabled) { + return; + } + + switch (vmState) { + case Creating: + case Destroyed: + case Error: + case Migrating: + case Expunging: + case Starting: + case Unknown: + return; + case Running: + handleVmStarted(userVm); + break; + case Stopping: + case Stopped: + handleVmStopped(userVm); + break; + } + + } + +} diff --git a/server/src/com/cloud/network/ovs/dao/VlanMappingDao.java b/server/src/com/cloud/network/ovs/dao/VlanMappingDao.java new file mode 100644 index 00000000000..713a9ca13c2 --- /dev/null +++ b/server/src/com/cloud/network/ovs/dao/VlanMappingDao.java @@ -0,0 +1,11 @@ +package com.cloud.network.ovs.dao; + +import java.util.List; + +import com.cloud.utils.db.GenericDao; + +public interface VlanMappingDao extends GenericDao { + List listByAccountIdAndHostId(long accountId, long hostId); + List listByHostId(long hostId); + List listByAccountId(long accountId); +} diff --git a/server/src/com/cloud/network/ovs/dao/VlanMappingDaoImpl.java b/server/src/com/cloud/network/ovs/dao/VlanMappingDaoImpl.java new file mode 100644 index 00000000000..be1c7bda360 --- /dev/null +++ b/server/src/com/cloud/network/ovs/dao/VlanMappingDaoImpl.java @@ -0,0 +1,50 @@ +package com.cloud.network.ovs.dao; + +import java.util.List; + +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.SearchCriteria.Op; + +import javax.ejb.Local; + +@Local(value = { VlanMappingDao.class }) +public class VlanMappingDaoImpl extends GenericDaoBase + implements VlanMappingDao { + protected final SearchBuilder AllFieldsSearch; + + public VlanMappingDaoImpl() { + super(); + AllFieldsSearch = createSearchBuilder(); + AllFieldsSearch.and("host_id", AllFieldsSearch.entity().getHostId(), Op.EQ); + AllFieldsSearch.and("account_id", AllFieldsSearch.entity().getAccountId(), Op.EQ); + AllFieldsSearch.and("vlan", AllFieldsSearch.entity().getAccountId(), Op.EQ); + AllFieldsSearch.done(); + } + + @Override + public List listByAccountIdAndHostId(long accountId, + long hostId) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("account_id", accountId); + sc.setParameters("host_id", hostId); + return listBy(sc, null); + } + + @Override + public List listByHostId(long hostId) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("host_id", hostId); + + return listBy(sc, null); + } + + @Override + public List listByAccountId(long accountId) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("account_id", accountId); + + return listBy(sc, null); + } +} diff --git a/server/src/com/cloud/network/ovs/dao/VlanMappingDirtyDao.java b/server/src/com/cloud/network/ovs/dao/VlanMappingDirtyDao.java new file mode 100644 index 00000000000..1719ab26bf0 --- /dev/null +++ b/server/src/com/cloud/network/ovs/dao/VlanMappingDirtyDao.java @@ -0,0 +1,9 @@ +package com.cloud.network.ovs.dao; + +import com.cloud.utils.db.GenericDao; + +public interface VlanMappingDirtyDao extends GenericDao { + public boolean isDirty(long accountId); + public void markDirty(long accountId); + public void clean(long accountId); +} diff --git a/server/src/com/cloud/network/ovs/dao/VlanMappingDirtyDaoImpl.java b/server/src/com/cloud/network/ovs/dao/VlanMappingDirtyDaoImpl.java new file mode 100644 index 00000000000..f2f54cb80e5 --- /dev/null +++ b/server/src/com/cloud/network/ovs/dao/VlanMappingDirtyDaoImpl.java @@ -0,0 +1,62 @@ +package com.cloud.network.ovs.dao; + +import javax.ejb.Local; + +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.SearchCriteria.Op; + +@Local(value = { VlanMappingDirtyDao.class }) +public class VlanMappingDirtyDaoImpl extends + GenericDaoBase implements VlanMappingDirtyDao { + protected final SearchBuilder AllFieldsSearch; + + public VlanMappingDirtyDaoImpl() { + super(); + AllFieldsSearch = createSearchBuilder(); + AllFieldsSearch.and("account_id", AllFieldsSearch.entity().getAccountId(), Op.EQ); + AllFieldsSearch.and("dirty", AllFieldsSearch.entity().isDirty(), Op.EQ); + AllFieldsSearch.done(); + } + + @Override + public boolean isDirty(long accountId) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("account_id", accountId); + VlanMappingDirtyVO vo = findOneBy(sc); + if (vo == null) { + return false; + } + return vo.isDirty(); + } + + @Override + public void markDirty(long accountId) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("account_id", accountId); + VlanMappingDirtyVO vo = findOneBy(sc); + if (vo == null) { + vo = new VlanMappingDirtyVO(accountId, true); + persist(vo); + } else { + vo.markDirty(); + update(vo, sc); + } + } + + @Override + public void clean(long accountId) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("account_id", accountId); + VlanMappingDirtyVO vo = findOneBy(sc); + if (vo == null) { + vo = new VlanMappingDirtyVO(accountId, false); + persist(vo); + } else { + vo.clean(); + update(vo, sc); + } + } + +} diff --git a/server/src/com/cloud/network/ovs/dao/VlanMappingDirtyVO.java b/server/src/com/cloud/network/ovs/dao/VlanMappingDirtyVO.java new file mode 100644 index 00000000000..451fadd5364 --- /dev/null +++ b/server/src/com/cloud/network/ovs/dao/VlanMappingDirtyVO.java @@ -0,0 +1,52 @@ +package com.cloud.network.ovs.dao; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name=("ovs_vlan_mapping_dirty")) +public class VlanMappingDirtyVO { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "dirty") + private boolean dirty; + + @Column(name = "account_id") + private long accountId; + + public VlanMappingDirtyVO() { + + } + + public VlanMappingDirtyVO(long accountId, boolean dirty) { + this.accountId = accountId; + this.dirty = dirty; + } + + public long getId() { + return id; + } + + public long getAccountId() { + return accountId; + } + + public boolean isDirty() { + return dirty; + } + + public void markDirty() { + dirty = true; + } + + public void clean() { + dirty = false; + } +} diff --git a/server/src/com/cloud/network/ovs/dao/VlanMappingVO.java b/server/src/com/cloud/network/ovs/dao/VlanMappingVO.java new file mode 100644 index 00000000000..37fafa55fda --- /dev/null +++ b/server/src/com/cloud/network/ovs/dao/VlanMappingVO.java @@ -0,0 +1,52 @@ +package com.cloud.network.ovs.dao; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Column; + +@Entity +@Table(name=("ovs_host_vlan_alloc")) +public class VlanMappingVO { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "host_id") + private long hostId; + + @Column(name = "account_id") + private long accountId; + + @Column(name = "vlan") + private long vlan; + + public VlanMappingVO(long accountId, long hostId, long vlan) { + this.hostId = hostId; + this.accountId = accountId; + this.vlan = vlan; + } + + public VlanMappingVO() { + + } + + public long getHostId() { + return hostId; + } + + public long getAccountId() { + return accountId; + } + + public long getVlan() { + return vlan; + } + + public long getId() { + return id; + } +} diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java index bb8b5b115bc..4974ecc933f 100755 --- a/server/src/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/com/cloud/vm/UserVmManagerImpl.java @@ -129,6 +129,7 @@ import com.cloud.network.dao.LoadBalancerDao; import com.cloud.network.dao.LoadBalancerVMMapDao; import com.cloud.network.dao.NetworkDao; import com.cloud.network.lb.LoadBalancingRulesManager; +import com.cloud.network.ovs.OvsNetworkManager; import com.cloud.network.router.VirtualNetworkApplianceManager; import com.cloud.network.rules.RulesManager; import com.cloud.network.security.SecurityGroupManager; @@ -258,6 +259,7 @@ public class UserVmManagerImpl implements UserVmManager, UserVmService, Manager @Inject RulesManager _rulesMgr; @Inject LoadBalancingRulesManager _lbMgr; @Inject UsageEventDao _usageEventDao; + @Inject OvsNetworkManager _ovsNetworkMgr; private IpAddrAllocator _IpAllocator; ScheduledExecutorService _executor = null; @@ -2395,6 +2397,7 @@ public class UserVmManagerImpl implements UserVmManager, UserVmService, Manager } _vmDao.update(userVm.getId(), userVm); + _ovsNetworkMgr.CheckAndCreateTunnel(cmds, profile, dest); return true; } diff --git a/server/src/com/cloud/vm/dao/UserVmDao.java b/server/src/com/cloud/vm/dao/UserVmDao.java index bc7dcf758cf..d372d9bd811 100755 --- a/server/src/com/cloud/vm/dao/UserVmDao.java +++ b/server/src/com/cloud/vm/dao/UserVmDao.java @@ -100,4 +100,5 @@ public interface UserVmDao extends GenericDao, StateDao listByAccountIdAndHostId(long accountId, long hostId); } diff --git a/server/src/com/cloud/vm/dao/UserVmDaoImpl.java b/server/src/com/cloud/vm/dao/UserVmDaoImpl.java index f61904d461d..73e439ded71 100755 --- a/server/src/com/cloud/vm/dao/UserVmDaoImpl.java +++ b/server/src/com/cloud/vm/dao/UserVmDaoImpl.java @@ -57,6 +57,7 @@ public class UserVmDaoImpl extends GenericDaoBase implements Use protected final SearchBuilder GuestIpSearch; protected final SearchBuilder ZoneAccountGuestIpSearch; protected final SearchBuilder ZoneNameSearch; + protected final SearchBuilder AccountHostSearch; protected final SearchBuilder DestroySearch; protected SearchBuilder AccountDataCenterVirtualSearch; @@ -136,6 +137,11 @@ public class UserVmDaoImpl extends GenericDaoBase implements Use ZoneNameSearch.and("dataCenterId", ZoneNameSearch.entity().getDataCenterId(), SearchCriteria.Op.EQ); ZoneNameSearch.and("name", ZoneNameSearch.entity().getName(), SearchCriteria.Op.EQ); ZoneNameSearch.done(); + + AccountHostSearch = createSearchBuilder(); + AccountHostSearch.and("accountId", AccountHostSearch.entity().getAccountId(), SearchCriteria.Op.EQ); + AccountHostSearch.and("hostId", AccountHostSearch.entity().getHostId(), SearchCriteria.Op.EQ); + AccountHostSearch.done(); _updateTimeAttr = _allAttributes.get("updateTime"); assert _updateTimeAttr != null : "Couldn't get this updateTime attribute"; @@ -401,4 +407,12 @@ public class UserVmDaoImpl extends GenericDaoBase implements Use sc.setParameters("name", name); return findOneBy(sc); } + + @Override + public List listByAccountIdAndHostId(long accountId, long hostId) { + SearchCriteria sc = AccountHostSearch.create(); + sc.setParameters("hostId", hostId); + sc.setParameters("accountId", accountId); + return listBy(sc); + } } diff --git a/setup/db/create-schema.sql b/setup/db/create-schema.sql index eb22d1fd532..d2d1c68b56a 100755 --- a/setup/db/create-schema.sql +++ b/setup/db/create-schema.sql @@ -1323,4 +1323,19 @@ CREATE TABLE `cloud`.`usage_event` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +CREATE TABLE `cloud`.`ovs_host_vlan_alloc`( + `id` bigint unsigned NOT NULL UNIQUE AUTO_INCREMENT, + `host_id` bigint unsigned COMMENT 'host id', + `account_id` bigint unsigned COMMENT 'account id', + `vlan` bigint unsigned COMMENT 'vlan id under account #account_id on host #host_id', + PRIMARY KEY(`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE `cloud`.`ovs_vlan_mapping_dirty`( + `id` bigint unsigned NOT NULL UNIQUE AUTO_INCREMENT, + `account_id` bigint unsigned COMMENT 'account id', + `dirty` int(1) unsigned NOT NULL DEFAULT 0 COMMENT '1 means vlan mapping of this account was changed', + PRIMARY KEY(`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + SET foreign_key_checks = 1;