Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion api/src/main/java/com/cloud/network/Network.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
public interface Network extends ControlledEntity, StateObject<Network.State>, InternalIdentity, Identity, Serializable, Displayable {

enum GuestType {
Shared, Isolated, L2;
Shared, Isolated, L2, L3;

public static GuestType fromValue(String type) {
if (StringUtils.isBlank(type)) {
Expand All @@ -54,6 +54,8 @@ public static GuestType fromValue(String type) {
return Isolated;
} else if (type.equalsIgnoreCase("L2")) {
return L2;
} else if (type.equalsIgnoreCase("L3")) {
return L3;
} else {
throw new InvalidParameterValueException("Unexpected Guest type : " + type);
}
Expand Down
44 changes: 44 additions & 0 deletions api/src/main/java/com/cloud/network/Networks.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import java.net.URI;
import java.net.URISyntaxException;
import java.util.regex.Pattern;

import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.commons.lang3.StringUtils;
Expand Down Expand Up @@ -131,6 +132,23 @@ public <T> URI toUri(T value) {
}
}
},
/**
* Direct Routed (L3) networks: the id is a label naming the per-network bridge on the
* hypervisor (brdr-&lt;id&gt;), not an encapsulation — nothing appears on the wire.
*/
Routed("routed", Long.class) {
@Override
public <T> URI toUri(T value) {
try {
if (value.toString().contains("://"))
return new URI(value.toString());
else
return new URI("routed://" + value.toString());
} catch (URISyntaxException e) {
throw new CloudRuntimeException("Unable to convert to broadcast URI: " + value);
}
}
},
UnDecided(null, null),
OpenDaylight("opendaylight", String.class),
TUNGSTEN("tf", String.class),
Expand Down Expand Up @@ -161,6 +179,32 @@ public Class<?> type() {
return type;
}

/**
* A routed id — the value of a routed://&lt;id&gt; broadcast domain — names a bridge on
* every hypervisor (brdr-&lt;id&gt;, at most 15 characters) and derives that bridge's MAC
* address from five bytes, so it is a positive integer of at most ten digits without
* leading zeros.
*/
public static final int ROUTED_ID_MAX_DIGITS = 10;
private static final Pattern ROUTED_ID_PATTERN = Pattern.compile("^[1-9][0-9]{0," + (ROUTED_ID_MAX_DIGITS - 1) + "}$");

/**
* Extracts the routed id from a bare number or from a routed://&lt;id&gt; URI string.
*
* @return the bare id, or null when the candidate is not a valid routed id
*/
public static String getRoutedId(String candidate) {
if (StringUtils.isBlank(candidate)) {
return null;
}
String id = candidate.trim();
String prefix = Routed.scheme() + "://";
if (id.startsWith(prefix)) {
id = id.substring(prefix.length());
}
return ROUTED_ID_PATTERN.matcher(id).matches() ? id : null;
}

/**
* The default implementation of toUri returns an uri with the scheme and value as host
*
Expand Down
1 change: 1 addition & 0 deletions api/src/main/java/com/cloud/offering/NetworkOffering.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ enum RoutingMode {
public final static String DefaultL2NetworkOfferingVlan = "DefaultL2NetworkOfferingVlan";
public final static String DefaultL2NetworkOfferingConfigDrive = "DefaultL2NetworkOfferingConfigDrive";
public final static String DefaultL2NetworkOfferingConfigDriveVlan = "DefaultL2NetworkOfferingConfigDriveVlan";
public final static String DefaultL3NetworkOffering = "DefaultL3NetworkOffering";

/**
* @return name for the network offering.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ public class CreateNetworkCmd extends BaseCmd implements UserCmd {
+ "for shared networks and isolated networks when it belongs to VPC")
private String netmask;

@Parameter(name = ApiConstants.CIDR, type = CommandType.STRING, since = "24.0.0",
description = "The IPv4 subnet of the Network in CIDR notation, e.g. 192.0.2.0/24. Supported for L3 (Direct Routed) "
+ "networks only, as an alternative to netmask: the IP range defaults to the subnet's usable addresses, "
+ "and startip/endip may narrow it")
private String cidr;

@Parameter(name = ApiConstants.START_IP, type = CommandType.STRING, description = "The beginning IP address in the Network IP range")
private String startIp;

Expand Down Expand Up @@ -231,6 +237,10 @@ public String getNetmask() {
return netmask;
}

public String getCidr() {
return cidr;
}

public String getStartIp() {
return startIp;
}
Expand Down Expand Up @@ -340,10 +350,10 @@ public Long getPhysicalNetworkId() {
}
}
if (physicalNetworkId != null) {
if ((offering.getGuestType() == GuestType.Shared) || (offering.getGuestType() == GuestType.L2)) {
if ((offering.getGuestType() == GuestType.Shared) || (offering.getGuestType() == GuestType.L2) || (offering.getGuestType() == GuestType.L3)) {
return physicalNetworkId;
} else {
throw new InvalidParameterValueException("Physical network ID can be specified for networks of guest IP type " + GuestType.Shared + " or " + GuestType.L2 + " only.");
throw new InvalidParameterValueException(String.format("Physical network ID can be specified for networks of guest IP type %s, %s or %s only.", GuestType.Shared, GuestType.L2, GuestType.L3));
}
} else {
if (zoneId == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public class ListNetworkOfferingsCmd extends BaseListCmd {
description = "The ID of the network. Pass this in if you want to see the available network offering that a network can be changed to.")
private Long networkId;

@Parameter(name = ApiConstants.GUEST_IP_TYPE, type = CommandType.STRING, description = "List network offerings by guest type: shared or isolated")
@Parameter(name = ApiConstants.GUEST_IP_TYPE, type = CommandType.STRING, description = "List network offerings by guest type: Shared, Isolated, L2 or L3")
private String guestIpType;

@Parameter(name = ApiConstants.SUPPORTED_SERVICES,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,16 @@ public NetworkType getNetworkType() {
}


/**
* A Direct Routed (L3) network needs the agent told when a secondary IP goes away, so the
* host route and neighbour entry are removed - otherwise the host keeps routing an address
* the Instance no longer owns, and the routing daemon keeps advertising it.
*/
private boolean isDirectRoutedNetwork() {
Network ntwk = _entityMgr.findById(Network.class, getNetworkId());
return ntwk != null && Network.GuestType.L3.equals(ntwk.getGuestType());
}

private boolean isZoneSGEnabled() {
Network ntwk = _entityMgr.findById(Network.class, getNetworkId());
DataCenter dc = _entityMgr.findById(DataCenter.class, ntwk.getDataCenterId());
Expand All @@ -144,7 +154,7 @@ public void execute() throws InvalidParameterValueException {
secIp = nicSecIp.getIp6Address();
}

if (isZoneSGEnabled()) {
if (isZoneSGEnabled() || isDirectRoutedNetwork()) {
//remove the security group rules for this secondary ip
boolean success = false;
success = _securityGroupService.securityGroupRulesForVmSecIp(nicSecIp.getNicId(), secIp, false);
Expand Down
15 changes: 15 additions & 0 deletions api/src/test/java/com/cloud/network/NetworksTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,21 @@ public void otherTypesTest() throws URISyntaxException {
Assert.assertEquals("id2 should be \"2\"", "2", id);
}

@Test
public void getRoutedIdAcceptsBareAndPrefixedIds() {
Assert.assertEquals("5828", BroadcastDomainType.getRoutedId("5828"));
Assert.assertEquals("5828", BroadcastDomainType.getRoutedId("routed://5828"));
Assert.assertEquals("9999999999", BroadcastDomainType.getRoutedId("9999999999"));
Assert.assertEquals("routed://5828", BroadcastDomainType.Routed.toUri(BroadcastDomainType.getRoutedId("routed://5828")).toString());
}

@Test
public void getRoutedIdRejectsMalformedIds() {
for (String candidate : new String[] {null, "", "0", "0534", "abc", "routed://abc", "routed://5828;x", "vlan://5828", "10000000000", "58 28"}) {
Assert.assertNull("expected " + candidate + " to be rejected", BroadcastDomainType.getRoutedId(candidate));
}
}

@Test
public void invalidTypesTest() throws URISyntaxException {
String uri1 = "https://1";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ public void testGetPhysicalNetworkIdForNonSharedNet() {
try {
cmd.getPhysicalNetworkId();
} catch (Exception e) {
Assert.assertTrue(e.getMessage().startsWith("Physical network ID can be specified for networks of guest IP type Shared or L2 only."));
Assert.assertTrue(e.getMessage().startsWith("Physical network ID can be specified for networks of guest IP type Shared, L2 or L3 only."));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,28 @@ public class NetworkRulesVmSecondaryIpCommand extends Command {
private String vmSecIp;
private String vmMac;
private String action;
private boolean directRouted;
private boolean applySecurityGroupRules = true;

public NetworkRulesVmSecondaryIpCommand(String vmName, VirtualMachine.Type type) {
this.vmName = vmName;
this.type = type;
}

public NetworkRulesVmSecondaryIpCommand(String vmName, String vmMac, String secondaryIp, boolean action, boolean directRouted, boolean applySecurityGroupRules) {
this(vmName, vmMac, secondaryIp, action);
this.directRouted = directRouted;
this.applySecurityGroupRules = applySecurityGroupRules;
}

public boolean isDirectRouted() {
return directRouted;
}

public boolean isApplySecurityGroupRules() {
return applySecurityGroupRules;
}

public NetworkRulesVmSecondaryIpCommand(String vmName, String vmMac, String secondaryIp, boolean action) {
this.vmName = vmName;
this.vmMac = vmMac;
Expand Down
Loading
Loading