mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
Merge branch 'pvlan'
Conflicts: scripts/vm/hypervisor/xenserver/xenserver60/patch server/src/com/cloud/network/NetworkManager.java server/src/com/cloud/network/NetworkManagerImpl.java server/src/com/cloud/network/NetworkServiceImpl.java server/src/com/cloud/network/router/VirtualNetworkApplianceManager.java server/src/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java server/src/com/cloud/network/vpc/VpcManagerImpl.java server/src/com/cloud/vm/UserVmManagerImpl.java server/test/com/cloud/network/MockNetworkManagerImpl.java server/test/com/cloud/vpc/MockNetworkManagerImpl.java server/test/com/cloud/vpc/MockVpcVirtualNetworkApplianceManager.java
This commit is contained in:
commit
2d2c0c48cd
121
api/src/com/cloud/agent/api/PvlanSetupCommand.java
Normal file
121
api/src/com/cloud/agent/api/PvlanSetupCommand.java
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
// 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.
|
||||||
|
package com.cloud.agent.api;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
|
import com.cloud.utils.net.NetUtils;
|
||||||
|
|
||||||
|
public class PvlanSetupCommand extends Command {
|
||||||
|
public enum Type {
|
||||||
|
DHCP,
|
||||||
|
VM
|
||||||
|
}
|
||||||
|
private String op;
|
||||||
|
private String primary;
|
||||||
|
private String isolated;
|
||||||
|
private String vmMac;
|
||||||
|
private String dhcpName;
|
||||||
|
private String dhcpMac;
|
||||||
|
private String dhcpIp;
|
||||||
|
private Type type;
|
||||||
|
private String networkTag;
|
||||||
|
|
||||||
|
protected PvlanSetupCommand() {}
|
||||||
|
|
||||||
|
protected PvlanSetupCommand(Type type, String op, URI uri, String networkTag)
|
||||||
|
{
|
||||||
|
this.type = type;
|
||||||
|
this.op = op;
|
||||||
|
this.primary = NetUtils.getPrimaryPvlanFromUri(uri);
|
||||||
|
this.isolated = NetUtils.getIsolatedPvlanFromUri(uri);
|
||||||
|
this.networkTag = networkTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
static public PvlanSetupCommand createDhcpSetup(String op, URI uri, String networkTag, String dhcpName, String dhcpMac, String dhcpIp)
|
||||||
|
{
|
||||||
|
PvlanSetupCommand cmd = new PvlanSetupCommand(Type.DHCP, op, uri, networkTag);
|
||||||
|
cmd.setDhcpName(dhcpName);
|
||||||
|
cmd.setDhcpMac(dhcpMac);
|
||||||
|
cmd.setDhcpIp(dhcpIp);
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
static public PvlanSetupCommand createVmSetup(String op, URI uri, String networkTag, String vmMac)
|
||||||
|
{
|
||||||
|
PvlanSetupCommand cmd = new PvlanSetupCommand(Type.VM, op, uri, networkTag);
|
||||||
|
cmd.setVmMac(vmMac);
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean executeInSequence() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOp() {
|
||||||
|
return op;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPrimary() {
|
||||||
|
return primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIsolated() {
|
||||||
|
return isolated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVmMac() {
|
||||||
|
return vmMac;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setVmMac(String vmMac) {
|
||||||
|
this.vmMac = vmMac;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDhcpMac() {
|
||||||
|
return dhcpMac;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setDhcpMac(String dhcpMac) {
|
||||||
|
this.dhcpMac = dhcpMac;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDhcpIp() {
|
||||||
|
return dhcpIp;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setDhcpIp(String dhcpIp) {
|
||||||
|
this.dhcpIp = dhcpIp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Type getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDhcpName() {
|
||||||
|
return dhcpName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDhcpName(String dhcpName) {
|
||||||
|
this.dhcpName = dhcpName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNetworkTag() {
|
||||||
|
return networkTag;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -63,6 +63,7 @@ public class Networks {
|
|||||||
Storage("storage", Integer.class),
|
Storage("storage", Integer.class),
|
||||||
Lswitch("lswitch", String.class),
|
Lswitch("lswitch", String.class),
|
||||||
Mido("mido", String.class),
|
Mido("mido", String.class),
|
||||||
|
Pvlan("pvlan", String.class),
|
||||||
UnDecided(null, null);
|
UnDecided(null, null);
|
||||||
|
|
||||||
private String scheme;
|
private String scheme;
|
||||||
|
|||||||
@ -230,6 +230,7 @@ public class ApiConstants {
|
|||||||
public static final String VLAN_RANGE = "vlanrange";
|
public static final String VLAN_RANGE = "vlanrange";
|
||||||
public static final String REMOVE_VLAN="removevlan";
|
public static final String REMOVE_VLAN="removevlan";
|
||||||
public static final String VLAN_ID = "vlanid";
|
public static final String VLAN_ID = "vlanid";
|
||||||
|
public static final String ISOLATED_PVLAN = "isolatedpvlan";
|
||||||
public static final String VM_AVAILABLE = "vmavailable";
|
public static final String VM_AVAILABLE = "vmavailable";
|
||||||
public static final String VM_LIMIT = "vmlimit";
|
public static final String VM_LIMIT = "vmlimit";
|
||||||
public static final String VM_TOTAL = "vmtotal";
|
public static final String VM_TOTAL = "vmtotal";
|
||||||
|
|||||||
@ -80,6 +80,9 @@ public class CreateNetworkCmd extends BaseCmd {
|
|||||||
@Parameter(name=ApiConstants.VLAN, type=CommandType.STRING, description="the ID or VID of the network")
|
@Parameter(name=ApiConstants.VLAN, type=CommandType.STRING, description="the ID or VID of the network")
|
||||||
private String vlan;
|
private String vlan;
|
||||||
|
|
||||||
|
@Parameter(name=ApiConstants.ISOLATED_PVLAN, type=CommandType.STRING, description="the isolated private vlan for this network")
|
||||||
|
private String isolatedPvlan;
|
||||||
|
|
||||||
@Parameter(name=ApiConstants.NETWORK_DOMAIN, type=CommandType.STRING, description="network domain")
|
@Parameter(name=ApiConstants.NETWORK_DOMAIN, type=CommandType.STRING, description="network domain")
|
||||||
private String networkDomain;
|
private String networkDomain;
|
||||||
|
|
||||||
@ -141,6 +144,10 @@ public class CreateNetworkCmd extends BaseCmd {
|
|||||||
return vlan;
|
return vlan;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getIsolatedPvlan() {
|
||||||
|
return isolatedPvlan;
|
||||||
|
}
|
||||||
|
|
||||||
public String getAccountName() {
|
public String getAccountName() {
|
||||||
return accountName;
|
return accountName;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -125,6 +125,7 @@ import com.cloud.agent.api.PlugNicAnswer;
|
|||||||
import com.cloud.agent.api.PlugNicCommand;
|
import com.cloud.agent.api.PlugNicCommand;
|
||||||
import com.cloud.agent.api.PrepareForMigrationAnswer;
|
import com.cloud.agent.api.PrepareForMigrationAnswer;
|
||||||
import com.cloud.agent.api.PrepareForMigrationCommand;
|
import com.cloud.agent.api.PrepareForMigrationCommand;
|
||||||
|
import com.cloud.agent.api.PvlanSetupCommand;
|
||||||
import com.cloud.agent.api.ReadyAnswer;
|
import com.cloud.agent.api.ReadyAnswer;
|
||||||
import com.cloud.agent.api.ReadyCommand;
|
import com.cloud.agent.api.ReadyCommand;
|
||||||
import com.cloud.agent.api.RebootAnswer;
|
import com.cloud.agent.api.RebootAnswer;
|
||||||
@ -267,6 +268,8 @@ ServerResource {
|
|||||||
private String _createTmplPath;
|
private String _createTmplPath;
|
||||||
private String _heartBeatPath;
|
private String _heartBeatPath;
|
||||||
private String _securityGroupPath;
|
private String _securityGroupPath;
|
||||||
|
private String _ovsPvlanDhcpHostPath;
|
||||||
|
private String _ovsPvlanVmPath;
|
||||||
private String _routerProxyPath;
|
private String _routerProxyPath;
|
||||||
private String _host;
|
private String _host;
|
||||||
private String _dcId;
|
private String _dcId;
|
||||||
@ -587,6 +590,18 @@ ServerResource {
|
|||||||
"Unable to find the router_proxy.sh");
|
"Unable to find the router_proxy.sh");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ovsPvlanDhcpHostPath = Script.findScript(networkScriptsDir, "ovs-pvlan-dhcp-host.sh");
|
||||||
|
if ( _ovsPvlanDhcpHostPath == null) {
|
||||||
|
throw new ConfigurationException(
|
||||||
|
"Unable to find the ovs-pvlan-dhcp-host.sh");
|
||||||
|
}
|
||||||
|
|
||||||
|
_ovsPvlanVmPath = Script.findScript(networkScriptsDir, "ovs-pvlan-vm.sh");
|
||||||
|
if ( _ovsPvlanVmPath == null) {
|
||||||
|
throw new ConfigurationException(
|
||||||
|
"Unable to find the ovs-pvlan-vm.sh");
|
||||||
|
}
|
||||||
|
|
||||||
String value = (String) params.get("developer");
|
String value = (String) params.get("developer");
|
||||||
boolean isDeveloper = Boolean.parseBoolean(value);
|
boolean isDeveloper = Boolean.parseBoolean(value);
|
||||||
|
|
||||||
@ -1202,6 +1217,8 @@ ServerResource {
|
|||||||
return execute((CheckNetworkCommand) cmd);
|
return execute((CheckNetworkCommand) cmd);
|
||||||
} else if (cmd instanceof NetworkRulesVmSecondaryIpCommand) {
|
} else if (cmd instanceof NetworkRulesVmSecondaryIpCommand) {
|
||||||
return execute((NetworkRulesVmSecondaryIpCommand) cmd);
|
return execute((NetworkRulesVmSecondaryIpCommand) cmd);
|
||||||
|
} else if (cmd instanceof PvlanSetupCommand) {
|
||||||
|
return execute((PvlanSetupCommand) cmd);
|
||||||
} else {
|
} else {
|
||||||
s_logger.warn("Unsupported command ");
|
s_logger.warn("Unsupported command ");
|
||||||
return Answer.createUnsupportedCommandAnswer(cmd);
|
return Answer.createUnsupportedCommandAnswer(cmd);
|
||||||
@ -1517,6 +1534,65 @@ ServerResource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Answer execute(PvlanSetupCommand cmd) {
|
||||||
|
String primaryPvlan = cmd.getPrimary();
|
||||||
|
String isolatedPvlan = cmd.getIsolated();
|
||||||
|
String op = cmd.getOp();
|
||||||
|
String dhcpName = cmd.getDhcpName();
|
||||||
|
String dhcpMac = cmd.getDhcpMac();
|
||||||
|
String dhcpIp = cmd.getDhcpIp();
|
||||||
|
String vmMac = cmd.getVmMac();
|
||||||
|
boolean add = true;
|
||||||
|
|
||||||
|
String opr = "-A";
|
||||||
|
if (op.equals("delete")) {
|
||||||
|
opr = "-D";
|
||||||
|
add = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String result = null;
|
||||||
|
Connect conn;
|
||||||
|
try {
|
||||||
|
if (cmd.getType() == PvlanSetupCommand.Type.DHCP) {
|
||||||
|
Script script = new Script(_ovsPvlanDhcpHostPath, _timeout, s_logger);
|
||||||
|
if (add) {
|
||||||
|
conn = LibvirtConnection.getConnectionByVmName(dhcpName);
|
||||||
|
List<InterfaceDef> ifaces = getInterfaces(conn, dhcpName);
|
||||||
|
InterfaceDef guestNic = ifaces.get(0);
|
||||||
|
script.add(opr, "-b", _guestBridgeName,
|
||||||
|
"-p", primaryPvlan, "-i", isolatedPvlan, "-n", dhcpName,
|
||||||
|
"-d", dhcpIp, "-m", dhcpMac, "-I", guestNic.getDevName());
|
||||||
|
} else {
|
||||||
|
script.add(opr, "-b", _guestBridgeName,
|
||||||
|
"-p", primaryPvlan, "-i", isolatedPvlan, "-n", dhcpName,
|
||||||
|
"-d", dhcpIp, "-m", dhcpMac);
|
||||||
|
}
|
||||||
|
result = script.execute();
|
||||||
|
if (result != null) {
|
||||||
|
s_logger.warn("Failed to program pvlan for dhcp server with mac " + dhcpMac);
|
||||||
|
return new Answer(cmd, false, result);
|
||||||
|
} else {
|
||||||
|
s_logger.info("Programmed pvlan for dhcp server with mac " + dhcpMac);
|
||||||
|
}
|
||||||
|
} else if (cmd.getType() == PvlanSetupCommand.Type.VM) {
|
||||||
|
Script script = new Script(_ovsPvlanVmPath, _timeout, s_logger);
|
||||||
|
script.add(opr, "-b", _guestBridgeName,
|
||||||
|
"-p", primaryPvlan, "-i", isolatedPvlan, "-v", vmMac);
|
||||||
|
result = script.execute();
|
||||||
|
if (result != null) {
|
||||||
|
s_logger.warn("Failed to program pvlan for vm with mac " + vmMac);
|
||||||
|
return new Answer(cmd, false, result);
|
||||||
|
} else {
|
||||||
|
s_logger.info("Programmed pvlan for vm with mac " + vmMac);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (LibvirtException e) {
|
||||||
|
// TODO Auto-generated catch block
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return new Answer(cmd, true, result);
|
||||||
|
}
|
||||||
|
|
||||||
private void VifHotPlug(Connect conn, String vmName, String vlanId,
|
private void VifHotPlug(Connect conn, String vmName, String vlanId,
|
||||||
String macAddr) throws InternalErrorException, LibvirtException {
|
String macAddr) throws InternalErrorException, LibvirtException {
|
||||||
NicTO nicTO = new NicTO();
|
NicTO nicTO = new NicTO();
|
||||||
|
|||||||
@ -76,10 +76,12 @@ public class OvsVifDriver extends VifDriverBase {
|
|||||||
}
|
}
|
||||||
else if (nic.getBroadcastType() == Networks.BroadcastDomainType.Lswitch) {
|
else if (nic.getBroadcastType() == Networks.BroadcastDomainType.Lswitch) {
|
||||||
logicalSwitchUuid = nic.getBroadcastUri().getSchemeSpecificPart();
|
logicalSwitchUuid = nic.getBroadcastUri().getSchemeSpecificPart();
|
||||||
|
} else if (nic.getBroadcastType() == Networks.BroadcastDomainType.Pvlan) {
|
||||||
|
vlanId = NetUtils.getPrimaryPvlanFromUri(nic.getBroadcastUri());
|
||||||
}
|
}
|
||||||
String trafficLabel = nic.getName();
|
String trafficLabel = nic.getName();
|
||||||
if (nic.getType() == Networks.TrafficType.Guest) {
|
if (nic.getType() == Networks.TrafficType.Guest) {
|
||||||
if (nic.getBroadcastType() == Networks.BroadcastDomainType.Vlan
|
if ((nic.getBroadcastType() == Networks.BroadcastDomainType.Vlan || nic.getBroadcastType() == Networks.BroadcastDomainType.Pvlan)
|
||||||
&& !vlanId.equalsIgnoreCase("untagged")) {
|
&& !vlanId.equalsIgnoreCase("untagged")) {
|
||||||
if(trafficLabel != null && !trafficLabel.isEmpty()) {
|
if(trafficLabel != null && !trafficLabel.isEmpty()) {
|
||||||
s_logger.debug("creating a vlan dev and bridge for guest traffic per traffic label " + trafficLabel);
|
s_logger.debug("creating a vlan dev and bridge for guest traffic per traffic label " + trafficLabel);
|
||||||
|
|||||||
@ -83,6 +83,7 @@ import com.cloud.agent.api.PlugNicCommand;
|
|||||||
import com.cloud.agent.api.PoolEjectCommand;
|
import com.cloud.agent.api.PoolEjectCommand;
|
||||||
import com.cloud.agent.api.PrepareForMigrationAnswer;
|
import com.cloud.agent.api.PrepareForMigrationAnswer;
|
||||||
import com.cloud.agent.api.PrepareForMigrationCommand;
|
import com.cloud.agent.api.PrepareForMigrationCommand;
|
||||||
|
import com.cloud.agent.api.PvlanSetupCommand;
|
||||||
import com.cloud.agent.api.ReadyAnswer;
|
import com.cloud.agent.api.ReadyAnswer;
|
||||||
import com.cloud.agent.api.ReadyCommand;
|
import com.cloud.agent.api.ReadyCommand;
|
||||||
import com.cloud.agent.api.RebootAnswer;
|
import com.cloud.agent.api.RebootAnswer;
|
||||||
@ -614,6 +615,8 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe
|
|||||||
return execute((NetworkRulesVmSecondaryIpCommand)cmd);
|
return execute((NetworkRulesVmSecondaryIpCommand)cmd);
|
||||||
} else if (clazz == ScaleVmCommand.class) {
|
} else if (clazz == ScaleVmCommand.class) {
|
||||||
return execute((ScaleVmCommand) cmd);
|
return execute((ScaleVmCommand) cmd);
|
||||||
|
} else if (clazz == PvlanSetupCommand.class) {
|
||||||
|
return execute((PvlanSetupCommand) cmd);
|
||||||
} else {
|
} else {
|
||||||
return Answer.createUnsupportedCommandAnswer(cmd);
|
return Answer.createUnsupportedCommandAnswer(cmd);
|
||||||
}
|
}
|
||||||
@ -1030,6 +1033,11 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe
|
|||||||
} else if (nic.getBroadcastType() == BroadcastDomainType.Lswitch) {
|
} else if (nic.getBroadcastType() == BroadcastDomainType.Lswitch) {
|
||||||
// Nicira Logical Switch
|
// Nicira Logical Switch
|
||||||
return network.getNetwork();
|
return network.getNetwork();
|
||||||
|
} else if (nic.getBroadcastType() == BroadcastDomainType.Pvlan) {
|
||||||
|
URI broadcastUri = nic.getBroadcastUri();
|
||||||
|
assert broadcastUri.getScheme().equals(BroadcastDomainType.Pvlan.scheme());
|
||||||
|
long vlan = Long.parseLong(NetUtils.getPrimaryPvlanFromUri(broadcastUri));
|
||||||
|
return enableVlanNetwork(conn, vlan, network);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new CloudRuntimeException("Unable to support this type of network broadcast domain: " + nic.getBroadcastUri());
|
throw new CloudRuntimeException("Unable to support this type of network broadcast domain: " + nic.getBroadcastUri());
|
||||||
@ -1477,6 +1485,55 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Answer execute(PvlanSetupCommand cmd) {
|
||||||
|
Connection conn = getConnection();
|
||||||
|
|
||||||
|
String primaryPvlan = cmd.getPrimary();
|
||||||
|
String isolatedPvlan = cmd.getIsolated();
|
||||||
|
String op = cmd.getOp();
|
||||||
|
String dhcpName = cmd.getDhcpName();
|
||||||
|
String dhcpMac = cmd.getDhcpMac();
|
||||||
|
String dhcpIp = cmd.getDhcpIp();
|
||||||
|
String vmMac = cmd.getVmMac();
|
||||||
|
String networkTag = cmd.getNetworkTag();
|
||||||
|
|
||||||
|
XsLocalNetwork nw = null;
|
||||||
|
String nwNameLabel = null;
|
||||||
|
try {
|
||||||
|
nw = getNativeNetworkForTraffic(conn, TrafficType.Guest, networkTag);
|
||||||
|
nwNameLabel = nw.getNetwork().getNameLabel(conn);
|
||||||
|
} catch (XenAPIException e) {
|
||||||
|
s_logger.warn("Fail to get network", e);
|
||||||
|
return new Answer(cmd, false, e.toString());
|
||||||
|
} catch (XmlRpcException e) {
|
||||||
|
s_logger.warn("Fail to get network", e);
|
||||||
|
return new Answer(cmd, false, e.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
String result = null;
|
||||||
|
if (cmd.getType() == PvlanSetupCommand.Type.DHCP) {
|
||||||
|
result = callHostPlugin(conn, "ovs-pvlan", "setup-pvlan-dhcp", "op", op, "nw-label", nwNameLabel,
|
||||||
|
"primary-pvlan", primaryPvlan, "isolated-pvlan", isolatedPvlan, "dhcp-name", dhcpName,
|
||||||
|
"dhcp-ip", dhcpIp, "dhcp-mac", dhcpMac);
|
||||||
|
if (result == null || result.isEmpty() || !Boolean.parseBoolean(result)) {
|
||||||
|
s_logger.warn("Failed to program pvlan for dhcp server with mac " + dhcpMac);
|
||||||
|
return new Answer(cmd, false, result);
|
||||||
|
} else {
|
||||||
|
s_logger.info("Programmed pvlan for dhcp server with mac " + dhcpMac);
|
||||||
|
}
|
||||||
|
} else if (cmd.getType() == PvlanSetupCommand.Type.VM) {
|
||||||
|
result = callHostPlugin(conn, "ovs-pvlan", "setup-pvlan-vm", "op", op, "nw-label", nwNameLabel,
|
||||||
|
"primary-pvlan", primaryPvlan, "isolated-pvlan", isolatedPvlan, "vm-mac", vmMac);
|
||||||
|
if (result == null || result.isEmpty() || !Boolean.parseBoolean(result)) {
|
||||||
|
s_logger.warn("Failed to program pvlan for vm with mac " + vmMac);
|
||||||
|
return new Answer(cmd, false, result);
|
||||||
|
} else {
|
||||||
|
s_logger.info("Programmed pvlan for vm with mac " + vmMac);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Answer(cmd, true, result);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StartAnswer execute(StartCommand cmd) {
|
public StartAnswer execute(StartCommand cmd) {
|
||||||
Connection conn = getConnection();
|
Connection conn = getConnection();
|
||||||
|
|||||||
27
scripts/vm/hypervisor/xenserver/ovs-get-bridge.sh
Executable file
27
scripts/vm/hypervisor/xenserver/ovs-get-bridge.sh
Executable file
@ -0,0 +1,27 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
nw_label=$1
|
||||||
|
br=`xe network-list name-label="$nw_label" params=bridge |cut -d ':' -f 2 |tr -d ' ' `
|
||||||
|
pbr=`ovs-vsctl br-to-parent $br`
|
||||||
|
while [ "$br" != "$pbr" ]
|
||||||
|
do
|
||||||
|
br=$pbr
|
||||||
|
pbr=`ovs-vsctl br-to-parent $br`
|
||||||
|
done
|
||||||
|
echo $pbr
|
||||||
25
scripts/vm/hypervisor/xenserver/ovs-get-dhcp-iface.sh
Executable file
25
scripts/vm/hypervisor/xenserver/ovs-get-dhcp-iface.sh
Executable file
@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
bridge=$1
|
||||||
|
dhcp_name=$2
|
||||||
|
dom_id=`xe vm-list is-control-domain=false power-state=running params=dom-id name-label=$dhcp_name|cut -d ':' -f 2 |tr -d ' ' `
|
||||||
|
iface="vif${dom_id}.0"
|
||||||
|
echo $iface
|
||||||
145
scripts/vm/hypervisor/xenserver/ovs-pvlan
Executable file
145
scripts/vm/hypervisor/xenserver/ovs-pvlan
Executable file
@ -0,0 +1,145 @@
|
|||||||
|
#!/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.
|
||||||
|
|
||||||
|
|
||||||
|
import cloudstack_pluginlib as lib
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
import XenAPIPlugin
|
||||||
|
|
||||||
|
sys.path.append("/opt/xensource/sm/")
|
||||||
|
import util
|
||||||
|
|
||||||
|
from time import localtime as _localtime, asctime as _asctime
|
||||||
|
|
||||||
|
xePath = "/opt/xensource/bin/xe"
|
||||||
|
lib.setup_logging("/var/log/ovs-pvlan.log")
|
||||||
|
dhcpSetupPath = "/opt/xensource/bin/ovs-pvlan-dhcp-host.sh"
|
||||||
|
vmSetupPath = "/opt/xensource/bin/ovs-pvlan-vm.sh"
|
||||||
|
getDhcpIfacePath = "/opt/xensource/bin/ovs-get-dhcp-iface.sh"
|
||||||
|
pvlanCleanupPath = "/opt/xensource/bin/ovs-pvlan-cleanup.sh"
|
||||||
|
getBridgePath = "/opt/xensource/bin/ovs-get-bridge.sh"
|
||||||
|
|
||||||
|
def echo(fn):
|
||||||
|
def wrapped(*v, **k):
|
||||||
|
name = fn.__name__
|
||||||
|
util.SMlog("#### VMOPS enter %s ####" % name)
|
||||||
|
res = fn(*v, **k)
|
||||||
|
util.SMlog("#### VMOPS exit %s ####" % name)
|
||||||
|
return res
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
@echo
|
||||||
|
def setup_pvlan_dhcp(session, args):
|
||||||
|
op = args.pop("op")
|
||||||
|
nw_label = args.pop("nw-label")
|
||||||
|
primary = args.pop("primary-pvlan")
|
||||||
|
isolated = args.pop("isolated-pvlan")
|
||||||
|
dhcp_name = args.pop("dhcp-name")
|
||||||
|
dhcp_ip = args.pop("dhcp-ip")
|
||||||
|
dhcp_mac = args.pop("dhcp-mac")
|
||||||
|
|
||||||
|
res = lib.check_switch()
|
||||||
|
if res != "SUCCESS":
|
||||||
|
return "FAILURE:%s" % res
|
||||||
|
|
||||||
|
logging.debug("Network is:%s" % (nw_label))
|
||||||
|
bridge = lib.do_cmd([getBridgePath, nw_label])
|
||||||
|
logging.debug("Determine bridge/switch is :%s" % (bridge))
|
||||||
|
|
||||||
|
if op == "add":
|
||||||
|
logging.debug("Try to get dhcp vm %s port on the switch:%s" % (dhcp_name, bridge))
|
||||||
|
dhcp_iface = lib.do_cmd([getDhcpIfacePath, bridge, dhcp_name])
|
||||||
|
logging.debug("About to setup dhcp vm on the switch:%s" % bridge)
|
||||||
|
res = lib.do_cmd([dhcpSetupPath, "-A", "-b", bridge, "-p", primary,
|
||||||
|
"-i", isolated, "-n", dhcp_name, "-d", dhcp_ip, "-m", dhcp_mac,
|
||||||
|
"-I", dhcp_iface])
|
||||||
|
if res:
|
||||||
|
result = "FAILURE:%s" % res
|
||||||
|
return result;
|
||||||
|
logging.debug("Setup dhcp vm on switch program done")
|
||||||
|
elif op == "delete":
|
||||||
|
logging.debug("About to remove dhcp the switch:%s" % bridge)
|
||||||
|
res = lib.do_cmd([dhcpSetupPath, "-D", "-b", bridge, "-p", primary,
|
||||||
|
"-i", isolated, "-n", dhcp_name, "-d", dhcp_ip, "-m", dhcp_mac])
|
||||||
|
if res:
|
||||||
|
result = "FAILURE:%s" % res
|
||||||
|
return result;
|
||||||
|
logging.debug("Remove DHCP on switch program done")
|
||||||
|
|
||||||
|
result = "true"
|
||||||
|
logging.debug("Setup_pvlan_dhcp completed with result:%s" % result)
|
||||||
|
return result
|
||||||
|
|
||||||
|
@echo
|
||||||
|
def setup_pvlan_vm(session, args):
|
||||||
|
op = args.pop("op")
|
||||||
|
nw_label = args.pop("nw-label")
|
||||||
|
primary = args.pop("primary-pvlan")
|
||||||
|
isolated = args.pop("isolated-pvlan")
|
||||||
|
vm_mac = args.pop("vm-mac")
|
||||||
|
trunk_port = 1
|
||||||
|
|
||||||
|
res = lib.check_switch()
|
||||||
|
if res != "SUCCESS":
|
||||||
|
return "FAILURE:%s" % res
|
||||||
|
|
||||||
|
bridge = lib.do_cmd([getBridgePath, nw_label])
|
||||||
|
logging.debug("Determine bridge/switch is :%s" % (bridge))
|
||||||
|
|
||||||
|
if op == "add":
|
||||||
|
logging.debug("About to setup vm on the switch:%s" % bridge)
|
||||||
|
res = lib.do_cmd([vmSetupPath, "-A", "-b", bridge, "-p", primary, "-i", isolated, "-v", vm_mac])
|
||||||
|
if res:
|
||||||
|
result = "FAILURE:%s" % res
|
||||||
|
return result;
|
||||||
|
logging.debug("Setup vm on switch program done")
|
||||||
|
elif op == "delete":
|
||||||
|
logging.debug("About to remove vm on the switch:%s" % bridge)
|
||||||
|
res = lib.do_cmd([vmSetupPath, "-D", "-b", bridge, "-p", primary, "-i", isolated, "-v", vm_mac])
|
||||||
|
if res:
|
||||||
|
result = "FAILURE:%s" % res
|
||||||
|
return result;
|
||||||
|
logging.debug("Remove vm on switch program done")
|
||||||
|
|
||||||
|
result = "true"
|
||||||
|
logging.debug("Setup_pvlan_vm_alone completed with result:%s" % result)
|
||||||
|
return result
|
||||||
|
|
||||||
|
@echo
|
||||||
|
def cleanup(session, args):
|
||||||
|
res = lib.check_switch()
|
||||||
|
if res != "SUCCESS":
|
||||||
|
return "FAILURE:%s" % res
|
||||||
|
|
||||||
|
res = lib.do_cmd([pvlanCleanUpPath])
|
||||||
|
if res:
|
||||||
|
result = "FAILURE:%s" % res
|
||||||
|
return result;
|
||||||
|
|
||||||
|
result = "true"
|
||||||
|
logging.debug("Setup_pvlan_vm_dhcp completed with result:%s" % result)
|
||||||
|
return result
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
XenAPIPlugin.dispatch({"setup-pvlan-dhcp": setup_pvlan_dhcp,
|
||||||
|
"setup-pvlan-vm": setup_pvlan_vm,
|
||||||
|
"cleanup":cleanup})
|
||||||
@ -70,4 +70,9 @@ swift=..,0755,/opt/xensource/bin
|
|||||||
swiftxen=..,0755,/etc/xapi.d/plugins
|
swiftxen=..,0755,/etc/xapi.d/plugins
|
||||||
s3xen=..,0755,/etc/xapi.d/plugins
|
s3xen=..,0755,/etc/xapi.d/plugins
|
||||||
add_to_vcpus_params_live.sh=..,0755,/opt/xensource/bin
|
add_to_vcpus_params_live.sh=..,0755,/opt/xensource/bin
|
||||||
|
ovs-pvlan=..,0755,/etc/xapi.d/plugins
|
||||||
|
ovs-pvlan-dhcp-host.sh=../../../network,0755,/opt/xensource/bin
|
||||||
|
ovs-pvlan-vm.sh=../../../network,0755,/opt/xensource/bin
|
||||||
|
ovs-pvlan-cleanup.sh=../../../network,0755,/opt/xensource/bin
|
||||||
|
ovs-get-dhcp-iface.sh=..,0755,/opt/xensource/bin
|
||||||
|
ovs-get-bridge.sh=..,0755,/opt/xensource/bin
|
||||||
|
|||||||
23
scripts/vm/network/ovs-pvlan-cleanup.sh
Executable file
23
scripts/vm/network/ovs-pvlan-cleanup.sh
Executable file
@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
ovs-ofctl del-flows xenbr0
|
||||||
|
ovs-ofctl add-flow xenbr0 priority=0,actions=NORMAL
|
||||||
|
|
||||||
123
scripts/vm/network/ovs-pvlan-dhcp-host.sh
Executable file
123
scripts/vm/network/ovs-pvlan-dhcp-host.sh
Executable file
@ -0,0 +1,123 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
printf "Usage: %s: (-A|-D) -b <bridge/switch> -p <primary vlan> -i <secondary isolated vlan> -n <DHCP server name> -d <DHCP server IP> -m <DHCP server MAC> -I <interface> -v <VM MAC> -h \n" $(basename $0) >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
br=
|
||||||
|
pri_vlan=
|
||||||
|
sec_iso_vlan=
|
||||||
|
dhcp_name=
|
||||||
|
dhcp_ip=
|
||||||
|
dhcp_mac=
|
||||||
|
vm_mac=
|
||||||
|
iface=
|
||||||
|
op=
|
||||||
|
|
||||||
|
while getopts 'ADb:p:i:d:m:v:n:I:h' OPTION
|
||||||
|
do
|
||||||
|
case $OPTION in
|
||||||
|
A) op="add"
|
||||||
|
;;
|
||||||
|
D) op="del"
|
||||||
|
;;
|
||||||
|
b) br="$OPTARG"
|
||||||
|
;;
|
||||||
|
p) pri_vlan="$OPTARG"
|
||||||
|
;;
|
||||||
|
i) sec_iso_vlan="$OPTARG"
|
||||||
|
;;
|
||||||
|
n) dhcp_name="$OPTARG"
|
||||||
|
;;
|
||||||
|
d) dhcp_ip="$OPTARG"
|
||||||
|
;;
|
||||||
|
m) dhcp_mac="$OPTARG"
|
||||||
|
;;
|
||||||
|
I) iface="$OPTARG"
|
||||||
|
;;
|
||||||
|
v) vm_mac="$OPTARG"
|
||||||
|
;;
|
||||||
|
h) usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "$op" ]
|
||||||
|
then
|
||||||
|
echo Missing operation pararmeter!
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$br" ]
|
||||||
|
then
|
||||||
|
echo Missing parameter bridge!
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$pri_vlan" ]
|
||||||
|
then
|
||||||
|
echo Missing parameter primary vlan!
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$sec_iso_vlan" ]
|
||||||
|
then
|
||||||
|
echo Missing parameter secondary isolate vlan!
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$dhcp_name" ]
|
||||||
|
then
|
||||||
|
echo Missing parameter DHCP NAME!
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$dhcp_ip" ]
|
||||||
|
then
|
||||||
|
echo Missing parameter DHCP IP!
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$dhcp_mac" ]
|
||||||
|
then
|
||||||
|
echo Missing parameter DHCP MAC!
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$op" == "add" -a -z "$iface" ]
|
||||||
|
then
|
||||||
|
echo Missing parameter DHCP VM interface!
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$op" == "add" ]
|
||||||
|
then
|
||||||
|
dhcp_port=`ovs-ofctl show $br | grep $iface | cut -d '(' -f 1|tr -d ' '`
|
||||||
|
ovs-ofctl add-flow $br priority=200,arp,dl_vlan=$sec_iso_vlan,nw_dst=$dhcp_ip,actions=strip_vlan,output:$dhcp_port
|
||||||
|
ovs-ofctl add-flow $br priority=150,dl_vlan=$sec_iso_vlan,dl_dst=$dhcp_mac,actions=strip_vlan,output:$dhcp_port
|
||||||
|
ovs-ofctl add-flow $br priority=100,udp,dl_vlan=$sec_iso_vlan,nw_dst=255.255.255.255,tp_dst=67,actions=strip_vlan,output:$dhcp_port
|
||||||
|
else
|
||||||
|
ovs-ofctl del-flows --strict $br priority=200,arp,dl_vlan=$sec_iso_vlan,nw_dst=$dhcp_ip
|
||||||
|
ovs-ofctl del-flows --strict $br priority=150,dl_vlan=$sec_iso_vlan,dl_dst=$dhcp_mac
|
||||||
|
ovs-ofctl del-flows --strict $br priority=100,udp,dl_vlan=$sec_iso_vlan,nw_dst=255.255.255.255,tp_dst=67
|
||||||
|
fi
|
||||||
99
scripts/vm/network/ovs-pvlan-vm.sh
Executable file
99
scripts/vm/network/ovs-pvlan-vm.sh
Executable file
@ -0,0 +1,99 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
printf "Usage: %s: (-A|-D) -b <bridge/switch> -p <primary vlan> -i <secondary isolated vlan> -d <DHCP server IP> -m <DHCP server MAC> -v <VM MAC> -h \n" $(basename $0) >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
br=
|
||||||
|
pri_vlan=
|
||||||
|
sec_iso_vlan=
|
||||||
|
dhcp_ip=
|
||||||
|
dhcp_mac=
|
||||||
|
vm_mac=
|
||||||
|
op=
|
||||||
|
|
||||||
|
while getopts 'ADb:p:i:d:m:v:h' OPTION
|
||||||
|
do
|
||||||
|
case $OPTION in
|
||||||
|
A) op="add"
|
||||||
|
;;
|
||||||
|
D) op="del"
|
||||||
|
;;
|
||||||
|
b) br="$OPTARG"
|
||||||
|
;;
|
||||||
|
p) pri_vlan="$OPTARG"
|
||||||
|
;;
|
||||||
|
i) sec_iso_vlan="$OPTARG"
|
||||||
|
;;
|
||||||
|
d) dhcp_ip="$OPTARG"
|
||||||
|
;;
|
||||||
|
m) dhcp_mac="$OPTARG"
|
||||||
|
;;
|
||||||
|
v) vm_mac="$OPTARG"
|
||||||
|
;;
|
||||||
|
h) usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "$op" ]
|
||||||
|
then
|
||||||
|
echo Missing operation pararmeter!
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$br" ]
|
||||||
|
then
|
||||||
|
echo Missing parameter bridge!
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$vm_mac" ]
|
||||||
|
then
|
||||||
|
echo Missing parameter VM MAC!
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$pri_vlan" ]
|
||||||
|
then
|
||||||
|
echo Missing parameter secondary isolate vlan!
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$sec_iso_vlan" ]
|
||||||
|
then
|
||||||
|
echo Missing parameter secondary isolate vlan!
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
trunk_port=1
|
||||||
|
|
||||||
|
if [ "$op" == "add" ]
|
||||||
|
then
|
||||||
|
ovs-ofctl add-flow $br priority=50,dl_vlan=0xffff,dl_src=$vm_mac,actions=mod_vlan_vid:$sec_iso_vlan,resubmit:$trunk_port
|
||||||
|
ovs-ofctl add-flow $br priority=60,dl_vlan=$sec_iso_vlan,dl_src=$vm_mac,actions=output:$trunk_port
|
||||||
|
else
|
||||||
|
ovs-ofctl del-flows --strict $br priority=50,dl_vlan=0xffff,dl_src=$vm_mac
|
||||||
|
ovs-ofctl del-flows --strict $br priority=60,dl_vlan=$sec_iso_vlan,dl_src=$vm_mac
|
||||||
|
fi
|
||||||
|
|
||||||
@ -2657,6 +2657,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
|
|||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
String[] vlan = uri.toString().split("vlan:\\/\\/");
|
String[] vlan = uri.toString().split("vlan:\\/\\/");
|
||||||
networkVlanId = vlan[1];
|
networkVlanId = vlan[1];
|
||||||
|
//For pvlan
|
||||||
|
networkVlanId = networkVlanId.split("-")[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vlanId != null) {
|
if (vlanId != null) {
|
||||||
|
|||||||
@ -130,7 +130,8 @@ public interface NetworkManager {
|
|||||||
|
|
||||||
Network createGuestNetwork(long networkOfferingId, String name, String displayText, String gateway, String cidr,
|
Network createGuestNetwork(long networkOfferingId, String name, String displayText, String gateway, String cidr,
|
||||||
String vlanId, String networkDomain, Account owner, Long domainId, PhysicalNetwork physicalNetwork,
|
String vlanId, String networkDomain, Account owner, Long domainId, PhysicalNetwork physicalNetwork,
|
||||||
long zoneId, ACLType aclType, Boolean subdomainAccess, Long vpcId, String ip6Gateway, String ip6Cidr, Boolean displayNetworkEnabled)
|
long zoneId, ACLType aclType, Boolean subdomainAccess, Long vpcId, String ip6Gateway, String ip6Cidr,
|
||||||
|
Boolean displayNetworkEnabled, String isolatedPvlan)
|
||||||
throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException;
|
throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1900,7 +1900,8 @@ public class NetworkManagerImpl extends ManagerBase implements NetworkManager, L
|
|||||||
@DB
|
@DB
|
||||||
public Network createGuestNetwork(long networkOfferingId, String name, String displayText, String gateway,
|
public Network createGuestNetwork(long networkOfferingId, String name, String displayText, String gateway,
|
||||||
String cidr, String vlanId, String networkDomain, Account owner, Long domainId,
|
String cidr, String vlanId, String networkDomain, Account owner, Long domainId,
|
||||||
PhysicalNetwork pNtwk, long zoneId, ACLType aclType, Boolean subdomainAccess, Long vpcId, String ip6Gateway, String ip6Cidr, Boolean isDisplayNetworkEnabled)
|
PhysicalNetwork pNtwk, long zoneId, ACLType aclType, Boolean subdomainAccess, Long vpcId, String ip6Gateway, String ip6Cidr,
|
||||||
|
Boolean isDisplayNetworkEnabled, String isolatedPvlan)
|
||||||
throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException {
|
throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException {
|
||||||
|
|
||||||
NetworkOfferingVO ntwkOff = _networkOfferingDao.findById(networkOfferingId);
|
NetworkOfferingVO ntwkOff = _networkOfferingDao.findById(networkOfferingId);
|
||||||
@ -1990,6 +1991,9 @@ public class NetworkManagerImpl extends ManagerBase implements NetworkManager, L
|
|||||||
if (ipv6) {
|
if (ipv6) {
|
||||||
throw new InvalidParameterValueException("IPv6 is not supported with security group!");
|
throw new InvalidParameterValueException("IPv6 is not supported with security group!");
|
||||||
}
|
}
|
||||||
|
if (isolatedPvlan != null) {
|
||||||
|
throw new InvalidParameterValueException("Isolated Private VLAN is not supported with security group!");
|
||||||
|
}
|
||||||
// Only Account specific Isolated network with sourceNat service disabled are allowed in security group
|
// Only Account specific Isolated network with sourceNat service disabled are allowed in security group
|
||||||
// enabled zone
|
// enabled zone
|
||||||
if ( ntwkOff.getGuestType() != GuestType.Shared ){
|
if ( ntwkOff.getGuestType() != GuestType.Shared ){
|
||||||
@ -2149,13 +2153,20 @@ public class NetworkManagerImpl extends ManagerBase implements NetworkManager, L
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (vlanId != null) {
|
if (vlanId != null) {
|
||||||
userNetwork.setBroadcastUri(URI.create("vlan://" + vlanId));
|
if (isolatedPvlan == null) {
|
||||||
userNetwork.setBroadcastDomainType(BroadcastDomainType.Vlan);
|
userNetwork.setBroadcastUri(URI.create("vlan://" + vlanId));
|
||||||
if (!vlanId.equalsIgnoreCase(Vlan.UNTAGGED)) {
|
if (!vlanId.equalsIgnoreCase(Vlan.UNTAGGED)) {
|
||||||
userNetwork.setBroadcastDomainType(BroadcastDomainType.Vlan);
|
userNetwork.setBroadcastDomainType(BroadcastDomainType.Vlan);
|
||||||
} else {
|
} else {
|
||||||
userNetwork.setBroadcastDomainType(BroadcastDomainType.Native);
|
userNetwork.setBroadcastDomainType(BroadcastDomainType.Native);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (vlanId.equalsIgnoreCase(Vlan.UNTAGGED)) {
|
||||||
|
throw new InvalidParameterValueException("Cannot support pvlan with untagged primary vlan!");
|
||||||
|
}
|
||||||
|
userNetwork.setBroadcastUri(NetUtils.generateUriForPvlan(vlanId, isolatedPvlan));
|
||||||
|
userNetwork.setBroadcastDomainType(BroadcastDomainType.Pvlan);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<NetworkVO> networks = setupNetwork(owner, ntwkOff, userNetwork, plan, name, displayText, true, domainId,
|
List<NetworkVO> networks = setupNetwork(owner, ntwkOff, userNetwork, plan, name, displayText, true, domainId,
|
||||||
@ -2758,7 +2769,7 @@ public class NetworkManagerImpl extends ManagerBase implements NetworkManager, L
|
|||||||
guestNetwork = createGuestNetwork(requiredOfferings.get(0).getId(), owner.getAccountName() + "-network"
|
guestNetwork = createGuestNetwork(requiredOfferings.get(0).getId(), owner.getAccountName() + "-network"
|
||||||
, owner.getAccountName() + "-network", null, null, null, null, owner, null, physicalNetwork,
|
, owner.getAccountName() + "-network", null, null, null, null, owner, null, physicalNetwork,
|
||||||
zoneId, ACLType.Account,
|
zoneId, ACLType.Account,
|
||||||
null, null, null, null, true);
|
null, null, null, null, true, null);
|
||||||
if (guestNetwork == null) {
|
if (guestNetwork == null) {
|
||||||
s_logger.warn("Failed to create default Virtual network for the account " + accountId + "in zone " + zoneId);
|
s_logger.warn("Failed to create default Virtual network for the account " + accountId + "in zone " + zoneId);
|
||||||
throw new CloudRuntimeException("Failed to create a Guest Isolated Networks with SourceNAT " +
|
throw new CloudRuntimeException("Failed to create a Guest Isolated Networks with SourceNAT " +
|
||||||
@ -3634,8 +3645,10 @@ public class NetworkManagerImpl extends ManagerBase implements NetworkManager, L
|
|||||||
nic.setGateway(ip.getGateway());
|
nic.setGateway(ip.getGateway());
|
||||||
nic.setNetmask(ip.getNetmask());
|
nic.setNetmask(ip.getNetmask());
|
||||||
nic.setIsolationUri(IsolationType.Vlan.toUri(ip.getVlanTag()));
|
nic.setIsolationUri(IsolationType.Vlan.toUri(ip.getVlanTag()));
|
||||||
nic.setBroadcastType(BroadcastDomainType.Vlan);
|
//nic.setBroadcastType(BroadcastDomainType.Vlan);
|
||||||
nic.setBroadcastUri(BroadcastDomainType.Vlan.toUri(ip.getVlanTag()));
|
//nic.setBroadcastUri(BroadcastDomainType.Vlan.toUri(ip.getVlanTag()));
|
||||||
|
nic.setBroadcastType(network.getBroadcastDomainType());
|
||||||
|
nic.setBroadcastUri(network.getBroadcastUri());
|
||||||
nic.setFormat(AddressFormat.Ip4);
|
nic.setFormat(AddressFormat.Ip4);
|
||||||
nic.setReservationId(String.valueOf(ip.getVlanTag()));
|
nic.setReservationId(String.valueOf(ip.getVlanTag()));
|
||||||
nic.setMacAddress(ip.getMacAddress());
|
nic.setMacAddress(ip.getMacAddress());
|
||||||
|
|||||||
@ -952,6 +952,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService {
|
|||||||
String ip6Cidr = cmd.getIp6Cidr();
|
String ip6Cidr = cmd.getIp6Cidr();
|
||||||
Boolean displayNetwork = cmd.getDisplayNetwork();
|
Boolean displayNetwork = cmd.getDisplayNetwork();
|
||||||
Long aclId = cmd.getAclId();
|
Long aclId = cmd.getAclId();
|
||||||
|
String isolatedPvlan = cmd.getIsolatedPvlan();
|
||||||
|
|
||||||
// Validate network offering
|
// Validate network offering
|
||||||
NetworkOfferingVO ntwkOff = _networkOfferingDao.findById(networkOfferingId);
|
NetworkOfferingVO ntwkOff = _networkOfferingDao.findById(networkOfferingId);
|
||||||
@ -1143,6 +1144,14 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isolatedPvlan != null && (zone.getNetworkType() != NetworkType.Advanced || ntwkOff.getGuestType() != Network.GuestType.Shared)) {
|
||||||
|
throw new InvalidParameterValueException("Can only support create Private VLAN network with advance shared network!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isolatedPvlan != null && ipv6) {
|
||||||
|
throw new InvalidParameterValueException("Can only support create Private VLAN network with IPv4!");
|
||||||
|
}
|
||||||
|
|
||||||
// Regular user can create Guest Isolated Source Nat enabled network only
|
// Regular user can create Guest Isolated Source Nat enabled network only
|
||||||
if (caller.getType() == Account.ACCOUNT_TYPE_NORMAL
|
if (caller.getType() == Account.ACCOUNT_TYPE_NORMAL
|
||||||
&& (ntwkOff.getTrafficType() != TrafficType.Guest || ntwkOff.getGuestType() != Network.GuestType.Isolated
|
&& (ntwkOff.getTrafficType() != TrafficType.Guest || ntwkOff.getGuestType() != Network.GuestType.Isolated
|
||||||
@ -1175,6 +1184,10 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService {
|
|||||||
throw new InvalidParameterValueException("Cannot support IPv6 on network offering with external devices!");
|
throw new InvalidParameterValueException("Cannot support IPv6 on network offering with external devices!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isolatedPvlan != null && providersConfiguredForExternalNetworking(ntwkProviders)) {
|
||||||
|
throw new InvalidParameterValueException("Cannot support private vlan on network offering with external devices!");
|
||||||
|
}
|
||||||
|
|
||||||
if (cidr != null && providersConfiguredForExternalNetworking(ntwkProviders)) {
|
if (cidr != null && providersConfiguredForExternalNetworking(ntwkProviders)) {
|
||||||
if (ntwkOff.getGuestType() == GuestType.Shared && (zone.getNetworkType() == NetworkType.Advanced) &&
|
if (ntwkOff.getGuestType() == GuestType.Shared && (zone.getNetworkType() == NetworkType.Advanced) &&
|
||||||
isSharedNetworkOfferingWithServices(networkOfferingId)) {
|
isSharedNetworkOfferingWithServices(networkOfferingId)) {
|
||||||
@ -1266,7 +1279,8 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
network = _networkMgr.createGuestNetwork(networkOfferingId, name, displayText, gateway, cidr, vlanId,
|
network = _networkMgr.createGuestNetwork(networkOfferingId, name, displayText, gateway, cidr, vlanId,
|
||||||
networkDomain, owner, sharedDomainId, pNtwk, zoneId, aclType, subdomainAccess, vpcId, ip6Gateway, ip6Cidr, displayNetwork);
|
networkDomain, owner, sharedDomainId, pNtwk, zoneId, aclType, subdomainAccess, vpcId,
|
||||||
|
ip6Gateway, ip6Cidr, displayNetwork, isolatedPvlan);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (caller.getType() == Account.ACCOUNT_TYPE_ADMIN && createVlan) {
|
if (caller.getType() == Account.ACCOUNT_TYPE_ADMIN && createVlan) {
|
||||||
@ -3813,8 +3827,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService {
|
|||||||
if (privateNetwork == null) {
|
if (privateNetwork == null) {
|
||||||
//create Guest network
|
//create Guest network
|
||||||
privateNetwork = _networkMgr.createGuestNetwork(ntwkOff.getId(), networkName, displayText, gateway, cidr, vlan,
|
privateNetwork = _networkMgr.createGuestNetwork(ntwkOff.getId(), networkName, displayText, gateway, cidr, vlan,
|
||||||
null, owner, null, pNtwk, pNtwk.getDataCenterId(), ACLType.Account, null, vpcId, null, null, true);
|
null, owner, null, pNtwk, pNtwk.getDataCenterId(), ACLType.Account, null, vpcId, null, null, true, null);
|
||||||
|
|
||||||
s_logger.debug("Created private network " + privateNetwork);
|
s_logger.debug("Created private network " + privateNetwork);
|
||||||
} else {
|
} else {
|
||||||
s_logger.debug("Private network already exists: " + privateNetwork);
|
s_logger.debug("Private network already exists: " + privateNetwork);
|
||||||
|
|||||||
@ -30,6 +30,7 @@ import org.apache.cloudstack.api.command.admin.router.CreateVirtualRouterElement
|
|||||||
import org.apache.cloudstack.api.command.admin.router.ListVirtualRouterElementsCmd;
|
import org.apache.cloudstack.api.command.admin.router.ListVirtualRouterElementsCmd;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
|
import com.cloud.agent.api.PvlanSetupCommand;
|
||||||
import com.cloud.agent.api.to.LoadBalancerTO;
|
import com.cloud.agent.api.to.LoadBalancerTO;
|
||||||
import com.cloud.configuration.ConfigurationManager;
|
import com.cloud.configuration.ConfigurationManager;
|
||||||
import com.cloud.configuration.dao.ConfigurationDao;
|
import com.cloud.configuration.dao.ConfigurationDao;
|
||||||
@ -47,6 +48,7 @@ import com.cloud.network.Network.Capability;
|
|||||||
import com.cloud.network.Network.Provider;
|
import com.cloud.network.Network.Provider;
|
||||||
import com.cloud.network.Network.Service;
|
import com.cloud.network.Network.Service;
|
||||||
import com.cloud.network.NetworkModel;
|
import com.cloud.network.NetworkModel;
|
||||||
|
import com.cloud.network.Networks.BroadcastDomainType;
|
||||||
import com.cloud.network.Networks.TrafficType;
|
import com.cloud.network.Networks.TrafficType;
|
||||||
import com.cloud.network.PhysicalNetworkServiceProvider;
|
import com.cloud.network.PhysicalNetworkServiceProvider;
|
||||||
import com.cloud.network.PublicIpAddress;
|
import com.cloud.network.PublicIpAddress;
|
||||||
@ -228,7 +230,6 @@ public class VirtualRouterElement extends AdapterBase implements VirtualRouterEl
|
|||||||
throw new ResourceUnavailableException("Can't find at least one running router!",
|
throw new ResourceUnavailableException("Can't find at least one running router!",
|
||||||
DataCenter.class, network.getDataCenterId());
|
DataCenter.class, network.getDataCenterId());
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -33,6 +33,7 @@ import com.cloud.user.User;
|
|||||||
import com.cloud.uservm.UserVm;
|
import com.cloud.uservm.UserVm;
|
||||||
import com.cloud.utils.component.Manager;
|
import com.cloud.utils.component.Manager;
|
||||||
import com.cloud.vm.DomainRouterVO;
|
import com.cloud.vm.DomainRouterVO;
|
||||||
|
import com.cloud.vm.Nic;
|
||||||
import com.cloud.vm.NicProfile;
|
import com.cloud.vm.NicProfile;
|
||||||
import com.cloud.vm.VirtualMachineProfile;
|
import com.cloud.vm.VirtualMachineProfile;
|
||||||
|
|
||||||
|
|||||||
@ -34,6 +34,7 @@ import com.cloud.agent.api.GetDomRVersionCmd;
|
|||||||
import com.cloud.agent.api.ModifySshKeysCommand;
|
import com.cloud.agent.api.ModifySshKeysCommand;
|
||||||
import com.cloud.agent.api.NetworkUsageAnswer;
|
import com.cloud.agent.api.NetworkUsageAnswer;
|
||||||
import com.cloud.agent.api.NetworkUsageCommand;
|
import com.cloud.agent.api.NetworkUsageCommand;
|
||||||
|
import com.cloud.agent.api.PvlanSetupCommand;
|
||||||
import com.cloud.agent.api.StartupCommand;
|
import com.cloud.agent.api.StartupCommand;
|
||||||
import com.cloud.agent.api.StopAnswer;
|
import com.cloud.agent.api.StopAnswer;
|
||||||
import com.cloud.agent.api.check.CheckSshAnswer;
|
import com.cloud.agent.api.check.CheckSshAnswer;
|
||||||
@ -2222,6 +2223,28 @@ public class VirtualNetworkApplianceManagerImpl extends ManagerBase implements V
|
|||||||
return dhcpRange;
|
return dhcpRange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean setupDhcpForPvlan(boolean add, DomainRouterVO router, Nic nic) {
|
||||||
|
if (!nic.getBroadcastUri().getScheme().equals("pvlan")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String op = "add";
|
||||||
|
if (!add) {
|
||||||
|
op = "delete";
|
||||||
|
}
|
||||||
|
Network network = _networkDao.findById(nic.getNetworkId());
|
||||||
|
String networkTag = _networkModel.getNetworkTag(router.getHypervisorType(), network);
|
||||||
|
PvlanSetupCommand cmd = PvlanSetupCommand.createDhcpSetup(op, nic.getBroadcastUri(), networkTag, router.getInstanceName(), nic.getMacAddress(), nic.getIp4Address());
|
||||||
|
Commands cmds = new Commands(cmd);
|
||||||
|
// In fact we send command to the host of router, we're not programming router but the host
|
||||||
|
try {
|
||||||
|
sendCommandsToRouter(router, cmds);
|
||||||
|
} catch (AgentUnavailableException e) {
|
||||||
|
s_logger.warn("Agent Unavailable ", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean finalizeDeployment(Commands cmds, VirtualMachineProfile<DomainRouterVO> profile,
|
public boolean finalizeDeployment(Commands cmds, VirtualMachineProfile<DomainRouterVO> profile,
|
||||||
DeployDestination dest, ReservationContext context) throws ResourceUnavailableException {
|
DeployDestination dest, ReservationContext context) throws ResourceUnavailableException {
|
||||||
@ -2535,13 +2558,20 @@ public class VirtualNetworkApplianceManagerImpl extends ManagerBase implements V
|
|||||||
List<Network> guestNetworks = new ArrayList<Network>();
|
List<Network> guestNetworks = new ArrayList<Network>();
|
||||||
|
|
||||||
List<? extends Nic> routerNics = _nicDao.listByVmId(profile.getId());
|
List<? extends Nic> routerNics = _nicDao.listByVmId(profile.getId());
|
||||||
for (Nic routerNic : routerNics) {
|
for (Nic nic : routerNics) {
|
||||||
Network network = _networkModel.getNetwork(routerNic.getNetworkId());
|
Network network = _networkModel.getNetwork(nic.getNetworkId());
|
||||||
if (network.getTrafficType() == TrafficType.Guest) {
|
if (network.getTrafficType() == TrafficType.Guest) {
|
||||||
guestNetworks.add(network);
|
guestNetworks.add(network);
|
||||||
|
if (nic.getBroadcastUri().getScheme().equals("pvlan")) {
|
||||||
|
result = setupDhcpForPvlan(true, router, nic);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
answer = cmds.getAnswer("getDomRVersion");
|
answer = cmds.getAnswer("getDomRVersion");
|
||||||
if (answer != null && answer instanceof GetDomRVersionAnswer) {
|
if (answer != null && answer instanceof GetDomRVersionAnswer) {
|
||||||
GetDomRVersionAnswer versionAnswer = (GetDomRVersionAnswer)answer;
|
GetDomRVersionAnswer versionAnswer = (GetDomRVersionAnswer)answer;
|
||||||
@ -2567,6 +2597,14 @@ public class VirtualNetworkApplianceManagerImpl extends ManagerBase implements V
|
|||||||
VMInstanceVO vm = profile.getVirtualMachine();
|
VMInstanceVO vm = profile.getVirtualMachine();
|
||||||
DomainRouterVO domR = _routerDao.findById(vm.getId());
|
DomainRouterVO domR = _routerDao.findById(vm.getId());
|
||||||
processStopOrRebootAnswer(domR, answer);
|
processStopOrRebootAnswer(domR, answer);
|
||||||
|
List<? extends Nic> routerNics = _nicDao.listByVmId(profile.getId());
|
||||||
|
for (Nic nic : routerNics) {
|
||||||
|
Network network = _networkModel.getNetwork(nic.getNetworkId());
|
||||||
|
if (network.getTrafficType() == TrafficType.Guest && nic.getBroadcastUri().getScheme().equals("pvlan")) {
|
||||||
|
setupDhcpForPvlan(false, domR, nic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2025,8 +2025,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
|
|||||||
|
|
||||||
//2) Create network
|
//2) Create network
|
||||||
Network guestNetwork = _ntwkMgr.createGuestNetwork(ntwkOffId, name, displayText, gateway, cidr, vlanId,
|
Network guestNetwork = _ntwkMgr.createGuestNetwork(ntwkOffId, name, displayText, gateway, cidr, vlanId,
|
||||||
networkDomain, owner, domainId, pNtwk, zoneId, aclType, subdomainAccess, vpcId, null, null, isDisplayNetworkEnabled);
|
networkDomain, owner, domainId, pNtwk, zoneId, aclType, subdomainAccess, vpcId, null, null, isDisplayNetworkEnabled, null);
|
||||||
|
|
||||||
|
|
||||||
if(guestNetwork != null){
|
if(guestNetwork != null){
|
||||||
guestNetwork.setNetworkACLId(aclId);
|
guestNetwork.setNetworkACLId(aclId);
|
||||||
|
|||||||
@ -69,6 +69,7 @@ import com.cloud.agent.api.GetVmStatsAnswer;
|
|||||||
import com.cloud.agent.api.GetVmStatsCommand;
|
import com.cloud.agent.api.GetVmStatsCommand;
|
||||||
import com.cloud.agent.api.PlugNicAnswer;
|
import com.cloud.agent.api.PlugNicAnswer;
|
||||||
import com.cloud.agent.api.PlugNicCommand;
|
import com.cloud.agent.api.PlugNicCommand;
|
||||||
|
import com.cloud.agent.api.PvlanSetupCommand;
|
||||||
import com.cloud.agent.api.StartAnswer;
|
import com.cloud.agent.api.StartAnswer;
|
||||||
import com.cloud.agent.api.StopAnswer;
|
import com.cloud.agent.api.StopAnswer;
|
||||||
import com.cloud.agent.api.UnPlugNicAnswer;
|
import com.cloud.agent.api.UnPlugNicAnswer;
|
||||||
@ -2191,7 +2192,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use
|
|||||||
s_logger.debug("Creating network for account " + owner + " from the network offering id=" +requiredOfferings.get(0).getId() + " as a part of deployVM process");
|
s_logger.debug("Creating network for account " + owner + " from the network offering id=" +requiredOfferings.get(0).getId() + " as a part of deployVM process");
|
||||||
Network newNetwork = _networkMgr.createGuestNetwork(requiredOfferings.get(0).getId(),
|
Network newNetwork = _networkMgr.createGuestNetwork(requiredOfferings.get(0).getId(),
|
||||||
owner.getAccountName() + "-network", owner.getAccountName() + "-network", null, null,
|
owner.getAccountName() + "-network", owner.getAccountName() + "-network", null, null,
|
||||||
null, null, owner, null, physicalNetwork, zone.getId(), ACLType.Account, null, null, null, null, true);
|
null, null, owner, null, physicalNetwork, zone.getId(), ACLType.Account, null, null, null, null, true, null);
|
||||||
defaultNetwork = _networkDao.findById(newNetwork.getId());
|
defaultNetwork = _networkDao.findById(newNetwork.getId());
|
||||||
} else if (virtualNetworks.size() > 1) {
|
} else if (virtualNetworks.size() > 1) {
|
||||||
throw new InvalidParameterValueException(
|
throw new InvalidParameterValueException(
|
||||||
@ -2788,6 +2789,37 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean setupVmForPvlan(boolean add, Long hostId, NicVO nic) {
|
||||||
|
if (!nic.getBroadcastUri().getScheme().equals("pvlan")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String op = "add";
|
||||||
|
if (!add) {
|
||||||
|
// "delete" would remove all the rules(if using ovs) related to this vm
|
||||||
|
op = "delete";
|
||||||
|
}
|
||||||
|
Network network = _networkDao.findById(nic.getNetworkId());
|
||||||
|
Host host = _hostDao.findById(hostId);
|
||||||
|
String networkTag = _networkModel.getNetworkTag(host.getHypervisorType(), network);
|
||||||
|
PvlanSetupCommand cmd = PvlanSetupCommand.createVmSetup(op, nic.getBroadcastUri(), networkTag, nic.getMacAddress());
|
||||||
|
Answer answer = null;
|
||||||
|
try {
|
||||||
|
answer = _agentMgr.send(hostId, cmd);
|
||||||
|
} catch (OperationTimedoutException e) {
|
||||||
|
s_logger.warn("Timed Out", e);
|
||||||
|
return false;
|
||||||
|
} catch (AgentUnavailableException e) {
|
||||||
|
s_logger.warn("Agent Unavailable ", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean result = true;
|
||||||
|
if (answer == null || !answer.getResult()) {
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean finalizeDeployment(Commands cmds,
|
public boolean finalizeDeployment(Commands cmds,
|
||||||
VirtualMachineProfile<UserVmVO> profile, DeployDestination dest,
|
VirtualMachineProfile<UserVmVO> profile, DeployDestination dest,
|
||||||
@ -2849,6 +2881,11 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use
|
|||||||
originalIp = nic.getIp4Address();
|
originalIp = nic.getIp4Address();
|
||||||
guestNic = nic;
|
guestNic = nic;
|
||||||
guestNetwork = network;
|
guestNetwork = network;
|
||||||
|
if (nic.getBroadcastUri().getScheme().equals("pvlan")) {
|
||||||
|
if (!setupVmForPvlan(true, hostId, nic)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
boolean ipChanged = false;
|
boolean ipChanged = false;
|
||||||
@ -2979,6 +3016,17 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use
|
|||||||
+ " stop due to exception ", ex);
|
+ " stop due to exception ", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VMInstanceVO vm = profile.getVirtualMachine();
|
||||||
|
List<NicVO> nics = _nicDao.listByVmId(vm.getId());
|
||||||
|
for (NicVO nic : nics) {
|
||||||
|
NetworkVO network = _networkDao.findById(nic.getNetworkId());
|
||||||
|
if (network.getTrafficType() == TrafficType.Guest) {
|
||||||
|
if (nic.getBroadcastUri().getScheme().equals("pvlan")) {
|
||||||
|
setupVmForPvlan(false, vm.getHostId(), nic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String generateRandomPassword() {
|
public String generateRandomPassword() {
|
||||||
@ -4038,7 +4086,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use
|
|||||||
requiredOfferings.get(0).getId() + " as a part of deployVM process");
|
requiredOfferings.get(0).getId() + " as a part of deployVM process");
|
||||||
Network newNetwork = _networkMgr.createGuestNetwork(requiredOfferings.get(0).getId(),
|
Network newNetwork = _networkMgr.createGuestNetwork(requiredOfferings.get(0).getId(),
|
||||||
newAccount.getAccountName() + "-network", newAccount.getAccountName() + "-network", null, null,
|
newAccount.getAccountName() + "-network", newAccount.getAccountName() + "-network", null, null,
|
||||||
null, null, newAccount, null, physicalNetwork, zone.getId(), ACLType.Account, null, null, null, null, true);
|
null, null, newAccount, null, physicalNetwork, zone.getId(), ACLType.Account, null, null, null, null, true, null);
|
||||||
// if the network offering has persistent set to true, implement the network
|
// if the network offering has persistent set to true, implement the network
|
||||||
if (requiredOfferings.get(0).getIsPersistent()) {
|
if (requiredOfferings.get(0).getIsPersistent()) {
|
||||||
DeployDestination dest = new DeployDestination(zone, null, null, null);
|
DeployDestination dest = new DeployDestination(zone, null, null, null);
|
||||||
|
|||||||
@ -272,7 +272,7 @@ public class MockNetworkManagerImpl extends ManagerBase implements NetworkManage
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Network createGuestNetwork(long networkOfferingId, String name, String displayText, String gateway, String cidr, String vlanId, String networkDomain, Account owner, Long domainId,
|
public Network createGuestNetwork(long networkOfferingId, String name, String displayText, String gateway, String cidr, String vlanId, String networkDomain, Account owner, Long domainId,
|
||||||
PhysicalNetwork physicalNetwork, long zoneId, ACLType aclType, Boolean subdomainAccess, Long vpcId, String gatewayv6, String cidrv6, Boolean displayNetworkEnabled) throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException {
|
PhysicalNetwork physicalNetwork, long zoneId, ACLType aclType, Boolean subdomainAccess, Long vpcId, String gatewayv6, String cidrv6, Boolean displayNetworkEnabled, String isolatedPvlan) throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException {
|
||||||
// TODO Auto-generated method stub
|
// TODO Auto-generated method stub
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -868,7 +868,8 @@ public class MockNetworkManagerImpl extends ManagerBase implements NetworkManage
|
|||||||
@Override
|
@Override
|
||||||
public Network createGuestNetwork(long networkOfferingId, String name, String displayText, String gateway,
|
public Network createGuestNetwork(long networkOfferingId, String name, String displayText, String gateway,
|
||||||
String cidr, String vlanId, String networkDomain, Account owner, Long domainId,
|
String cidr, String vlanId, String networkDomain, Account owner, Long domainId,
|
||||||
PhysicalNetwork physicalNetwork, long zoneId, ACLType aclType, Boolean subdomainAccess, Long vpcId, String gatewayv6, String cidrv6, Boolean displayNetworkEnabled)
|
PhysicalNetwork physicalNetwork, long zoneId, ACLType aclType, Boolean subdomainAccess, Long vpcId, String gatewayv6, String cidrv6,
|
||||||
|
Boolean displayNetworkEnabled, String isolatedPvlan)
|
||||||
throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException {
|
throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException {
|
||||||
// TODO Auto-generated method stub
|
// TODO Auto-generated method stub
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
86
test/integration/smoke/test_pvlan.py
Normal file
86
test/integration/smoke/test_pvlan.py
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
# 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.
|
||||||
|
""" test for private vlan isolation
|
||||||
|
"""
|
||||||
|
#Import Local Modules
|
||||||
|
import marvin
|
||||||
|
from marvin.cloudstackTestCase import *
|
||||||
|
from marvin.cloudstackAPI import *
|
||||||
|
from marvin import remoteSSHClient
|
||||||
|
from marvin.integration.lib.utils import *
|
||||||
|
from marvin.integration.lib.base import *
|
||||||
|
from marvin.integration.lib.common import *
|
||||||
|
from nose.plugins.attrib import attr
|
||||||
|
import telnetlib
|
||||||
|
|
||||||
|
#Import System modules
|
||||||
|
import time
|
||||||
|
_multiprocess_shared_ = True
|
||||||
|
|
||||||
|
class TestPVLAN(cloudstackTestCase):
|
||||||
|
|
||||||
|
zoneId = 1
|
||||||
|
networkOfferingId = 7
|
||||||
|
vlan = 1234
|
||||||
|
isolatedpvlan = 567
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.apiClient = self.testClient.getApiClient()
|
||||||
|
|
||||||
|
def test_create_pvlan_network(self):
|
||||||
|
self.debug("Test create pvlan network")
|
||||||
|
createNetworkCmd = createNetwork.createNetworkCmd()
|
||||||
|
createNetworkCmd.name = "pvlan network"
|
||||||
|
createNetworkCmd.displaytext = "pvlan network"
|
||||||
|
createNetworkCmd.netmask = "255.255.255.0"
|
||||||
|
createNetworkCmd.gateway = "10.10.10.1"
|
||||||
|
createNetworkCmd.startip = "10.10.10.10"
|
||||||
|
createNetworkCmd.gateway = "10.10.10.20"
|
||||||
|
createNetworkCmd.vlan = "1234"
|
||||||
|
createNetworkCmd.isolatedpvlan = "567"
|
||||||
|
createNetworkCmd.zoneid = self.zoneId
|
||||||
|
createNetworkCmd.networkofferingid = self.networkOfferingId
|
||||||
|
createNetworkResponse = self.apiClient.createNetwork(createNetworkCmd)
|
||||||
|
self.networkId = createNetworkResponse.id
|
||||||
|
self.broadcasttype = createNetworkResponse.broadcastdomaintype
|
||||||
|
self.broadcasturi = createNetworkResponse.broadcasturi
|
||||||
|
|
||||||
|
self.assertIsNotNone(createNetworkResponse.id, "Network failed to create")
|
||||||
|
self.assertTrue(createNetworkResponse.broadcastdomaintype, "Pvlan")
|
||||||
|
self.assertTrue(createNetworkResponse.broadcasturi, "pvlan://1234-i567")
|
||||||
|
|
||||||
|
self.debug("Clean up test pvlan network")
|
||||||
|
deleteNetworkCmd = deleteNetwork.deleteNetworkCmd()
|
||||||
|
deleteNetworkCmd.id = self.networkId;
|
||||||
|
self.apiClient.deleteNetwork(deleteNetworkCmd)
|
||||||
|
|
||||||
|
#Test invalid parameter
|
||||||
|
|
||||||
|
# CLOUDSTACK-2392: Should not allow create pvlan with ipv6
|
||||||
|
createNetworkCmd.ip6gateway="fc00:1234::1"
|
||||||
|
createNetworkCmd.ip6cidr="fc00:1234::/64"
|
||||||
|
createNetworkCmd.startipv6="fc00:1234::10"
|
||||||
|
createNetworkCmd.endipv6="fc00:1234::20"
|
||||||
|
err = 0;
|
||||||
|
try:
|
||||||
|
createNetworkResponse = self.apiClient.createNetwork(createNetworkCmd)
|
||||||
|
except Exception as e:
|
||||||
|
err = 1;
|
||||||
|
self.debug("Try alloc with ipv6, got:%s" % e)
|
||||||
|
self.assertEqual(err, 1, "Shouldn't allow create PVLAN network with IPv6");
|
||||||
|
|
||||||
|
|
||||||
@ -1300,6 +1300,7 @@
|
|||||||
name: { label: 'label.name' },
|
name: { label: 'label.name' },
|
||||||
type: { label: 'label.type' },
|
type: { label: 'label.type' },
|
||||||
vlan: { label: 'label.vlan.id' },
|
vlan: { label: 'label.vlan.id' },
|
||||||
|
broadcasturi: { label: 'broadcast URI' },
|
||||||
cidr: { label: 'IPv4 CIDR' },
|
cidr: { label: 'IPv4 CIDR' },
|
||||||
ip6cidr: { label: 'IPv6 CIDR'}
|
ip6cidr: { label: 'IPv6 CIDR'}
|
||||||
//scope: { label: 'label.scope' }
|
//scope: { label: 'label.scope' }
|
||||||
@ -1335,6 +1336,9 @@
|
|||||||
label: 'label.vlan.id',
|
label: 'label.vlan.id',
|
||||||
docID: 'helpGuestNetworkZoneVLANID'
|
docID: 'helpGuestNetworkZoneVLANID'
|
||||||
},
|
},
|
||||||
|
isolatedpvlanId: {
|
||||||
|
label: 'Private VLAN ID'
|
||||||
|
},
|
||||||
|
|
||||||
scope: {
|
scope: {
|
||||||
label: 'label.scope',
|
label: 'label.scope',
|
||||||
@ -1550,10 +1554,14 @@
|
|||||||
if(this.specifyvlan == false) {
|
if(this.specifyvlan == false) {
|
||||||
$form.find('.form-item[rel=vlanId]').hide();
|
$form.find('.form-item[rel=vlanId]').hide();
|
||||||
cloudStack.dialog.createFormField.validation.required.remove($form.find('.form-item[rel=vlanId]')); //make vlanId optional
|
cloudStack.dialog.createFormField.validation.required.remove($form.find('.form-item[rel=vlanId]')); //make vlanId optional
|
||||||
|
|
||||||
|
$form.find('.form-item[rel=isolatedpvlanId]').hide();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$form.find('.form-item[rel=vlanId]').css('display', 'inline-block');
|
$form.find('.form-item[rel=vlanId]').css('display', 'inline-block');
|
||||||
cloudStack.dialog.createFormField.validation.required.add($form.find('.form-item[rel=vlanId]')); //make vlanId required
|
cloudStack.dialog.createFormField.validation.required.add($form.find('.form-item[rel=vlanId]')); //make vlanId required
|
||||||
|
|
||||||
|
$form.find('.form-item[rel=isolatedpvlanId]').css('display', 'inline-block');
|
||||||
}
|
}
|
||||||
return false; //break each loop
|
return false; //break each loop
|
||||||
}
|
}
|
||||||
@ -1640,6 +1648,9 @@
|
|||||||
if(($form.find('.form-item[rel=vlanId]').css("display") != "none") && (args.data.vlanId != null && args.data.vlanId.length > 0))
|
if(($form.find('.form-item[rel=vlanId]').css("display") != "none") && (args.data.vlanId != null && args.data.vlanId.length > 0))
|
||||||
array1.push("&vlan=" + todb(args.data.vlanId));
|
array1.push("&vlan=" + todb(args.data.vlanId));
|
||||||
|
|
||||||
|
if(($form.find('.form-item[rel=isolatedpvlanId]').css("display") != "none") && (args.data.isolatedpvlanId != null && args.data.isolatedpvlanId.length > 0))
|
||||||
|
array1.push("&isolatedpvlan=" + todb(args.data.isolatedpvlanId));
|
||||||
|
|
||||||
if($form.find('.form-item[rel=domainId]').css("display") != "none") {
|
if($form.find('.form-item[rel=domainId]').css("display") != "none") {
|
||||||
array1.push("&domainId=" + args.data.domainId);
|
array1.push("&domainId=" + args.data.domainId);
|
||||||
|
|
||||||
@ -2007,6 +2018,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
vlan: { label: 'label.vlan.id' },
|
vlan: { label: 'label.vlan.id' },
|
||||||
|
broadcasturi: { label: 'broadcast URI' },
|
||||||
scope: { label: 'label.scope' },
|
scope: { label: 'label.scope' },
|
||||||
networkofferingdisplaytext: { label: 'label.network.offering' },
|
networkofferingdisplaytext: { label: 'label.network.offering' },
|
||||||
networkofferingid: {
|
networkofferingid: {
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import java.net.InetAddress;
|
|||||||
import java.net.InterfaceAddress;
|
import java.net.InterfaceAddress;
|
||||||
import java.net.NetworkInterface;
|
import java.net.NetworkInterface;
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
|
import java.net.URI;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Formatter;
|
import java.util.Formatter;
|
||||||
@ -1294,4 +1295,29 @@ public class NetUtils {
|
|||||||
}
|
}
|
||||||
return resultIp;
|
return resultIp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static URI generateUriForPvlan(String primaryVlan, String isolatedPvlan) {
|
||||||
|
return URI.create("pvlan://" + primaryVlan + "-i" + isolatedPvlan);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getPrimaryPvlanFromUri(URI uri) {
|
||||||
|
String[] vlans = uri.getHost().split("-");
|
||||||
|
if (vlans.length < 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return vlans[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getIsolatedPvlanFromUri(URI uri) {
|
||||||
|
String[] vlans = uri.getHost().split("-");
|
||||||
|
if (vlans.length < 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (String vlan : vlans) {
|
||||||
|
if (vlan.startsWith("i")) {
|
||||||
|
return vlan.replace("i", " ").trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,7 @@
|
|||||||
package com.cloud.utils.net;
|
package com.cloud.utils.net;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
import java.net.URI;
|
||||||
import java.util.SortedSet;
|
import java.util.SortedSet;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
|
|
||||||
@ -128,4 +129,11 @@ public class NetUtilsTest extends TestCase {
|
|||||||
assertFalse(NetUtils.isIp6InRange("1234:5678:abcd::1", null));
|
assertFalse(NetUtils.isIp6InRange("1234:5678:abcd::1", null));
|
||||||
assertTrue(NetUtils.isIp6InRange("1234:5678:abcd::1", "1234:5678::1-1234:5679::1"));
|
assertTrue(NetUtils.isIp6InRange("1234:5678:abcd::1", "1234:5678::1-1234:5679::1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testPvlan() {
|
||||||
|
URI uri = NetUtils.generateUriForPvlan("123", "456");
|
||||||
|
assertTrue(uri.toString().equals("pvlan://123-i456"));
|
||||||
|
assertTrue(NetUtils.getPrimaryPvlanFromUri(uri).equals("123"));
|
||||||
|
assertTrue(NetUtils.getIsolatedPvlanFromUri(uri).equals("456"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user