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
10 changes: 10 additions & 0 deletions api/src/main/java/com/cloud/network/Network.java
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,16 @@ public void setIp6Address(String ip6Address) {

void setCidr(String cidr);

// For an isolated guest network, an optional custom DHCP range within the CIDR that guest IPs
// are allocated from; null means the whole CIDR is used, which is the default.
String getDhcpStartIp();

void setDhcpStartIp(String dhcpStartIp);

String getDhcpEndIp();

void setDhcpEndIp(String dhcpEndIp);

// "networkcidr" is the network CIDR of the guest network which uses IP reservation.
// It is the summation of "cidr" and the reservedIPrange(the address space used for non CloudStack purposes).
// For networks not configured with IP reservation, "networkcidr" is always null
Expand Down
24 changes: 24 additions & 0 deletions api/src/main/java/com/cloud/network/NetworkProfile.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ public class NetworkProfile implements Network {
private TrafficType trafficType;
private String gateway;
private String cidr;
private String dhcpStartIp;
private String dhcpEndIp;
private final String networkCidr;
private final String ip6Gateway;
private final String ip6Cidr;
Expand Down Expand Up @@ -79,6 +81,8 @@ public NetworkProfile(Network network) {
gateway = network.getGateway();
cidr = network.getCidr();
networkCidr = network.getNetworkCidr();
dhcpStartIp = network.getDhcpStartIp();
dhcpEndIp = network.getDhcpEndIp();
ip6Gateway = network.getIp6Gateway();
ip6Cidr = network.getIp6Cidr();
networkOfferingId = network.getNetworkOfferingId();
Expand Down Expand Up @@ -228,6 +232,26 @@ public void setCidr(String cidr) {
this.cidr = cidr;
}

@Override
public String getDhcpStartIp() {
return dhcpStartIp;
}

@Override
public void setDhcpStartIp(String dhcpStartIp) {
this.dhcpStartIp = dhcpStartIp;
}

@Override
public String getDhcpEndIp() {
return dhcpEndIp;
}

@Override
public void setDhcpEndIp(String dhcpEndIp) {
this.dhcpEndIp = dhcpEndIp;
}

@Override
public String getNetworkCidr() {
return networkCidr;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ public class NetworkResponse extends BaseResponseWithAssociatedNetwork implement
@Param(description = "CloudStack managed address space, all CloudStack managed Instances get IP address from CIDR")
private String cidr;

@SerializedName(ApiConstants.START_IP)
@Param(description = "the start of the custom DHCP range for an isolated network, within its CIDR", since = "4.24.0")
private String dhcpStartIp;

@SerializedName(ApiConstants.END_IP)
@Param(description = "the end of the custom DHCP range for an isolated network, within its CIDR", since = "4.24.0")
private String dhcpEndIp;

@SerializedName(ApiConstants.NETWORK_CIDR)
@Param(description = "The Network CIDR of the guest Network configured with IP reservation. It is the summation of CIDR and RESERVED_IP_RANGE")
private String networkCidr;
Expand Down Expand Up @@ -511,6 +519,14 @@ public void setCidr(String cidr) {
this.cidr = cidr;
}

public void setDhcpStartIp(String dhcpStartIp) {
this.dhcpStartIp = dhcpStartIp;
}

public void setDhcpEndIp(String dhcpEndIp) {
this.dhcpEndIp = dhcpEndIp;
}

public void setNetworkCidr(String networkCidr) {
this.networkCidr = networkCidr;
}
Expand Down
26 changes: 26 additions & 0 deletions engine/schema/src/main/java/com/cloud/network/dao/NetworkVO.java
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ public class NetworkVO implements Network {
@Column(name = "network_cidr")
String networkCidr;

@Column(name = "dhcp_start_ip")
String dhcpStartIp;

@Column(name = "dhcp_end_ip")
String dhcpEndIp;

@Column(name = "network_offering_id")
long networkOfferingId;

Expand Down Expand Up @@ -470,6 +476,26 @@ public void setCidr(String cidr) {
this.cidr = cidr;
}

@Override
public String getDhcpStartIp() {
return dhcpStartIp;
}

@Override
public void setDhcpStartIp(String dhcpStartIp) {
this.dhcpStartIp = dhcpStartIp;
}

@Override
public String getDhcpEndIp() {
return dhcpEndIp;
}

@Override
public void setDhcpEndIp(String dhcpEndIp) {
this.dhcpEndIp = dhcpEndIp;
}

// "networkcidr" is the network CIDR of the guest network which is configured with IP reservation feature
// It is the summation of "cidr" and the reservedIPrange(the address space used for non cloudstack purposes.)
// For networks not using IP reservation "networkcidr" is always null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@
--;
-- Schema upgrade from 4.23.0.0 to 24.0.0
--;

-- Custom DHCP (guest IP allocation) range for isolated guest networks
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.networks', 'dhcp_start_ip', 'VARCHAR(15) DEFAULT NULL COMMENT ''start of the custom DHCP range for an isolated network, within its CIDR'' ');
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.networks', 'dhcp_end_ip', 'VARCHAR(15) DEFAULT NULL COMMENT ''end of the custom DHCP range for an isolated network, within its CIDR'' ');
2 changes: 2 additions & 0 deletions server/src/main/java/com/cloud/api/ApiResponseHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -2592,6 +2592,8 @@ public NetworkResponse createNetworkResponse(ResponseView view, Network network)

// FIXME - either set netmask or cidr
response.setCidr(cidr);
response.setDhcpStartIp(network.getDhcpStartIp());
response.setDhcpEndIp(network.getDhcpEndIp());
if (network.getNetworkCidr() != null) {
response.setNetworkCidr((network.getNetworkCidr()));
}
Expand Down
6 changes: 6 additions & 0 deletions server/src/main/java/com/cloud/network/NetworkModelImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -2333,6 +2333,12 @@ public Set<Long> getAvailableIps(Network network, String requestedIp) {
if ((gateway != null) && (allPossibleIps.contains(NetUtils.ip2Long(gateway))))
allPossibleIps.remove(NetUtils.ip2Long(gateway));

if (StringUtils.isNoneBlank(network.getDhcpStartIp(), network.getDhcpEndIp())) {
long start = NetUtils.ip2Long(network.getDhcpStartIp());
long end = NetUtils.ip2Long(network.getDhcpEndIp());
allPossibleIps.removeIf(ip -> ip < start || ip > end);
}

return allPossibleIps;
}

Expand Down
27 changes: 27 additions & 0 deletions server/src/main/java/com/cloud/network/NetworkServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -1838,6 +1838,12 @@ public Network createGuestNetwork(CreateNetworkCmd cmd) throws InsufficientCapac
domainId, isDomainSpecific, subdomainAccess, vpcId, startIPv6, endIPv6, ip6Gateway, ip6Cidr, displayNetwork, aclId, secondaryVlanId, privateVlanType, ntwkOff, pNtwk, aclType, owner, cidr, createVlan,
externalId, routerIPv4, routerIPv6, associatedNetwork, ip4Dns1, ip4Dns2, ip6Dns1, ip6Dns2, interfaceMTUs, networkCidrSize, keepMacAddressOnPublicNic);

// For an isolated network a start/end IP defines a custom DHCP range within the CIDR that
// guest IPs are allocated from; without it the whole CIDR is used, which is the default.
if (ntwkOff.getGuestType() == GuestType.Isolated && StringUtils.isNotBlank(startIP)) {
storeIsolatedNetworkDhcpRange(network.getId(), startIP, endIP);
}

// retrieve, acquire and associate the correct IP addresses
checkAndSetRouterSourceNatIp(owner, cmd, network);

Expand Down Expand Up @@ -1872,6 +1878,27 @@ private boolean isNonVpcNetworkSupportingDynamicRouting(NetworkOffering networkO
return !networkOffering.isForVpc() && NetworkOffering.RoutingMode.Dynamic == networkOffering.getRoutingMode();
}

protected void storeIsolatedNetworkDhcpRange(long networkId, String startIP, String endIP) {
NetworkVO network = _networksDao.findById(networkId);
String cidr = network.getCidr();
if (endIP == null) {
endIP = startIP;
}
if (!NetUtils.isIpWithInCidrRange(startIP, cidr) || !NetUtils.isIpWithInCidrRange(endIP, cidr)) {
throw new InvalidParameterValueException(String.format("The DHCP range %s-%s is not within the network CIDR %s", startIP, endIP, cidr));
}
if (NetUtils.ip2Long(startIP) > NetUtils.ip2Long(endIP)) {
throw new InvalidParameterValueException(String.format("The DHCP start IP %s is greater than the end IP %s", startIP, endIP));
}
String gateway = network.getGateway();
if (gateway != null && NetUtils.ip2Long(gateway) >= NetUtils.ip2Long(startIP) && NetUtils.ip2Long(gateway) <= NetUtils.ip2Long(endIP)) {
throw new InvalidParameterValueException(String.format("The DHCP range %s-%s must not include the gateway %s", startIP, endIP, gateway));
}
network.setDhcpStartIp(startIP);
network.setDhcpEndIp(endIP);
_networksDao.update(networkId, network);
}

private void validateNetworkCreationSupported(long zoneId, String zoneName, GuestType guestType) {
NsxProviderVO nsxProviderVO = nsxProviderDao.findByZoneId(zoneId);
if (Objects.nonNull(nsxProviderVO) && GuestType.L2.equals(guestType)) {
Expand Down
40 changes: 40 additions & 0 deletions server/src/test/java/com/cloud/network/NetworkModelImplTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
import com.cloud.offerings.dao.NetworkOfferingServiceMapDao;
import com.cloud.utils.Pair;
import com.cloud.utils.net.Ip;
import com.cloud.utils.net.NetUtils;
import com.cloud.vm.Nic;
import com.cloud.vm.NicProfile;
import com.cloud.vm.VirtualMachine;
Expand Down Expand Up @@ -452,4 +453,43 @@ public void listSupportedNetworkServiceProvidersExcludesExtensionBackedProviders
Mockito.verify(physicalNetworkServiceProviderDao, Mockito.times(1)).listAll();
Mockito.verify(physicalNetworkServiceProviderDao, Mockito.never()).listBy(Mockito.anyLong());
}

private NetworkVO networkWithDhcpRange(String cidr, String gateway, String dhcpStart, String dhcpEnd) {
NetworkVO network = mock(NetworkVO.class);
when(network.getCidr()).thenReturn(cidr);
when(network.getGateway()).thenReturn(gateway);
when(network.getDhcpStartIp()).thenReturn(dhcpStart);
when(network.getDhcpEndIp()).thenReturn(dhcpEnd);
Mockito.doReturn(new ArrayList<String>()).when(networkModel).getUsedIpsInNetwork(network);
return network;
}

@Test
public void getAvailableIpsRestrictsToTheDhcpRangeWhenSet() {
NetworkVO network = networkWithDhcpRange("10.1.1.0/24", "10.1.1.1", "10.1.1.10", "10.1.1.20");

Set<Long> ips = networkModel.getAvailableIps(network, null);

long start = NetUtils.ip2Long("10.1.1.10");
long end = NetUtils.ip2Long("10.1.1.20");
for (Long ip : ips) {
assertTrue(ip >= start && ip <= end);
}
assertEquals(11, ips.size());
assertTrue(ips.contains(NetUtils.ip2Long("10.1.1.10")));
assertTrue(ips.contains(NetUtils.ip2Long("10.1.1.20")));
assertFalse(ips.contains(NetUtils.ip2Long("10.1.1.9")));
assertFalse(ips.contains(NetUtils.ip2Long("10.1.1.21")));
}

@Test
public void getAvailableIpsUsesTheWholeCidrWhenNoDhcpRangeIsSet() {
NetworkVO network = networkWithDhcpRange("10.1.1.0/24", "10.1.1.1", null, null);

Set<Long> ips = networkModel.getAvailableIps(network, null);

assertTrue(ips.contains(NetUtils.ip2Long("10.1.1.10")));
assertTrue(ips.contains(NetUtils.ip2Long("10.1.1.250")));
assertFalse(ips.contains(NetUtils.ip2Long("10.1.1.1")));
}
}
47 changes: 47 additions & 0 deletions server/src/test/java/com/cloud/network/NetworkServiceImplTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -1378,4 +1378,51 @@ public void getAndValidateSupportForKeepMacAddressOnPublicNicParameterTestReturn

Assert.assertFalse(service.getAndValidateSupportForKeepMacAddressOnPublicNicParameter(false, networkOfferingVO));
}

private NetworkVO isolatedNetworkForDhcpRange() {
NetworkVO network = Mockito.mock(NetworkVO.class);
Mockito.when(network.getCidr()).thenReturn("10.1.1.0/24");
Mockito.when(network.getGateway()).thenReturn("10.1.1.1");
Mockito.when(networkDao.findById(5L)).thenReturn(network);
return network;
}

@Test
public void storeIsolatedNetworkDhcpRangeStoresAValidRange() {
NetworkVO network = isolatedNetworkForDhcpRange();

service.storeIsolatedNetworkDhcpRange(5L, "10.1.1.10", "10.1.1.20");

Mockito.verify(network).setDhcpStartIp("10.1.1.10");
Mockito.verify(network).setDhcpEndIp("10.1.1.20");
Mockito.verify(networkDao).update(5L, network);
}

@Test
public void storeIsolatedNetworkDhcpRangeDefaultsEndToStart() {
NetworkVO network = isolatedNetworkForDhcpRange();

service.storeIsolatedNetworkDhcpRange(5L, "10.1.1.10", null);

Mockito.verify(network).setDhcpStartIp("10.1.1.10");
Mockito.verify(network).setDhcpEndIp("10.1.1.10");
}

@Test(expected = InvalidParameterValueException.class)
public void storeIsolatedNetworkDhcpRangeRejectsRangeOutsideCidr() {
isolatedNetworkForDhcpRange();
service.storeIsolatedNetworkDhcpRange(5L, "10.2.2.10", "10.2.2.20");
}

@Test(expected = InvalidParameterValueException.class)
public void storeIsolatedNetworkDhcpRangeRejectsStartGreaterThanEnd() {
isolatedNetworkForDhcpRange();
service.storeIsolatedNetworkDhcpRange(5L, "10.1.1.20", "10.1.1.10");
}

@Test(expected = InvalidParameterValueException.class)
public void storeIsolatedNetworkDhcpRangeRejectsRangeIncludingGateway() {
isolatedNetworkForDhcpRange();
service.storeIsolatedNetworkDhcpRange(5L, "10.1.1.1", "10.1.1.20");
}
}
Loading