diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 94ebf10d456..731b514a542 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -80,7 +80,8 @@ jobs: smoke/test_metrics_api smoke/test_migration smoke/test_multipleips_per_nic - smoke/test_nested_virtualization", + smoke/test_nested_virtualization + smoke/test_set_sourcenat", "smoke/test_network smoke/test_network_acl smoke/test_network_ipv6 diff --git a/api/src/main/java/com/cloud/network/Network.java b/api/src/main/java/com/cloud/network/Network.java index a2633ec7bac..458169c80aa 100644 --- a/api/src/main/java/com/cloud/network/Network.java +++ b/api/src/main/java/com/cloud/network/Network.java @@ -256,7 +256,7 @@ public interface Network extends ControlledEntity, StateObject, I public static class Capability { - private static List supportedCapabilities = new ArrayList(); + private static List supportedCapabilities = new ArrayList<>(); public static final Capability SupportedProtocols = new Capability("SupportedProtocols"); public static final Capability SupportedLBAlgorithms = new Capability("SupportedLbAlgorithms"); diff --git a/api/src/main/java/com/cloud/network/vpc/VpcService.java b/api/src/main/java/com/cloud/network/vpc/VpcService.java index 362a1101c7b..2cdc034a16e 100644 --- a/api/src/main/java/com/cloud/network/vpc/VpcService.java +++ b/api/src/main/java/com/cloud/network/vpc/VpcService.java @@ -23,7 +23,9 @@ import org.apache.cloudstack.api.command.user.vpc.CreatePrivateGatewayCmd; import org.apache.cloudstack.api.command.user.vpc.CreateVPCCmd; import org.apache.cloudstack.api.command.user.vpc.ListPrivateGatewaysCmd; import org.apache.cloudstack.api.command.user.vpc.ListStaticRoutesCmd; +import org.apache.cloudstack.api.command.user.vpc.ListVPCsCmd; import org.apache.cloudstack.api.command.user.vpc.RestartVPCCmd; +import org.apache.cloudstack.api.command.user.vpc.UpdateVPCCmd; import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.InsufficientAddressCapacityException; @@ -37,7 +39,6 @@ import com.cloud.utils.Pair; public interface VpcService { - public Vpc createVpc(CreateVPCCmd cmd) throws ResourceAllocationException; /** * Persists VPC record in the database * @@ -48,14 +49,25 @@ public interface VpcService { * @param displayText * @param cidr * @param networkDomain TODO + * @param ip4Dns1 + * @param ip4Dns2 * @param displayVpc TODO * @return * @throws ResourceAllocationException TODO */ - public Vpc createVpc(long zoneId, long vpcOffId, long vpcOwnerId, String vpcName, String displayText, String cidr, String networkDomain, - String dns1, String dns2, String ip6Dns1, String ip6Dns2, Boolean displayVpc, Integer publicMtu) + Vpc createVpc(long zoneId, long vpcOffId, long vpcOwnerId, String vpcName, String displayText, String cidr, String networkDomain, + String ip4Dns1, String ip4Dns2, String ip6Dns1, String ip6Dns2, Boolean displayVpc, Integer publicMtu) throws ResourceAllocationException; + /** + * Persists VPC record in the database + * + * @param cmd the command with specification data for the new vpc + * @return a data object describing the new vpc + * @throws ResourceAllocationException the resources for this VPC cannot be allocated + */ + Vpc createVpc(CreateVPCCmd cmd) throws ResourceAllocationException; + /** * Deletes a VPC * @@ -65,48 +77,48 @@ public interface VpcService { * @throws ResourceUnavailableException * @throws ConcurrentOperationException */ - public boolean deleteVpc(long vpcId) throws ConcurrentOperationException, ResourceUnavailableException; + boolean deleteVpc(long vpcId) throws ConcurrentOperationException, ResourceUnavailableException; + + /** + * Persists VPC record in the database + * + * @param cmd the command with specification data for updating the vpc + * @return a data object describing the new vpc state + * @throws ResourceUnavailableException if during restart some resources may not be available + * @throws InsufficientCapacityException if for instance no address space, compute or storage is sufficiently available + */ + Vpc updateVpc(UpdateVPCCmd cmd) throws ResourceUnavailableException, InsufficientCapacityException; /** * Updates VPC with new name/displayText * - * @param vpcId - * @param vpcName - * @param displayText - * @param customId TODO - * @param displayVpc TODO - * @param mtu - * @return + * @param vpcId the ID of the Vpc to update + * @param vpcName The new name to give the vpc + * @param displayText the new display text to use for describing the VPC + * @param customId A new custom (external) ID to associate this VPC with + * @param displayVpc should this VPC be displayed on public lists + * @param mtu what maximal transfer unit to us in this VPCs networks + * @param sourceNatIp the source NAT address to use for this VPC (must already be associated with the VPC) + * @return an object describing the current state of the VPC + * @throws ResourceUnavailableException if during restart some resources may not be available + * @throws InsufficientCapacityException if for instance no address space, compute or storage is sufficiently available */ - public Vpc updateVpc(long vpcId, String vpcName, String displayText, String customId, Boolean displayVpc, Integer mtu); + Vpc updateVpc(long vpcId, String vpcName, String displayText, String customId, Boolean displayVpc, Integer mtu, String sourceNatIp) throws ResourceUnavailableException, InsufficientCapacityException; + + /** + * Lists VPC(s) based on the parameters passed to the API call + * + * @param cmd object containing the search specs + * @return the List of VPCs + */ + Pair, Integer> listVpcs(ListVPCsCmd cmd); /** * Lists VPC(s) based on the parameters passed to the method call - * - * @param id - * @param vpcName - * @param displayText - * @param supportedServicesStr - * @param cidr - * @param state TODO - * @param accountName - * @param domainId - * @param keyword - * @param startIndex - * @param pageSizeVal - * @param zoneId TODO - * @param isRecursive TODO - * @param listAll TODO - * @param restartRequired TODO - * @param tags TODO - * @param projectId TODO - * @param display TODO - * @param vpc - * @return */ - public Pair, Integer> listVpcs(Long id, String vpcName, String displayText, List supportedServicesStr, String cidr, Long vpcOffId, String state, - String accountName, Long domainId, String keyword, Long startIndex, Long pageSizeVal, Long zoneId, Boolean isRecursive, Boolean listAll, Boolean restartRequired, - Map tags, Long projectId, Boolean display); + Pair, Integer> listVpcs(Long id, String vpcName, String displayText, List supportedServicesStr, String cidr, Long vpcOffId, String state, + String accountName, Long domainId, String keyword, Long startIndex, Long pageSizeVal, Long zoneId, Boolean isRecursive, Boolean listAll, Boolean restartRequired, + Map tags, Long projectId, Boolean display); /** * Starts VPC which includes starting VPC provider and applying all the networking rules on the backend @@ -130,17 +142,17 @@ public interface VpcService { */ boolean shutdownVpc(long vpcId) throws ConcurrentOperationException, ResourceUnavailableException; + boolean restartVpc(RestartVPCCmd cmd) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException; + /** * Restarts the VPC. VPC gets shutdown and started as a part of it * - * @param id - * @param cleanUp - * @param makeredundant - * @return - * @throws InsufficientCapacityException + * @param networkId the network to restart + * @param cleanup throw away the existing VR and rebuild a new one? + * @param makeRedundant create two VRs for this network + * @return success or not + * @throws InsufficientCapacityException when there is no suitable deployment plan possible */ - boolean restartVpc(RestartVPCCmd cmd) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException; - boolean restartVpc(Long networkId, boolean cleanup, boolean makeRedundant, boolean livePatch, User user) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException; /** @@ -154,23 +166,12 @@ public interface VpcService { /** * Persists VPC private gateway in the Database. * - * - * @param vpcId TODO - * @param physicalNetworkId - * @param vlan - * @param ipAddress - * @param gateway - * @param netmask - * @param gatewayOwnerId - * @param networkOfferingId - * @param isSourceNat - * @param aclId - * @return + * @return data object describing the private gateway * @throws InsufficientCapacityException * @throws ConcurrentOperationException * @throws ResourceAllocationException */ - public PrivateGateway createVpcPrivateGateway(CreatePrivateGatewayCmd command) throws ResourceAllocationException, ConcurrentOperationException, InsufficientCapacityException; + PrivateGateway createVpcPrivateGateway(CreatePrivateGatewayCmd command) throws ResourceAllocationException, ConcurrentOperationException, InsufficientCapacityException; /** * Applies VPC private gateway on the backend, so it becomes functional @@ -181,12 +182,12 @@ public interface VpcService { * @throws ResourceUnavailableException * @throws ConcurrentOperationException */ - public PrivateGateway applyVpcPrivateGateway(long gatewayId, boolean destroyOnFailure) throws ConcurrentOperationException, ResourceUnavailableException; + PrivateGateway applyVpcPrivateGateway(long gatewayId, boolean destroyOnFailure) throws ConcurrentOperationException, ResourceUnavailableException; /** * Deletes VPC private gateway * - * @param id + * @param gatewayId * @return * @throws ResourceUnavailableException * @throws ConcurrentOperationException @@ -199,7 +200,7 @@ public interface VpcService { * @param listPrivateGatewaysCmd * @return */ - public Pair, Integer> listPrivateGateway(ListPrivateGatewaysCmd listPrivateGatewaysCmd); + Pair, Integer> listPrivateGateway(ListPrivateGatewaysCmd listPrivateGatewaysCmd); /** * Returns Static Route found by Id @@ -216,7 +217,7 @@ public interface VpcService { * @return * @throws ResourceUnavailableException */ - public boolean applyStaticRoutesForVpc(long vpcId) throws ResourceUnavailableException; + boolean applyStaticRoutesForVpc(long vpcId) throws ResourceUnavailableException; /** * Deletes static route from the backend and the database @@ -225,7 +226,7 @@ public interface VpcService { * @return TODO * @throws ResourceUnavailableException */ - public boolean revokeStaticRoute(long routeId) throws ResourceUnavailableException; + boolean revokeStaticRoute(long routeId) throws ResourceUnavailableException; /** * Persists static route entry in the Database @@ -234,15 +235,15 @@ public interface VpcService { * @param cidr * @return */ - public StaticRoute createStaticRoute(long gatewayId, String cidr) throws NetworkRuleConflictException; + StaticRoute createStaticRoute(long gatewayId, String cidr) throws NetworkRuleConflictException; /** * Lists static routes based on parameters passed to the call * - * @param listStaticRoutesCmd + * @param cmd Command object with parameters for { @see ListStaticRoutesCmd } * @return */ - public Pair, Integer> listStaticRoutes(ListStaticRoutesCmd cmd); + Pair, Integer> listStaticRoutes(ListStaticRoutesCmd cmd); /** * Associates IP address from the Public network, to the VPC @@ -262,6 +263,5 @@ public interface VpcService { * @param routeId * @return */ - public boolean applyStaticRoute(long routeId) throws ResourceUnavailableException; - + boolean applyStaticRoute(long routeId) throws ResourceUnavailableException; } diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index 9dc4e587cfa..acf9f4dae3b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -1032,6 +1032,10 @@ public class ApiConstants { public static final String AUTO_ENABLE_KVM_HOST = "autoenablekvmhost"; public static final String LIST_APIS = "listApis"; + public static final String SOURCE_NAT_IP = "sourcenatipaddress"; + public static final String SOURCE_NAT_IP_ID = "sourcenatipaddressid"; + public static final String HAS_RULES = "hasrules"; + /** * This enum specifies IO Drivers, each option controls specific policies on I/O. * Qemu guests support "threads" and "native" options Since 0.8.8 ; "io_uring" is supported Since 6.3.0 (QEMU 5.0). diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/address/ListPublicIpAddressesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/address/ListPublicIpAddressesCmd.java index e456074145d..505de6829a0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/address/ListPublicIpAddressesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/address/ListPublicIpAddressesCmd.java @@ -192,8 +192,8 @@ public class ListPublicIpAddressesCmd extends BaseListTaggedResourcesCmd impleme @Override public void execute() { Pair, Integer> result = _mgr.searchForIPAddresses(this); - ListResponse response = new ListResponse(); - List ipAddrResponses = new ArrayList(); + ListResponse response = new ListResponse<>(); + List ipAddrResponses = new ArrayList<>(); for (IpAddress ipAddress : result.first()) { IPAddressResponse ipResponse = _responseGenerator.createIPAddressResponse(getResponseView(), ipAddress); ipResponse.setObjectName("publicipaddress"); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/network/CreateNetworkCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/network/CreateNetworkCmd.java index 8b8ce104076..e74debb5454 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/network/CreateNetworkCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/network/CreateNetworkCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.user.network; -import com.cloud.network.NetworkService; +import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import org.apache.cloudstack.acl.RoleType; @@ -43,10 +43,10 @@ import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.ResourceAllocationException; import com.cloud.network.Network; +import com.cloud.network.NetworkService; import com.cloud.network.Network.GuestType; import com.cloud.offering.NetworkOffering; import com.cloud.utils.net.NetUtils; -import org.apache.commons.lang3.StringUtils; @APICommand(name = "createNetwork", description = "Creates a network", responseObject = NetworkResponse.class, responseView = ResponseView.Restricted, entityType = {Network.class}, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) @@ -183,6 +183,14 @@ public class CreateNetworkCmd extends BaseCmd implements UserCmd { @Parameter(name = ApiConstants.IP6_DNS2, type = CommandType.STRING, description = "the second IPv6 DNS for the network", since = "4.18.0") private String ip6Dns2; + @Parameter(name = ApiConstants.SOURCE_NAT_IP, + type = CommandType.STRING, + description = "IPV4 address to be assigned to the public interface of the network router. " + + "This address will be used as source NAT address for the network. " + + "\nIf an address is given and it cannot be acquired, an error will be returned and the network won´t be implemented,", + since = "4.19") + private String sourceNatIP; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -266,6 +274,10 @@ public class CreateNetworkCmd extends BaseCmd implements UserCmd { return tungstenVirtualRouterUuid; } + public String getSourceNatIP() { + return sourceNatIP; + } + @Override public boolean isDisplay() { if(displayNetwork == null) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/network/UpdateNetworkCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/network/UpdateNetworkCmd.java index 3aef9730c1c..d3cc169b7da 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/network/UpdateNetworkCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/network/UpdateNetworkCmd.java @@ -104,6 +104,9 @@ public class UpdateNetworkCmd extends BaseAsyncCustomIdCmd implements UserCmd { @Parameter(name = ApiConstants.IP6_DNS2, type = CommandType.STRING, description = "the second IPv6 DNS for the network. Empty string will update the second IPv6 DNS with the value from the zone", since = "4.18.0") private String ip6Dns2; + @Parameter(name = ApiConstants.SOURCE_NAT_IP, type = CommandType.STRING, description = "IPV4 address to be assigned to the public interface of the network router. This address must already be acquired for this network", since = "4.19") + private String sourceNatIP; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -181,6 +184,10 @@ public class UpdateNetworkCmd extends BaseAsyncCustomIdCmd implements UserCmd { return ip6Dns2; } + public String getSourceNatIP() { + return sourceNatIP; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreateVPCCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreateVPCCmd.java index 40048a2cd6c..7ca66b2a471 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreateVPCCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreateVPCCmd.java @@ -16,7 +16,6 @@ // under the License. package org.apache.cloudstack.api.command.user.vpc; -import com.cloud.network.NetworkService; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; @@ -41,6 +40,7 @@ import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.NetworkService; import com.cloud.network.vpc.Vpc; @APICommand(name = "createVPC", description = "Creates a VPC", responseObject = VpcResponse.class, responseView = ResponseView.Restricted, entityType = {Vpc.class}, @@ -113,6 +113,12 @@ public class CreateVPCCmd extends BaseAsyncCreateCmd implements UserCmd { @Parameter(name = ApiConstants.IP6_DNS2, type = CommandType.STRING, description = "the second IPv6 DNS for the VPC", since = "4.18.0") private String ip6Dns2; + @Parameter(name = ApiConstants.SOURCE_NAT_IP, type = CommandType.STRING, description = "IPV4 address to be assigned to the public interface of the network router." + + "This address will be used as source NAT address for the networks in ths VPC. " + + "\nIf an address is given and it cannot be acquired, an error will be returned and the network won´t be implemented,", + since = "4.19") + private String sourceNatIP; + // /////////////////////////////////////////////////// // ///////////////// Accessors /////////////////////// // /////////////////////////////////////////////////// @@ -180,6 +186,15 @@ public class CreateVPCCmd extends BaseAsyncCreateCmd implements UserCmd { return display; } + + public String getSourceNatIP() { + return sourceNatIP; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + @Override public void create() throws ResourceAllocationException { Vpc vpc = _vpcService.createVpc(this); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/ListVPCsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/ListVPCsCmd.java index b230603f852..76cbcca61bb 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/ListVPCsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/ListVPCsCmd.java @@ -142,9 +142,7 @@ public class ListVPCsCmd extends BaseListTaggedResourcesCmd implements UserCmd { @Override public void execute() { Pair, Integer> vpcs = - _vpcService.listVpcs(getId(), getVpcName(), getDisplayText(), getSupportedServices(), getCidr(), getVpcOffId(), getState(), getAccountName(), getDomainId(), - getKeyword(), getStartIndex(), getPageSizeVal(), getZoneId(), isRecursive(), listAll(), getRestartRequired(), getTags(), - getProjectId(), getDisplay()); + _vpcService.listVpcs(this); ListResponse response = new ListResponse(); List vpcResponses = new ArrayList(); for (Vpc vpc : vpcs.first()) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/UpdateVPCCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/UpdateVPCCmd.java index 4c3408e054e..d4c7d0d5c59 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/UpdateVPCCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/UpdateVPCCmd.java @@ -34,6 +34,8 @@ import org.apache.cloudstack.api.command.user.UserCmd; import org.apache.cloudstack.api.response.VpcResponse; import com.cloud.event.EventTypes; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.ResourceUnavailableException; import com.cloud.network.vpc.Vpc; import com.cloud.user.Account; @@ -63,6 +65,12 @@ public class UpdateVPCCmd extends BaseAsyncCustomIdCmd implements UserCmd { description = "MTU to be configured on the network VR's public facing interfaces", since = "4.18.0") private Integer publicMtu; + @Parameter(name = ApiConstants.SOURCE_NAT_IP, + type = CommandType.STRING, + description = "IPV4 address to be assigned to the public interface of the network router. This address must already be acquired for this VPC", + since = "4.19") + private String sourceNatIP; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -87,6 +95,10 @@ public class UpdateVPCCmd extends BaseAsyncCustomIdCmd implements UserCmd { return publicMtu; } + public String getSourceNatIP() { + return sourceNatIP; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -107,13 +119,22 @@ public class UpdateVPCCmd extends BaseAsyncCustomIdCmd implements UserCmd { @Override public void execute() { - Vpc result = _vpcService.updateVpc(getId(), getVpcName(), getDisplayText(), getCustomId(), isDisplayVpc(), getPublicMtu()); - if (result != null) { - VpcResponse response = _responseGenerator.createVpcResponse(getResponseView(), result); - response.setResponseName(getCommandName()); - setResponseObject(response); - } else { - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update VPC"); + try { + Vpc result = _vpcService.updateVpc(this); + if (result != null) { + VpcResponse response = _responseGenerator.createVpcResponse(getResponseView(), result); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update VPC"); + } + } catch (final ResourceUnavailableException ex) { + s_logger.warn("Exception: ", ex); + throw new ServerApiException(ApiErrorCode.RESOURCE_UNAVAILABLE_ERROR, ex.getMessage()); + } catch (final InsufficientCapacityException ex) { + s_logger.info(ex); + s_logger.trace(ex); + throw new ServerApiException(ApiErrorCode.INSUFFICIENT_CAPACITY_ERROR, ex.getMessage()); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/IPAddressResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/IPAddressResponse.java index 2ad0a4fad0d..7e3adc4ba01 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/IPAddressResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/IPAddressResponse.java @@ -163,10 +163,9 @@ public class IPAddressResponse extends BaseResponseWithAnnotations implements Co @Param(description="the name of the Network where ip belongs to") private String networkName; - /* - @SerializedName(ApiConstants.JOB_ID) @Param(description="shows the current pending asynchronous job ID. This tag is not returned if no current pending jobs are acting on the volume") - private IdentityProxy jobId = new IdentityProxy("async_job"); - */ + @SerializedName(ApiConstants.HAS_RULES) + @Param(description="whether the ip address has Firewall/PortForwarding/LoadBalancing rules defined") + private boolean hasRules; public void setIpAddress(String ipAddress) { this.ipAddress = ipAddress; @@ -313,4 +312,8 @@ public class IPAddressResponse extends BaseResponseWithAnnotations implements Co public void setNetworkName(String networkName) { this.networkName = networkName; } + + public void setHasRules(final boolean hasRules) { + this.hasRules = hasRules; + } } diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/vpc/UpdateVPCCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/vpc/UpdateVPCCmdTest.java index 3f27f4c6da3..d174d3694cd 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/user/vpc/UpdateVPCCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/vpc/UpdateVPCCmdTest.java @@ -17,6 +17,8 @@ package org.apache.cloudstack.api.command.user.vpc; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.ResourceUnavailableException; import com.cloud.network.vpc.Vpc; import com.cloud.network.vpc.VpcService; import junit.framework.TestCase; @@ -72,7 +74,7 @@ public class UpdateVPCCmdTest extends TestCase { Assert.assertEquals(cmd.getPublicMtu(), publicMtu); } - public void testExecute() { + public void testExecute() throws ResourceUnavailableException, InsufficientCapacityException { ReflectionTestUtils.setField(cmd, "id", 1L); ReflectionTestUtils.setField(cmd, "vpcName", "updatedVpcName"); ReflectionTestUtils.setField(cmd, "displayText", "Updated VPC Name"); @@ -85,10 +87,10 @@ public class UpdateVPCCmdTest extends TestCase { responseGenerator = Mockito.mock(ResponseGenerator.class); cmd._responseGenerator = responseGenerator; Mockito.when(_vpcService.updateVpc(Mockito.anyLong(), Mockito.anyString(), Mockito.anyString(), - Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyInt())).thenReturn(vpc); + Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyInt(), Mockito.anyString())).thenReturn(vpc); Mockito.when(responseGenerator.createVpcResponse(ResponseObject.ResponseView.Full, vpc)).thenReturn(response); Mockito.verify(_vpcService, Mockito.times(0)).updateVpc(Mockito.anyLong(), Mockito.anyString(), Mockito.anyString(), - Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyInt()); + Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyInt(), Mockito.anyString()); } } diff --git a/engine/components-api/src/main/java/com/cloud/network/IpAddressManager.java b/engine/components-api/src/main/java/com/cloud/network/IpAddressManager.java index 2fa66d7166b..80d9ab3602f 100644 --- a/engine/components-api/src/main/java/com/cloud/network/IpAddressManager.java +++ b/engine/components-api/src/main/java/com/cloud/network/IpAddressManager.java @@ -233,4 +233,6 @@ public interface IpAddressManager { public static final String MESSAGE_ASSIGN_IPADDR_EVENT = "Message.AssignIpAddr.Event"; public static final String MESSAGE_RELEASE_IPADDR_EVENT = "Message.ReleaseIpAddr.Event"; + + void updateSourceNatIpAddress(IPAddressVO requestedIp, List userIps) throws Exception; } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesDao.java b/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesDao.java index f2e9bcba1e5..21200dbf9b5 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesDao.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesDao.java @@ -53,6 +53,12 @@ public interface FirewallRulesDao extends GenericDao { List listByIpAndNotRevoked(long ipAddressId); + /** + * counts the number of portforwarding rules for an IP address + * + * @param sourceIpId the id of the IP record + * @return the number of portforwarding rules for this IP + */ long countRulesByIpId(long sourceIpId); long countRulesByIpIdAndState(long sourceIpId, FirewallRule.State state); diff --git a/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDao.java b/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDao.java index 7557084f52f..bb4822ebb38 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDao.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDao.java @@ -75,7 +75,7 @@ public interface IPAddressDao extends GenericDao { long countFreeIPsInNetwork(long networkId); - IPAddressVO findByVmIp(String vmIp); + IPAddressVO findByIp(String ipAddress); IPAddressVO findByAssociatedVmIdAndVmIp(long vmId, String vmIp); diff --git a/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDaoImpl.java index 39c00978be0..d82ffbe2af0 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDaoImpl.java @@ -314,9 +314,9 @@ public class IPAddressDaoImpl extends GenericDaoBase implemen } @Override - public IPAddressVO findByVmIp(String vmIp) { + public IPAddressVO findByIp(String ipAddress) { SearchCriteria sc = AllFieldsSearch.create(); - sc.setParameters("associatedVmIp", vmIp); + sc.setParameters("ipAddress", ipAddress); return findOneBy(sc); } diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index 11db312ebdf..e65d6549603 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -292,6 +292,7 @@ import com.cloud.network.as.AutoScaleVmProfileVO; import com.cloud.network.as.Condition; import com.cloud.network.as.ConditionVO; import com.cloud.network.as.Counter; +import com.cloud.network.dao.FirewallRulesDao; import com.cloud.network.dao.IPAddressDao; import com.cloud.network.dao.IPAddressVO; import com.cloud.network.dao.LoadBalancerVO; @@ -456,6 +457,8 @@ public class ApiResponseHelper implements ResponseGenerator { UserVmJoinDao userVmJoinDao; @Inject NetworkServiceMapDao ntwkSrvcDao; + @Inject + FirewallRulesDao firewallRulesDao; @Override public UserResponse createUserResponse(User user) { @@ -1084,6 +1087,7 @@ public class ApiResponseHelper implements ResponseGenerator { ipResponse.setHasAnnotation(annotationDao.hasAnnotations(ipAddr.getUuid(), AnnotationService.EntityType.PUBLIC_IP_ADDRESS.name(), _accountMgr.isRootAdmin(CallContext.current().getCallingAccount().getId()))); + ipResponse.setHasRules(firewallRulesDao.countRulesByIpId(ipAddr.getId()) > 0); ipResponse.setObjectName("ipaddress"); return ipResponse; } diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index 9255f9275e9..3ac5ca053a1 100644 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -295,6 +295,8 @@ import com.googlecode.ipv6.IPv6Network; public class ConfigurationManagerImpl extends ManagerBase implements ConfigurationManager, ConfigurationService, Configurable { public static final Logger s_logger = Logger.getLogger(ConfigurationManagerImpl.class); + public static final String PERACCOUNT = "peraccount"; + public static final String PERZONE = "perzone"; @Inject EntityManager _entityMgr; @@ -6235,34 +6237,32 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } void validateSourceNatServiceCapablities(final Map sourceNatServiceCapabilityMap) { - if (sourceNatServiceCapabilityMap != null && !sourceNatServiceCapabilityMap.isEmpty()) { - if (sourceNatServiceCapabilityMap.keySet().size() > 2) { - throw new InvalidParameterValueException("Only " + Capability.SupportedSourceNatTypes.getName() + " and " + Capability.RedundantRouter - + " capabilities can be sepcified for source nat service"); - } + if (MapUtils.isNotEmpty(sourceNatServiceCapabilityMap) && (sourceNatServiceCapabilityMap.size() > 2 || ! sourceNatCapabilitiesContainValidValues(sourceNatServiceCapabilityMap))) { + throw new InvalidParameterValueException("Only " + Capability.SupportedSourceNatTypes.getName() + + ", " + Capability.RedundantRouter + + " capabilities can be specified for source nat service"); + } + } - for (final Map.Entry srcNatPair : sourceNatServiceCapabilityMap.entrySet()) { - final Capability capability = srcNatPair.getKey(); - final String value = srcNatPair.getValue(); - if (capability == Capability.SupportedSourceNatTypes) { - final boolean perAccount = value.contains("peraccount"); - final boolean perZone = value.contains("perzone"); - if (perAccount && perZone || !perAccount && !perZone) { - throw new InvalidParameterValueException("Either peraccount or perzone source NAT type can be specified for " - + Capability.SupportedSourceNatTypes.getName()); - } - } else if (capability == Capability.RedundantRouter) { - final boolean enabled = value.contains("true"); - final boolean disabled = value.contains("false"); - if (!enabled && !disabled) { - throw new InvalidParameterValueException("Unknown specified value for " + Capability.RedundantRouter.getName()); - } - } else { - throw new InvalidParameterValueException("Only " + Capability.SupportedSourceNatTypes.getName() + " and " + Capability.RedundantRouter - + " capabilities can be sepcified for source nat service"); + boolean sourceNatCapabilitiesContainValidValues(Map sourceNatServiceCapabilityMap) { + for (final Entry srcNatPair : sourceNatServiceCapabilityMap.entrySet()) { + final Capability capability = srcNatPair.getKey(); + final String value = srcNatPair.getValue(); + if (Capability.SupportedSourceNatTypes.equals(capability)) { + List snatTypes = Arrays.asList(PERACCOUNT, PERZONE); + if (! snatTypes.contains(value) || ( value.contains(PERACCOUNT) && value.contains(PERZONE))) { + throw new InvalidParameterValueException("Either peraccount or perzone source NAT type can be specified for " + + Capability.SupportedSourceNatTypes.getName()); } + } else if (Capability.RedundantRouter.equals(capability)) { + if (! Arrays.asList("true", "false").contains(value.toLowerCase())) { + throw new InvalidParameterValueException("Unknown specified value for " + capability.getName()); + } + } else { + return false; } } + return true; } void validateStaticNatServiceCapablities(final Map staticNatServiceCapabilityMap) { @@ -6439,17 +6439,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati final Map sourceNatServiceCapabilityMap = serviceCapabilityMap.get(Service.SourceNat); if (sourceNatServiceCapabilityMap != null && !sourceNatServiceCapabilityMap.isEmpty()) { - final String sourceNatType = sourceNatServiceCapabilityMap.get(Capability.SupportedSourceNatTypes); - if (sourceNatType != null) { - _networkModel.checkCapabilityForProvider(serviceProviderMap.get(Service.SourceNat), Service.SourceNat, Capability.SupportedSourceNatTypes, sourceNatType); - sharedSourceNat = sourceNatType.contains("perzone"); - } - - final String param = sourceNatServiceCapabilityMap.get(Capability.RedundantRouter); - if (param != null) { - _networkModel.checkCapabilityForProvider(serviceProviderMap.get(Service.SourceNat), Service.SourceNat, Capability.RedundantRouter, param); - redundantRouter = param.contains("true"); - } + sharedSourceNat = isSharedSourceNat(serviceProviderMap, sourceNatServiceCapabilityMap); + redundantRouter = isRedundantRouter(serviceProviderMap, sourceNatServiceCapabilityMap); } final Map staticNatServiceCapabilityMap = serviceCapabilityMap.get(Service.StaticNat); @@ -6599,6 +6590,26 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati }); } + boolean isRedundantRouter(Map> serviceProviderMap, Map sourceNatServiceCapabilityMap) { + boolean redundantRouter = false; + String param = sourceNatServiceCapabilityMap.get(Capability.RedundantRouter); + if (param != null) { + _networkModel.checkCapabilityForProvider(serviceProviderMap.get(Service.SourceNat), Service.SourceNat, Capability.RedundantRouter, param); + redundantRouter = param.contains("true"); + } + return redundantRouter; + } + + boolean isSharedSourceNat(Map> serviceProviderMap, Map sourceNatServiceCapabilityMap) { + boolean sharedSourceNat = false; + String param = sourceNatServiceCapabilityMap.get(Capability.SupportedSourceNatTypes); + if (param != null) { + _networkModel.checkCapabilityForProvider(serviceProviderMap.get(Service.SourceNat), Service.SourceNat, Capability.SupportedSourceNatTypes, param); + sharedSourceNat = param.contains(PERZONE); + } + return sharedSourceNat; + } + protected void validateNtwkOffDetails(final Map details, final Map> serviceProviderMap) { for (final Detail detail : details.keySet()) { diff --git a/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java b/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java index b690acd7dd9..8fae26eacf6 100644 --- a/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java +++ b/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java @@ -2358,4 +2358,19 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage public static ConfigKey getSystemvmpublicipreservationmodestrictness() { return SystemVmPublicIpReservationModeStrictness; } + + @Override + public void updateSourceNatIpAddress(IPAddressVO requestedIp, List userIps) throws Exception{ + Transaction.execute((TransactionCallbackWithException) status -> { + // update all other IPs to not be sourcenat, should be at most one + for(IPAddressVO oldIpAddress :userIps) { + oldIpAddress.setSourceNat(false); + _ipAddressDao.update(oldIpAddress.getId(), oldIpAddress); + } + requestedIp.setSourceNat(true); + _ipAddressDao.update(requestedIp.getId(),requestedIp); + return requestedIp; + }); + } + } diff --git a/server/src/main/java/com/cloud/network/NetworkMigrationManagerImpl.java b/server/src/main/java/com/cloud/network/NetworkMigrationManagerImpl.java index e1f4fe7fb17..0cfd6e6021b 100644 --- a/server/src/main/java/com/cloud/network/NetworkMigrationManagerImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkMigrationManagerImpl.java @@ -297,6 +297,7 @@ public class NetworkMigrationManagerImpl implements NetworkMigrationManager { Vpc copyOfVpc; long copyOfVpcId; try { + copyOfVpc = _vpcService.createVpc(vpc.getZoneId(), vpcOfferingId, vpc.getAccountId(), vpc.getName(), vpc.getDisplayText(), vpc.getCidr(), vpc.getNetworkDomain(), vpc.getIp4Dns1(), vpc.getIp4Dns2(), vpc.getIp6Dns1(), vpc.getIp6Dns2(), vpc.isDisplay(), vpc.getPublicMtu()); diff --git a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java index bf3ba0f6ff7..d10a624323a 100644 --- a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java @@ -80,6 +80,8 @@ import org.apache.commons.lang3.EnumUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -267,9 +269,13 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C private static final long MIN_GRE_KEY = 0L; private static final long MAX_GRE_KEY = 4294967295L; // 2^32 -1 private static final long MIN_VXLAN_VNI = 0L; + /** + // MAX_VXLAN_VNI should be 16777215L (2^24-1), but Linux vxlan interface doesn't accept VNI:2^24-1 now. + // It seems a bug. + // Is this still valid (per 2023?) + */ private static final long MAX_VXLAN_VNI = 16777214L; // 2^24 -2 - // MAX_VXLAN_VNI should be 16777215L (2^24-1), but Linux vxlan interface doesn't accept VNI:2^24-1 now. - // It seems a bug. + private static final String NETWORK_OFFERING_ID = "networkOfferingId"; @Inject DataCenterDao _dcDao = null; @@ -1298,15 +1304,20 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C } } - private void validateRouterIps(String routerIp, String routerIpv6, String startIp, String endIp, String gateway, - String netmask, String startIpv6, String endIpv6, String ip6Cidr) { + void validateSharedNetworkRouterIPs(String gateway, String startIP, String endIP, String netmask, String routerIPv4, String routerIPv6, String startIPv6, String endIPv6, String ip6Cidr, NetworkOffering ntwkOff) { + if (ntwkOff.getGuestType() == GuestType.Shared) { + validateSharedNetworkRouterIPv4(routerIPv4, startIP, endIP, gateway, netmask); + validateSharedNetworkRouterIPv6(routerIPv6, startIPv6, endIPv6, ip6Cidr); + + } + } + + private void validateSharedNetworkRouterIPv4(String routerIp, String startIp, String endIp, String gateway, String netmask) { if (StringUtils.isNotBlank(routerIp)) { if (startIp != null && endIp == null) { endIp = startIp; } - if (!NetUtils.isValidIp4(routerIp)) { - throw new CloudRuntimeException("Router IPv4 IP provided is of incorrect format"); - } + isIPv4AddressValid(routerIp); if (StringUtils.isNoneBlank(startIp, endIp)) { if (!NetUtils.isIpInRange(routerIp, startIp, endIp)) { throw new CloudRuntimeException("Router IPv4 IP provided is not within the specified range: " + startIp + " - " + endIp); @@ -1318,26 +1329,39 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C } } } - if (StringUtils.isNotBlank(routerIpv6)) { - if (startIpv6 != null && endIpv6 == null) { - endIpv6 = startIpv6; + } + + private void validateSharedNetworkRouterIPv6(String routerIPv6, String startIPv6, String endIPv6, String cidrIPv6) { + if (StringUtils.isNotBlank(routerIPv6)) { + if (startIPv6 != null && endIPv6 == null) { + endIPv6 = startIPv6; } - if (!NetUtils.isValidIp6(routerIpv6)) { - throw new CloudRuntimeException("Router IPv6 address provided is of incorrect format"); - } - if (StringUtils.isNoneBlank(startIpv6, endIpv6)) { - String ipv6Range = startIpv6 + "-" + endIpv6; - if (!NetUtils.isIp6InRange(routerIpv6, ipv6Range)) { - throw new CloudRuntimeException("Router IPv6 address provided is not within the specified range: " + startIpv6 + " - " + endIpv6); + isIPv6AddressValid(routerIPv6); + if (StringUtils.isNoneBlank(startIPv6, endIPv6)) { + String ipv6Range = startIPv6 + "-" + endIPv6; + if (!NetUtils.isIp6InRange(routerIPv6, ipv6Range)) { + throw new CloudRuntimeException("Router IPv6 address provided is not within the specified range: " + startIPv6 + " - " + endIPv6); } } else { - if (!NetUtils.isIp6InNetwork(routerIpv6, ip6Cidr)) { + if (!NetUtils.isIp6InNetwork(routerIPv6, cidrIPv6)) { throw new CloudRuntimeException("Router IPv6 address provided is not with the network range"); } } } } + private void isIPv4AddressValid(String routerIp) { + if (!NetUtils.isValidIp4(routerIp)) { + throw new CloudRuntimeException("Router IPv4 IP provided is of incorrect format"); + } + } + + private void isIPv6AddressValid(String routerIPv6) { + if (!NetUtils.isValidIp6(routerIPv6)) { + throw new CloudRuntimeException("Router IPv6 address provided is of incorrect format"); + } + } + @Override @DB @ActionEvent(eventType = EventTypes.EVENT_NETWORK_CREATE, eventDescription = "creating network") @@ -1348,34 +1372,26 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C String endIP = cmd.getEndIp(); String netmask = cmd.getNetmask(); String networkDomain = cmd.getNetworkDomain(); - String vlanId = null; - boolean bypassVlanOverlapCheck = false; - boolean hideIpAddressUsage = false; - String routerIp = null; - String routerIpv6 = null; - if (cmd instanceof CreateNetworkCmdByAdmin) { - vlanId = ((CreateNetworkCmdByAdmin)cmd).getVlan(); - bypassVlanOverlapCheck = ((CreateNetworkCmdByAdmin)cmd).getBypassVlanOverlapCheck(); - hideIpAddressUsage = ((CreateNetworkCmdByAdmin)cmd).getHideIpAddressUsage(); - routerIp = ((CreateNetworkCmdByAdmin)cmd).getRouterIp(); - routerIpv6 = ((CreateNetworkCmdByAdmin)cmd).getRouterIpv6(); - } + + boolean adminCalledUs = cmd instanceof CreateNetworkCmdByAdmin; + String vlanId = adminCalledUs ? ((CreateNetworkCmdByAdmin)cmd).getVlan() : null; + boolean bypassVlanOverlapCheck = adminCalledUs && ((CreateNetworkCmdByAdmin)cmd).getBypassVlanOverlapCheck(); + boolean hideIpAddressUsage = adminCalledUs && ((CreateNetworkCmdByAdmin)cmd).getHideIpAddressUsage(); + String routerIPv4 = adminCalledUs ? ((CreateNetworkCmdByAdmin)cmd).getRouterIp() : null; + String routerIPv6 = adminCalledUs ? ((CreateNetworkCmdByAdmin)cmd).getRouterIpv6() : null; String name = cmd.getNetworkName(); String displayText = cmd.getDisplayText(); Account caller = CallContext.current().getCallingAccount(); Long physicalNetworkId = cmd.getPhysicalNetworkId(); - Long zoneId = cmd.getZoneId(); - String aclTypeStr = cmd.getAclType(); Long domainId = cmd.getDomainId(); - boolean isDomainSpecific = false; Boolean subdomainAccess = cmd.getSubdomainAccess(); Long vpcId = cmd.getVpcId(); String startIPv6 = cmd.getStartIpv6(); String endIPv6 = cmd.getEndIpv6(); String ip6Gateway = cmd.getIp6Gateway(); String ip6Cidr = cmd.getIp6Cidr(); - Boolean displayNetwork = cmd.getDisplayNetwork(); + boolean displayNetwork = ! Boolean.FALSE.equals(cmd.getDisplayNetwork()); Long aclId = cmd.getAclId(); String isolatedPvlan = cmd.getIsolatedPvlan(); String externalId = cmd.getExternalId(); @@ -1388,127 +1404,31 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C String ip6Dns1 = cmd.getIp6Dns1(); String ip6Dns2 = cmd.getIp6Dns2(); - // Validate network offering - NetworkOfferingVO ntwkOff = _networkOfferingDao.findById(networkOfferingId); - if (ntwkOff == null || ntwkOff.isSystemOnly()) { - InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find network offering by specified id"); - if (ntwkOff != null) { - ex.addProxyObject(ntwkOff.getUuid(), "networkOfferingId"); - } - throw ex; - } + // Validate network offering id + NetworkOffering ntwkOff = getAndValidateNetworkOffering(networkOfferingId); - Account owner = null; - if ((cmd.getAccountName() != null && domainId != null) || cmd.getProjectId() != null) { - owner = _accountMgr.finalizeOwner(caller, cmd.getAccountName(), domainId, cmd.getProjectId()); - } else { - s_logger.info(String.format("Assigning the network to caller:%s because either projectId or accountname and domainId are not provided", caller.getAccountName())); - owner = caller; - } + Account owner = getOwningAccount(cmd, caller); - // validate physical network and zone - // Check if physical network exists - PhysicalNetwork pNtwk = null; - if (physicalNetworkId != null) { - pNtwk = _physicalNetworkDao.findById(physicalNetworkId); - if (pNtwk == null) { - throw new InvalidParameterValueException("Unable to find a physical network having the specified physical network id"); - } - } + PhysicalNetwork pNtwk = getAndValidatePhysicalNetwork(physicalNetworkId); - if (zoneId == null) { - zoneId = pNtwk.getDataCenterId(); - } - - if (displayNetwork == null) { - displayNetwork = true; - } - - DataCenter zone = _dcDao.findById(zoneId); - if (zone == null) { - throw new InvalidParameterValueException("Specified zone id was not found"); - } + DataCenter zone = getAndValidateZone(cmd, pNtwk); _accountMgr.checkAccess(owner, ntwkOff, zone); - if (Grouping.AllocationState.Disabled == zone.getAllocationState() && !_accountMgr.isRootAdmin(caller.getId())) { - // See DataCenterVO.java - PermissionDeniedException ex = new PermissionDeniedException("Cannot perform this operation since specified Zone is currently disabled"); - ex.addProxyObject(zone.getUuid(), "zoneId"); - throw ex; - } + validateZoneAvailability(caller, zone); - // Only domain and account ACL types are supported in Acton. - ACLType aclType = null; - if (aclTypeStr != null) { - if (aclTypeStr.equalsIgnoreCase(ACLType.Account.toString())) { - aclType = ACLType.Account; - } else if (aclTypeStr.equalsIgnoreCase(ACLType.Domain.toString())) { - aclType = ACLType.Domain; - } else { - throw new InvalidParameterValueException("Incorrect aclType specified. Check the API documentation for supported types"); - } - // In 3.0 all Shared networks should have aclType == Domain, all Isolated networks aclType==Account - if (ntwkOff.getGuestType() == GuestType.Isolated) { - if (aclType != ACLType.Account) { - throw new InvalidParameterValueException("AclType should be " + ACLType.Account + " for network of type " + Network.GuestType.Isolated); - } - } else if (ntwkOff.getGuestType() == GuestType.Shared) { - if (!(aclType == ACLType.Domain || aclType == ACLType.Account)) { - throw new InvalidParameterValueException("AclType should be " + ACLType.Domain + " or " + ACLType.Account + " for network of type " + Network.GuestType.Shared); - } - } - } else { - if (ntwkOff.getGuestType() == GuestType.Isolated || ntwkOff.getGuestType() == GuestType.L2) { - aclType = ACLType.Account; - } else if (ntwkOff.getGuestType() == GuestType.Shared) { - if (_accountMgr.isRootAdmin(caller.getId())) { - aclType = ACLType.Domain; - } else if (_accountMgr.isNormalUser(caller.getId())) { - aclType = ACLType.Account; - } else { - throw new InvalidParameterValueException("AclType must be specified for shared network created by domain admin"); - } - } - } + ACLType aclType = getAclType(caller, cmd.getAclType(), ntwkOff); - if (ntwkOff.getGuestType() != GuestType.Shared && (!StringUtils.isAllBlank(routerIp, routerIpv6))) { + if (ntwkOff.getGuestType() != GuestType.Shared && (!StringUtils.isAllBlank(routerIPv4, routerIPv6))) { throw new InvalidParameterValueException("Router IP can be specified only for Shared networks"); } if (ntwkOff.getGuestType() == GuestType.Shared && !_networkModel.isProviderForNetworkOffering(Provider.VirtualRouter, networkOfferingId) - && (!StringUtils.isAllBlank(routerIp, routerIpv6))) { + && (!StringUtils.isAllBlank(routerIPv4, routerIPv6))) { throw new InvalidParameterValueException("Virtual Router is not a supported provider for the Shared network, hence router ip should not be provided"); } - // Check if the network is domain specific - if (aclType == ACLType.Domain) { - // only Admin can create domain with aclType=Domain - if (!_accountMgr.isAdmin(caller.getId())) { - throw new PermissionDeniedException("Only admin can create networks with aclType=Domain"); - } - - // only shared networks can be Domain specific - if (ntwkOff.getGuestType() != GuestType.Shared) { - throw new InvalidParameterValueException("Only " + GuestType.Shared + " networks can have aclType=" + ACLType.Domain); - } - - if (domainId != null) { - if (ntwkOff.getTrafficType() != TrafficType.Guest || ntwkOff.getGuestType() != Network.GuestType.Shared) { - throw new InvalidParameterValueException("Domain level networks are supported just for traffic type " + TrafficType.Guest + " and guest type " + Network.GuestType.Shared); - } - - DomainVO domain = _domainDao.findById(domainId); - if (domain == null) { - throw new InvalidParameterValueException("Unable to find domain by specified id"); - } - _accountMgr.checkAccess(caller, domain); - } - isDomainSpecific = true; - - } else if (subdomainAccess != null) { - throw new InvalidParameterValueException("Parameter subDomainAccess can be specified only with aclType=Domain"); - } + boolean isDomainSpecific = isDomainSpecificNetworkRequested(caller, domainId, subdomainAccess, ntwkOff, aclType); if (aclType == ACLType.Domain) { owner = _accountDao.findById(Account.ACCOUNT_ID_SYSTEM); @@ -1610,7 +1530,8 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C } } - validateRouterIps(routerIp, routerIpv6, startIP, endIP, gateway, netmask, startIPv6, endIPv6, ip6Cidr); + validateSharedNetworkRouterIPs(gateway, startIP, endIP, netmask, routerIPv4, routerIPv6, startIPv6, endIPv6, ip6Cidr, ntwkOff); + Pair ip6GatewayCidr = null; if (zone.getNetworkType() == NetworkType.Advanced && ntwkOff.getGuestType() == GuestType.Isolated) { ipv6 = _networkOfferingDao.isIpv6Supported(ntwkOff.getId()); @@ -1682,7 +1603,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C if (cidr != null && providersConfiguredForExternalNetworking(ntwkProviders)) { if (ntwkOff.getGuestType() == GuestType.Shared && (zone.getNetworkType() == NetworkType.Advanced) && isSharedNetworkOfferingWithServices(networkOfferingId)) { // validate if CIDR specified overlaps with any of the CIDR's allocated for isolated networks and shared networks in the zone - checkSharedNetworkCidrOverlap(zoneId, pNtwk.getId(), cidr); + checkSharedNetworkCidrOverlap(zone.getId(), pNtwk.getId(), cidr); } else { // if the guest network is for the VPC, if any External Provider are supported in VPC // cidr will not be null as it is generated from the super cidr of vpc. @@ -1707,10 +1628,10 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C // Can add vlan range only to the network which allows it if (createVlan && !ntwkOff.isSpecifyIpRanges()) { - throwInvalidIdException("Network offering with specified id doesn't support adding multiple ip ranges", ntwkOff.getUuid(), "networkOfferingId"); + throwInvalidIdException("Network offering with specified id doesn't support adding multiple ip ranges", ntwkOff.getUuid(), NETWORK_OFFERING_ID); } - Pair interfaceMTUs = validateMtuConfig(publicMtu, privateMtu, zoneId); + Pair interfaceMTUs = validateMtuConfig(publicMtu, privateMtu, zone.getId()); mtuCheckForVpcNetwork(vpcId, interfaceMTUs, publicMtu, privateMtu); Network associatedNetwork = null; @@ -1729,9 +1650,12 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C checkNetworkDns(ipv6, ntwkOff, vpcId, ip4Dns1, ip4Dns2, ip6Dns1, ip6Dns2); - Network network = commitNetwork(networkOfferingId, gateway, startIP, endIP, netmask, networkDomain, vlanId, bypassVlanOverlapCheck, name, displayText, caller, physicalNetworkId, zoneId, + Network network = commitNetwork(networkOfferingId, gateway, startIP, endIP, netmask, networkDomain, vlanId, bypassVlanOverlapCheck, name, displayText, caller, physicalNetworkId, zone.getId(), domainId, isDomainSpecific, subdomainAccess, vpcId, startIPv6, endIPv6, ip6Gateway, ip6Cidr, displayNetwork, aclId, secondaryVlanId, privateVlanType, ntwkOff, pNtwk, aclType, owner, cidr, createVlan, - externalId, routerIp, routerIpv6, associatedNetwork, ip4Dns1, ip4Dns2, ip6Dns1, ip6Dns2, interfaceMTUs); + externalId, routerIPv4, routerIPv6, associatedNetwork, ip4Dns1, ip4Dns2, ip6Dns1, ip6Dns2, interfaceMTUs); + + // retrieve, acquire and associate the correct ip adresses + checkAndSetRouterSourceNatIp(owner, cmd, network); if (hideIpAddressUsage) { _networkDetailsDao.persist(new NetworkDetailVO(network.getId(), Network.hideIpAddressUsage, String.valueOf(hideIpAddressUsage), false)); @@ -1748,6 +1672,213 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C return network; } + void checkAndSetRouterSourceNatIp(Account owner, CreateNetworkCmd cmd, Network network) throws InsufficientAddressCapacityException, ResourceAllocationException { + String sourceNatIp = cmd.getSourceNatIP(); + if (sourceNatIp == null) { + s_logger.debug(String.format("no source nat ip given for create network %s command, using something arbitrary.", cmd.getNetworkName())); + return; // nothing to try + } + IpAddress ip = allocateIP(owner, cmd.getZoneId(), network.getId(), null, sourceNatIp); + try { + associateIPToNetwork(ip.getId(), network.getId()); + } catch (ResourceUnavailableException e) { + String msg = String.format("can´t use %s as sourcenat IP address for network %s/%s as it is un available", sourceNatIp, network.getName(), network.getUuid()); + s_logger.error(msg); + throw new CloudRuntimeException(msg,e); + } + } + + /** + * @param cmd + * @param network + * @return whether the sourceNat is changed, and consequently restart is needed + * @throws InsufficientAddressCapacityException + * @throws ResourceAllocationException + */ + private boolean checkAndUpdateRouterSourceNatIp(UpdateNetworkCmd cmd, Network network) { + IPAddressVO requestedIp = checkSourceNatIpAddressForUpdate(cmd, network); + if (requestedIp == null) return false; // ip not associated with this network + + List userIps = _ipAddressDao.listByAssociatedNetwork(network.getId(), true); + if (! userIps.isEmpty()) { + try { + _ipAddrMgr.updateSourceNatIpAddress(requestedIp, userIps); + } catch (Exception e) { // pokemon execption from transaction + String msg = String.format("Update of source NAT ip to %s for network \"%s\"/%s failed due to %s", + requestedIp.getAddress().addr(), network.getName(), network.getUuid(), e.getLocalizedMessage()); + s_logger.error(msg); + throw new CloudRuntimeException(msg, e); + } + } + return true; + } + + @Nullable + private IPAddressVO checkSourceNatIpAddressForUpdate(UpdateNetworkCmd cmd, Network network) { + String sourceNatIp = cmd.getSourceNatIP(); + if (sourceNatIp == null) { + s_logger.trace(String.format("no source NAT ip given to update network %s with.", cmd.getNetworkName())); + return null; + } else { + s_logger.info(String.format("updating network %s to have source NAT ip %s", cmd.getNetworkName(), sourceNatIp)); + } + // check if the address is already aqcuired for this network + IPAddressVO requestedIp = _ipAddressDao.findByIp(sourceNatIp); + if (requestedIp == null || requestedIp.getAssociatedWithNetworkId() == null || ! requestedIp.getAssociatedWithNetworkId().equals(network.getId())) { + s_logger.warn(String.format("Source NAT IP %s is not associated with network %s/%s. It cannot be used as source NAT IP.", + sourceNatIp, network.getName(), network.getUuid())); + return null; + } + // check if it is the current source NAT address + if (requestedIp.isSourceNat()) { + s_logger.info(String.format("IP address %s is allready the source Nat address. Not updating!", sourceNatIp)); + return null; + } + return requestedIp; + } + + @Nullable + private ACLType getAclType(Account caller, String aclTypeStr, NetworkOffering ntwkOff) { + // Only domain and account ACL types are supported in Acton. + ACLType aclType = null; + if (aclTypeStr != null) { + aclType = getAclType(aclTypeStr, ntwkOff); + } else { + aclType = getAclType(caller, ntwkOff, aclType); + } + return aclType; + } + + @NotNull + private static ACLType getAclType(String aclTypeStr, NetworkOffering ntwkOff) { + ACLType aclType; + if (aclTypeStr.equalsIgnoreCase(ACLType.Account.toString())) { + aclType = ACLType.Account; + } else if (aclTypeStr.equalsIgnoreCase(ACLType.Domain.toString())) { + aclType = ACLType.Domain; + } else { + throw new InvalidParameterValueException("Incorrect aclType specified. Check the API documentation for supported types"); + } + // In 3.0 all Shared networks should have aclType == Domain, all Isolated networks aclType==Account + if (ntwkOff.getGuestType() == GuestType.Isolated && aclType != ACLType.Account) { + throw new InvalidParameterValueException("AclType should be " + ACLType.Account + " for network of type " + GuestType.Isolated); + } + return aclType; + } + + private ACLType getAclType(Account caller, NetworkOffering ntwkOff, ACLType aclType) { + if (ntwkOff.getGuestType() == GuestType.Isolated || ntwkOff.getGuestType() == GuestType.L2) { + aclType = ACLType.Account; + } else if (ntwkOff.getGuestType() == GuestType.Shared) { + if (_accountMgr.isRootAdmin(caller.getId())) { + aclType = ACLType.Domain; + } else if (_accountMgr.isNormalUser(caller.getId())) { + aclType = ACLType.Account; + } else { + throw new InvalidParameterValueException("AclType must be specified for shared network created by domain admin"); + } + } + return aclType; + } + + private void validateZoneAvailability(Account caller, DataCenter zone) { + if (Grouping.AllocationState.Disabled == zone.getAllocationState() && !_accountMgr.isRootAdmin(caller.getId())) { + // See DataCenterVO.java + PermissionDeniedException ex = new PermissionDeniedException("Cannot perform this operation since specified Zone is currently disabled"); + ex.addProxyObject(zone.getUuid(), "zoneId"); + throw ex; + } + } + + private boolean isDomainSpecificNetworkRequested(Account caller, Long domainId, Boolean subdomainAccess, NetworkOffering ntwkOff, ACLType aclType) { + boolean isDomainSpecific = false; + // Check if the network is domain specific + if (aclType == ACLType.Domain) { + // only Admin can create domain with aclType=Domain + if (!_accountMgr.isAdmin(caller.getId())) { + throw new PermissionDeniedException("Only admin can create networks with aclType=Domain"); + } + + // only shared networks can be Domain specific + if (ntwkOff.getGuestType() != GuestType.Shared) { + throw new InvalidParameterValueException("Only " + GuestType.Shared + " networks can have aclType=" + ACLType.Domain); + } + + if (domainId != null) { + if (ntwkOff.getTrafficType() != TrafficType.Guest || ntwkOff.getGuestType() != GuestType.Shared) { + throw new InvalidParameterValueException("Domain level networks are supported just for traffic type " + TrafficType.Guest + " and guest type " + GuestType.Shared); + } + + DomainVO domain = _domainDao.findById(domainId); + if (domain == null) { + throw new InvalidParameterValueException("Unable to find domain by specified id"); + } + _accountMgr.checkAccess(caller, domain); + } + isDomainSpecific = true; + + } else if (subdomainAccess != null) { + throw new InvalidParameterValueException("Parameter subDomainAccess can be specified only with aclType=Domain"); + } + return isDomainSpecific; + } + + @NotNull + private DataCenter getAndValidateZone(CreateNetworkCmd cmd, PhysicalNetwork pNtwk) { + Long zoneId = (cmd.getZoneId() == null) ? pNtwk.getDataCenterId() : cmd.getZoneId(); + DataCenter zone = _dcDao.findById(zoneId); + if (zone == null) { + throw new InvalidParameterValueException("Specified zone id was not found"); + } + return zone; + } + + /** + // validate physical network and zone + // Check if physical network exists + * + * @param physicalNetworkId the id of the required physical network + * @return the data object for the physical network + */ + @NotNull + private PhysicalNetwork getAndValidatePhysicalNetwork(Long physicalNetworkId) { + PhysicalNetwork pNtwk = null; + if (physicalNetworkId != null) { + pNtwk = getPhysicalNetwork(physicalNetworkId); + if (pNtwk == null) { + throw new InvalidParameterValueException("Unable to find a physical network having the specified physical network id"); + } + } else { + throw new CloudRuntimeException("cannot create Guestnetwork without physical network."); + } + return pNtwk; + } + + private Account getOwningAccount(CreateNetworkCmd cmd, Account caller) { + Account owner = null; + Long domainId = cmd.getDomainId(); + if ((cmd.getAccountName() != null && domainId != null) || cmd.getProjectId() != null) { + owner = _accountMgr.finalizeOwner(caller, cmd.getAccountName(), domainId, cmd.getProjectId()); + } else { + s_logger.info(String.format("Assigning the network to caller:%s because either projectId or accountname and domainId are not provided", caller.getAccountName())); + owner = caller; + } + return owner; + } + + @NotNull + private NetworkOffering getAndValidateNetworkOffering(Long networkOfferingId) { + NetworkOfferingVO ntwkOff = _networkOfferingDao.findById(networkOfferingId); + if (ntwkOff == null || ntwkOff.isSystemOnly()) { + InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find network offering by specified id"); + if (ntwkOff != null) { + ex.addProxyObject(ntwkOff.getUuid(), NETWORK_OFFERING_ID); + } + throw ex; + } + return ntwkOff; + } + protected void mtuCheckForVpcNetwork(Long vpcId, Pair interfaceMTUs, Integer publicMtu, Integer privateMtu) { if (vpcId != null && publicMtu != null) { VpcVO vpc = _vpcDao.findById(vpcId); @@ -1862,7 +1993,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C } } - private void validateNetworkOfferingForNonRootAdminUser(NetworkOfferingVO ntwkOff) { + private void validateNetworkOfferingForNonRootAdminUser(NetworkOffering ntwkOff) { if (ntwkOff.getTrafficType() != TrafficType.Guest) { throw new InvalidParameterValueException("This user can only create a Guest network"); } @@ -1924,7 +2055,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C private Network commitNetwork(final Long networkOfferingId, final String gateway, final String startIP, final String endIP, final String netmask, final String networkDomain, final String vlanIdFinal, final Boolean bypassVlanOverlapCheck, final String name, final String displayText, final Account caller, final Long physicalNetworkId, final Long zoneId, final Long domainId, final boolean isDomainSpecific, final Boolean subdomainAccessFinal, final Long vpcId, final String startIPv6, final String endIPv6, final String ip6Gateway, final String ip6Cidr, - final Boolean displayNetwork, final Long aclId, final String isolatedPvlan, final PVlanType isolatedPvlanType, final NetworkOfferingVO ntwkOff, final PhysicalNetwork pNtwk, final ACLType aclType, final Account ownerFinal, + final Boolean displayNetwork, final Long aclId, final String isolatedPvlan, final PVlanType isolatedPvlanType, final NetworkOffering ntwkOff, final PhysicalNetwork pNtwk, final ACLType aclType, final Account ownerFinal, final String cidr, final boolean createVlan, final String externalId, String routerIp, String routerIpv6, final Network associatedNetwork, final String ip4Dns1, final String ip4Dns2, final String ip6Dns1, final String ip6Dns2, Pair vrIfaceMTUs) throws InsufficientCapacityException, ResourceAllocationException { try { @@ -2373,7 +2504,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C } if (networkOfferingId != null) { - sc.addAnd("networkOfferingId", SearchCriteria.Op.EQ, networkOfferingId); + sc.addAnd(NETWORK_OFFERING_ID, SearchCriteria.Op.EQ, networkOfferingId); } if (associatedNetworkId != null) { @@ -2796,6 +2927,8 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C _accountMgr.checkAccess(callerAccount, AccessType.OperateEntry, true, network); _accountMgr.checkAccess(_accountMgr.getActiveAccountById(network.getAccountId()), offering, _dcDao.findById(network.getDataCenterId())); + restartNetwork |= checkAndUpdateRouterSourceNatIp(cmd, network); + if (cmd instanceof UpdateNetworkCmdByAdmin) { final Boolean hideIpAddressUsage = ((UpdateNetworkCmdByAdmin) cmd).getHideIpAddressUsage(); if (hideIpAddressUsage != null) { @@ -2844,13 +2977,13 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C NetworkOfferingVO networkOffering = _networkOfferingDao.findById(networkOfferingId); if (networkOfferingId != null) { if (networkOffering == null || networkOffering.isSystemOnly()) { - throwInvalidIdException("Unable to find network offering with specified id", networkOfferingId.toString(), "networkOfferingId"); + throwInvalidIdException("Unable to find network offering with specified id", networkOfferingId.toString(), NETWORK_OFFERING_ID); } // network offering should be in Enabled state if (networkOffering.getState() != NetworkOffering.State.Enabled) { throwInvalidIdException("Network offering with specified id is not in " + NetworkOffering.State.Enabled + " state, can't upgrade to it", networkOffering.getUuid(), - "networkOfferingId"); + NETWORK_OFFERING_ID); } //can't update from vpc to non-vpc network offering boolean forVpcNew = _configMgr.isOfferingForVpc(networkOffering); @@ -2893,10 +3026,8 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C } } - if (checkAndUpdateNetworkDns(network, networkOfferingChanged ? networkOffering : oldNtwkOff, ip4Dns1, ip4Dns2, - ip6Dns1, ip6Dns2)) { - restartNetwork = true; - } + restartNetwork |= checkAndUpdateNetworkDns(network, networkOfferingChanged ? networkOffering : oldNtwkOff, ip4Dns1, ip4Dns2, + ip6Dns1, ip6Dns2); final Map newSvcProviders = networkOfferingChanged ? _networkMgr.finalizeServicesAndProvidersForNetwork(_entityMgr.findById(NetworkOffering.class, networkOfferingId), network.getPhysicalNetworkId()) @@ -3093,7 +3224,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C if (servicesNotInNewOffering != null && !servicesNotInNewOffering.isEmpty()) { _networkMgr.cleanupConfigForServicesInNetwork(servicesNotInNewOffering, network); } - } catch (Throwable e) { + } catch (Exception e) { // old pokemon catch that used to catch throwable s_logger.debug("failed to cleanup config related to unused services error:" + e.getMessage()); } @@ -3647,7 +3778,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C if (newNtwkOff == null || newNtwkOff.isSystemOnly()) { InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find network offering."); if (newNtwkOff != null) { - ex.addProxyObject(String.valueOf(newNtwkOff.getId()), "networkOfferingId"); + ex.addProxyObject(String.valueOf(newNtwkOff.getId()), NETWORK_OFFERING_ID); } throw ex; } diff --git a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java index f5682665895..5de05a12d18 100644 --- a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java +++ b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java @@ -53,7 +53,9 @@ import org.apache.cloudstack.api.command.user.vpc.CreateVPCCmd; import org.apache.cloudstack.api.command.user.vpc.ListPrivateGatewaysCmd; import org.apache.cloudstack.api.command.user.vpc.ListStaticRoutesCmd; import org.apache.cloudstack.api.command.user.vpc.ListVPCOfferingsCmd; +import org.apache.cloudstack.api.command.user.vpc.ListVPCsCmd; import org.apache.cloudstack.api.command.user.vpc.RestartVPCCmd; +import org.apache.cloudstack.api.command.user.vpc.UpdateVPCCmd; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; @@ -62,6 +64,7 @@ import org.apache.cloudstack.query.QueryService; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.log4j.Logger; +import org.jetbrains.annotations.Nullable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -1017,7 +1020,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis @Override @ActionEvent(eventType = EventTypes.EVENT_VPC_CREATE, eventDescription = "creating vpc", create = true) public Vpc createVpc(final long zoneId, final long vpcOffId, final long vpcOwnerId, final String vpcName, final String displayText, final String cidr, String networkDomain, - final String ip4Dns1, final String ip4Dns2, final String ip6Dns1, final String ip6Dns2, final Boolean displayVpc, Integer publicMtu) throws ResourceAllocationException { + final String ip4Dns1, final String ip4Dns2, final String ip6Dns1, final String ip6Dns2, final Boolean displayVpc, Integer publicMtu) throws ResourceAllocationException { final Account caller = CallContext.current().getCallingAccount(); final Account owner = _accountMgr.getAccount(vpcOwnerId); @@ -1091,6 +1094,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis final VpcVO vpc = new VpcVO(zoneId, vpcName, displayText, owner.getId(), owner.getDomainId(), vpcOffId, cidr, networkDomain, useDistributedRouter, isRegionLevelVpcOff, vpcOff.isRedundantRouter(), ip4Dns1, ip4Dns2, ip6Dns1, ip6Dns2); vpc.setPublicMtu(publicMtu); + vpc.setDisplay(Boolean.TRUE.equals(displayVpc)); return createVpc(displayVpc, vpc); } @@ -1098,9 +1102,24 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis @Override @ActionEvent(eventType = EventTypes.EVENT_VPC_CREATE, eventDescription = "creating vpc", create = true) public Vpc createVpc(CreateVPCCmd cmd) throws ResourceAllocationException { - return createVpc(cmd.getZoneId(), cmd.getVpcOffering(), cmd.getEntityOwnerId(), cmd.getVpcName(), cmd.getDisplayText(), + Vpc vpc = createVpc(cmd.getZoneId(), cmd.getVpcOffering(), cmd.getEntityOwnerId(), cmd.getVpcName(), cmd.getDisplayText(), cmd.getCidr(), cmd.getNetworkDomain(), cmd.getIp4Dns1(), cmd.getIp4Dns2(), cmd.getIp6Dns1(), cmd.getIp6Dns2(), cmd.isDisplay(), cmd.getPublicMtu()); + // associate cmd.getSourceNatIP() with this vpc + allocateSourceNatIp(vpc, cmd.getSourceNatIP()); + return vpc; + } + + private void allocateSourceNatIp(Vpc vpc, String sourceNatIP) { + Account account = _accountMgr.getAccount(vpc.getAccountId()); + DataCenter zone = _dcDao.findById(vpc.getZoneId()); + // reserve this ip and then + try { + IpAddress ip = _ipAddrMgr.allocateIp(account, false, CallContext.current().getCallingAccount(), CallContext.current().getCallingUserId(), zone, null, sourceNatIP); + this.associateIPToVpc(ip.getId(), vpc.getId()); + } catch (ResourceAllocationException | ResourceUnavailableException | InsufficientAddressCapacityException e){ + throw new CloudRuntimeException("new source NAT address cannot be acquired", e); + } } @DB @@ -1126,10 +1145,6 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis return Transaction.execute(new TransactionCallback() { @Override public VpcVO doInTransaction(final TransactionStatus status) { - if (displayVpc != null) { - vpc.setDisplay(displayVpc); - } - final VpcVO persistedVpc = vpcDao.persist(vpc, finalizeServicesAndProvidersForVpc(vpc.getZoneId(), vpc.getVpcOfferingId())); _resourceLimitMgr.incrementResourceCount(vpc.getAccountId(), ResourceType.vpc); s_logger.debug("Created VPC " + persistedVpc); @@ -1242,9 +1257,14 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis } } + @Override + public Vpc updateVpc(UpdateVPCCmd cmd) throws ResourceUnavailableException, InsufficientCapacityException { + return updateVpc(cmd.getId(), cmd.getVpcName(), cmd.getDisplayText(), cmd.getCustomId(), cmd.isDisplayVpc(), cmd.getPublicMtu(), cmd.getSourceNatIP()); + } + @Override @ActionEvent(eventType = EventTypes.EVENT_VPC_UPDATE, eventDescription = "updating vpc") - public Vpc updateVpc(final long vpcId, final String vpcName, final String displayText, final String customId, final Boolean displayVpc, Integer mtu) { + public Vpc updateVpc(final long vpcId, final String vpcName, final String displayText, final String customId, final Boolean displayVpc, Integer mtu, String sourceNatIp) throws ResourceUnavailableException, InsufficientCapacityException { CallContext.current().setEventDetails(" Id: " + vpcId); final Account caller = CallContext.current().getCallingAccount(); @@ -1279,14 +1299,80 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis updateMtuOfVpcNetwork(vpcToUpdate, vpc, mtu); } - if (vpcDao.update(vpcId, vpc)) { + boolean restartRequired = checkAndUpdateRouterSourceNatIp(vpcToUpdate, sourceNatIp); + + if (vpcDao.update(vpcId, vpc) || restartRequired) { // Note that the update may fail because nothing has changed, other than the sourcenat ip s_logger.debug("Updated VPC id=" + vpcId); + if (restartRequired) { + if (s_logger.isDebugEnabled()) { + s_logger.debug(String.format("restarting vpc %s/%s, due to changing sourcenat in Update VPC call", vpc.getName(), vpc.getUuid())); + } + final User callingUser = _accountMgr.getActiveUser(CallContext.current().getCallingUserId()); + restartVpc(vpcId, true, false, false, callingUser); + } else { + if (s_logger.isDebugEnabled()) { + s_logger.debug("no restart needed."); + } + } return vpcDao.findById(vpcId); } else { + s_logger.error(String.format("failed to update vpc %s/%s",vpc.getName(), vpc.getUuid())); return null; } } + private boolean checkAndUpdateRouterSourceNatIp(Vpc vpc, String sourceNatIp) { + IPAddressVO requestedIp = validateSourceNatip(vpc, sourceNatIp); + if (requestedIp == null) return false; // ip not associated with this network + + List userIps = _ipAddressDao.listByAssociatedVpc(vpc.getId(), true); + if (! userIps.isEmpty()) { + try { + _ipAddrMgr.updateSourceNatIpAddress(requestedIp, userIps); + } catch (Exception e) { // pokemon exception from transaction + String msg = String.format("Update of source NAT ip to %s for network \"%s\"/%s failed due to %s", + requestedIp.getAddress().addr(), vpc.getName(), vpc.getUuid(), e.getLocalizedMessage()); + s_logger.error(msg); + throw new CloudRuntimeException(msg, e); + } + } + return true; + } + + @Nullable + protected IPAddressVO validateSourceNatip(Vpc vpc, String sourceNatIp) { + if (sourceNatIp == null) { + s_logger.trace(String.format("no source NAT ip given to update vpc %s with.", vpc.getName())); + return null; + } else { + s_logger.info(String.format("updating VPC %s to have source NAT ip %s", vpc.getName(), sourceNatIp)); + } + IPAddressVO requestedIp = getIpAddressVO(vpc, sourceNatIp); + if (requestedIp == null) return null; + // check if it is the current source NAT address + if (requestedIp.isSourceNat()) { + s_logger.info(String.format("IP address %s is already the source Nat address. Not updating!", sourceNatIp)); + return null; + } + if (_firewallDao.countRulesByIpId(requestedIp.getId()) > 0) { + s_logger.info(String.format("IP address %s has firewall/portforwarding rules. Not updating!", sourceNatIp)); + return null; + } + return requestedIp; + } + + @Nullable + private IPAddressVO getIpAddressVO(Vpc vpc, String sourceNatIp) { + // check if the address is already aqcuired for this network + IPAddressVO requestedIp = _ipAddressDao.findByIp(sourceNatIp); + if (requestedIp == null || requestedIp.getVpcId() == null || ! requestedIp.getVpcId().equals(vpc.getId())) { + s_logger.warn(String.format("Source NAT IP %s is not associated with network %s/%s. It cannot be used as source NAT IP.", + sourceNatIp, vpc.getName(), vpc.getUuid())); + return null; + } + return requestedIp; + } + protected Integer validateMtu(VpcVO vpcToUpdate, Integer mtu) { Long zoneId = vpcToUpdate.getZoneId(); if (mtu == null || NetworkService.AllowUsersToSpecifyVRMtu.valueIn(zoneId)) { @@ -1373,6 +1459,13 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis return success; } + @Override + public Pair, Integer> listVpcs(ListVPCsCmd cmd) { + return listVpcs(cmd.getId(), cmd.getVpcName(), cmd.getDisplayText(), cmd.getSupportedServices(), cmd.getCidr(), cmd.getVpcOffId(), + cmd.getState(), cmd.getAccountName(), cmd.getDomainId(), cmd.getKeyword(), cmd.getStartIndex(), cmd.getPageSizeVal(), + cmd.getZoneId(), cmd.isRecursive(), cmd.listAll(), cmd.getRestartRequired(), cmd.getTags(), cmd.getProjectId(), + cmd.getDisplay()); + } @Override public Pair, Integer> listVpcs(final Long id, final String vpcName, final String displayText, final List supportedServicesStr, final String cidr, final Long vpcOffId, final String state, final String accountName, Long domainId, final String keyword, final Long startIndex, final Long pageSizeVal, @@ -1476,7 +1569,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis final boolean listBySupportedServices = supportedServicesStr != null && !supportedServicesStr.isEmpty() && !vpcs.isEmpty(); if (listBySupportedServices) { - final List supportedVpcs = new ArrayList(); + final List supportedVpcs = new ArrayList<>(); Service[] supportedServices = null; if (listBySupportedServices) { @@ -1501,22 +1594,20 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis final List wPagination = StringUtils.applyPagination(supportedVpcs, startIndex, pageSizeVal); if (wPagination != null) { - final Pair, Integer> listWPagination = new Pair, Integer>(wPagination, supportedVpcs.size()); - return listWPagination; + return new Pair<>(wPagination, supportedVpcs.size()); } - return new Pair, Integer>(supportedVpcs, supportedVpcs.size()); + return new Pair<>(supportedVpcs, supportedVpcs.size()); } else { final List wPagination = StringUtils.applyPagination(vpcs, startIndex, pageSizeVal); if (wPagination != null) { - final Pair, Integer> listWPagination = new Pair, Integer>(wPagination, vpcs.size()); - return listWPagination; + return new Pair<>(wPagination, vpcs.size()); } - return new Pair, Integer>(vpcs, vpcs.size()); + return new Pair<>(vpcs, vpcs.size()); } } protected List getSupportedServices() { - final List services = new ArrayList(); + final List services = new ArrayList<>(); services.add(Network.Service.Dhcp); services.add(Network.Service.Dns); services.add(Network.Service.UserData); diff --git a/server/src/test/java/com/cloud/configuration/ConfigurationManagerTest.java b/server/src/test/java/com/cloud/configuration/ConfigurationManagerTest.java index 715924d5c54..5d1986e68a9 100644 --- a/server/src/test/java/com/cloud/configuration/ConfigurationManagerTest.java +++ b/server/src/test/java/com/cloud/configuration/ConfigurationManagerTest.java @@ -37,6 +37,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; +import java.util.Set; import java.util.UUID; import org.apache.cloudstack.api.command.admin.network.CreateGuestNetworkIpv6PrefixCmd; @@ -547,16 +548,68 @@ public class ConfigurationManagerTest { assertThat(configurationMgr.searchForNetworkOfferings(cmd).second(), is(2)); } + @Test + public void validateEmptySourceNatServiceCapablitiesTest() { + Map sourceNatServiceCapabilityMap = new HashMap<>(); + + configurationMgr.validateSourceNatServiceCapablities(sourceNatServiceCapabilityMap); + } + + @Test + public void validateInvalidSourceNatTypeForSourceNatServiceCapablitiesTest() { + Map sourceNatServiceCapabilityMap = new HashMap<>(); + sourceNatServiceCapabilityMap.put(Capability.SupportedSourceNatTypes, "perDomain"); + + boolean caught = false; + try { + configurationMgr.validateSourceNatServiceCapablities(sourceNatServiceCapabilityMap); + } catch (InvalidParameterValueException e) { + Assert.assertTrue(e.getMessage(), e.getMessage().contains("Either peraccount or perzone source NAT type can be specified for SupportedSourceNatTypes")); + caught = true; + } + Assert.assertTrue("should not be accepted", caught); + } + + @Test + public void validateInvalidBooleanValueForSourceNatServiceCapablitiesTest() { + Map sourceNatServiceCapabilityMap = new HashMap<>(); + sourceNatServiceCapabilityMap.put(Capability.RedundantRouter, "maybe"); + + boolean caught = false; + try { + configurationMgr.validateSourceNatServiceCapablities(sourceNatServiceCapabilityMap); + } catch (InvalidParameterValueException e) { + Assert.assertTrue(e.getMessage(), e.getMessage().contains("Unknown specified value for RedundantRouter")); + caught = true; + } + Assert.assertTrue("should not be accepted", caught); + } + + @Test + public void validateInvalidCapabilityForSourceNatServiceCapablitiesTest() { + Map sourceNatServiceCapabilityMap = new HashMap<>(); + sourceNatServiceCapabilityMap.put(Capability.ElasticIp, "perDomain"); + + boolean caught = false; + try { + configurationMgr.validateSourceNatServiceCapablities(sourceNatServiceCapabilityMap); + } catch (InvalidParameterValueException e) { + Assert.assertTrue(e.getMessage(), e.getMessage().contains("Only SupportedSourceNatTypes, Network.Capability[name=RedundantRouter] capabilities can be specified for source nat service")); + caught = true; + } + Assert.assertTrue("should not be accepted", caught); + } + @Test public void validateEmptyStaticNatServiceCapablitiesTest() { - Map staticNatServiceCapabilityMap = new HashMap(); + Map staticNatServiceCapabilityMap = new HashMap<>(); configurationMgr.validateStaticNatServiceCapablities(staticNatServiceCapabilityMap); } @Test public void validateInvalidStaticNatServiceCapablitiesTest() { - Map staticNatServiceCapabilityMap = new HashMap(); + Map staticNatServiceCapabilityMap = new HashMap<>(); staticNatServiceCapabilityMap.put(Capability.AssociatePublicIP, "Frue and Talse"); boolean caught = false; @@ -569,9 +622,43 @@ public class ConfigurationManagerTest { Assert.assertTrue("should not be accepted", caught); } + @Test + public void isRedundantRouter() { + Map> serviceCapabilityMap = new HashMap<>(); + Map sourceNatServiceCapabilityMap = new HashMap<>(); + sourceNatServiceCapabilityMap.put(Capability.SupportedSourceNatTypes, "peraccount"); + sourceNatServiceCapabilityMap.put(Capability.RedundantRouter, "true"); + Assert.assertTrue(configurationMgr.isRedundantRouter(serviceCapabilityMap, sourceNatServiceCapabilityMap)); + } + + @Test + public void isSharedSourceNat() { + Map> serviceCapabilityMap = new HashMap<>(); + Map sourceNatServiceCapabilityMap = new HashMap<>(); + sourceNatServiceCapabilityMap.put(Capability.SupportedSourceNatTypes, "perzone"); + Assert.assertTrue(configurationMgr.isSharedSourceNat(serviceCapabilityMap, sourceNatServiceCapabilityMap)); + } + + @Test + public void isNotSharedSourceNat() { + Map> serviceCapabilityMap = new HashMap<>(); + Map sourceNatServiceCapabilityMap = new HashMap<>(); + sourceNatServiceCapabilityMap.put(Capability.SupportedSourceNatTypes, "peraccount"); + Assert.assertFalse(configurationMgr.isSharedSourceNat(serviceCapabilityMap, sourceNatServiceCapabilityMap)); + } + + @Test + public void sourceNatCapabilitiesContainValidValues() { + Map sourceNatServiceCapabilityMap = new HashMap<>(); + sourceNatServiceCapabilityMap.put(Capability.SupportedSourceNatTypes, "peraccount"); + sourceNatServiceCapabilityMap.put(Capability.RedundantRouter, "True"); + + Assert.assertTrue(configurationMgr.sourceNatCapabilitiesContainValidValues(sourceNatServiceCapabilityMap)); + } + @Test public void validateTTStaticNatServiceCapablitiesTest() { - Map staticNatServiceCapabilityMap = new HashMap(); + Map staticNatServiceCapabilityMap = new HashMap<>(); staticNatServiceCapabilityMap.put(Capability.AssociatePublicIP, "true and Talse"); staticNatServiceCapabilityMap.put(Capability.ElasticIp, "True"); @@ -580,7 +667,7 @@ public class ConfigurationManagerTest { @Test public void validateFTStaticNatServiceCapablitiesTest() { - Map staticNatServiceCapabilityMap = new HashMap(); + Map staticNatServiceCapabilityMap = new HashMap<>(); staticNatServiceCapabilityMap.put(Capability.AssociatePublicIP, "false"); staticNatServiceCapabilityMap.put(Capability.ElasticIp, "True"); @@ -589,7 +676,7 @@ public class ConfigurationManagerTest { @Test public void validateTFStaticNatServiceCapablitiesTest() { - Map staticNatServiceCapabilityMap = new HashMap(); + Map staticNatServiceCapabilityMap = new HashMap<>(); staticNatServiceCapabilityMap.put(Capability.AssociatePublicIP, "true and Talse"); staticNatServiceCapabilityMap.put(Capability.ElasticIp, "false"); @@ -608,7 +695,7 @@ public class ConfigurationManagerTest { @Test public void validateFFStaticNatServiceCapablitiesTest() { - Map staticNatServiceCapabilityMap = new HashMap(); + Map staticNatServiceCapabilityMap = new HashMap<>(); staticNatServiceCapabilityMap.put(Capability.AssociatePublicIP, "false"); staticNatServiceCapabilityMap.put(Capability.ElasticIp, "False"); diff --git a/server/src/test/java/com/cloud/network/IpAddressManagerTest.java b/server/src/test/java/com/cloud/network/IpAddressManagerTest.java index 50ad62e0543..ef5ad8c0967 100644 --- a/server/src/test/java/com/cloud/network/IpAddressManagerTest.java +++ b/server/src/test/java/com/cloud/network/IpAddressManagerTest.java @@ -23,11 +23,13 @@ import static org.mockito.Matchers.anyLong; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Vector; import com.cloud.user.Account; import org.junit.Assert; @@ -230,4 +232,14 @@ public class IpAddressManagerTest { return network; } + @Test + public void updateSourceNatIpAddress() throws Exception { + IPAddressVO requestedIp = Mockito.mock(IPAddressVO.class); + IPAddressVO oldIp = Mockito.mock(IPAddressVO.class); + List userIps = new Vector<>(); + userIps.add(oldIp); + ipAddressManager.updateSourceNatIpAddress(requestedIp, userIps); + verify(requestedIp).setSourceNat(true); + verify(oldIp).setSourceNat(false); + } } diff --git a/server/src/test/java/com/cloud/network/NetworkServiceImplTest.java b/server/src/test/java/com/cloud/network/NetworkServiceImplTest.java index fcf7ad3665a..8235395174e 100644 --- a/server/src/test/java/com/cloud/network/NetworkServiceImplTest.java +++ b/server/src/test/java/com/cloud/network/NetworkServiceImplTest.java @@ -17,8 +17,10 @@ package com.cloud.network; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; @@ -34,6 +36,7 @@ import java.util.List; import java.util.Map; import java.util.UUID; +import com.cloud.exception.InsufficientAddressCapacityException; import org.apache.cloudstack.alert.AlertService; import org.apache.cloudstack.api.command.user.network.CreateNetworkCmd; import org.apache.cloudstack.api.command.user.network.UpdateNetworkCmd; @@ -81,7 +84,6 @@ import com.cloud.offerings.dao.NetworkOfferingServiceMapDao; import com.cloud.org.Grouping; import com.cloud.user.Account; import com.cloud.user.AccountManager; -import com.cloud.user.AccountManagerImpl; import com.cloud.user.AccountService; import com.cloud.user.AccountVO; import com.cloud.user.User; @@ -122,8 +124,6 @@ public class NetworkServiceImplTest { @Mock NetworkOfferingServiceMapDao networkOfferingServiceMapDao; @Mock - AccountManager accountMgr; - @Mock EntityManager entityMgr; @Mock NetworkService networkService; @@ -162,8 +162,8 @@ public class NetworkServiceImplTest { @Mock ServiceOfferingVO serviceOfferingVoMock; - @InjectMocks - AccountManagerImpl accountManagerImpl; + @Mock + IpAddressManager ipAddressManager; @Mock ConfigKey privateMtuKey; @Mock @@ -223,7 +223,7 @@ public class NetworkServiceImplTest { service._networkOfferingDao = networkOfferingDao; service._physicalNetworkDao = physicalNetworkDao; service._dcDao = dcDao; - service._accountMgr = accountMgr; + service._accountMgr = accountManager; service._networkMgr = networkManager; service.alertManager = alertManager; service._configMgr = configMgr; @@ -236,6 +236,7 @@ public class NetworkServiceImplTest { service.routerDao = routerDao; service.commandSetupHelper = commandSetupHelper; service.networkHelper = networkHelper; + service._ipAddrMgr = ipAddressManager; PowerMockito.mockStatic(CallContext.class); CallContext callContextMock = PowerMockito.mock(CallContext.class); PowerMockito.when(CallContext.current()).thenReturn(callContextMock); @@ -248,8 +249,8 @@ public class NetworkServiceImplTest { Mockito.when(networkOfferingDao.findById(1L)).thenReturn(offering); Mockito.when(physicalNetworkDao.findById(Mockito.anyLong())).thenReturn(phyNet); Mockito.when(dcDao.findById(Mockito.anyLong())).thenReturn(dc); - Mockito.lenient().doNothing().when(accountMgr).checkAccess(accountMock, networkOffering, dc); - Mockito.when(accountMgr.isRootAdmin(accountMock.getId())).thenReturn(true); + Mockito.lenient().doNothing().when(accountManager).checkAccess(accountMock, networkOffering, dc); + Mockito.when(accountManager.isRootAdmin(accountMock.getId())).thenReturn(true); } @Test @@ -352,6 +353,7 @@ public class NetworkServiceImplTest { ReflectionTestUtils.setField(createNetworkCmd, "privateMtu", privateMtu); ReflectionTestUtils.setField(createNetworkCmd, "physicalNetworkId", null); Mockito.when(offering.isSystemOnly()).thenReturn(false); + Mockito.when(dc.getId()).thenReturn(1L); Mockito.when(dc.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled); Map networkProvidersMap = new HashMap(); Mockito.when(networkManager.finalizeServicesAndProvidersForNetwork(ArgumentMatchers.any(NetworkOffering.class), anyLong())).thenReturn(networkProvidersMap); @@ -409,6 +411,7 @@ public class NetworkServiceImplTest { ReflectionTestUtils.setField(createNetworkCmd, "privateMtu", privateMtu); ReflectionTestUtils.setField(createNetworkCmd, "physicalNetworkId", null); ReflectionTestUtils.setField(createNetworkCmd, "vpcId", 1L); + Mockito.when(dc.getId()).thenReturn(1L); Mockito.when(configMgr.isOfferingForVpc(offering)).thenReturn(true); Mockito.when(vpcDao.findById(anyLong())).thenReturn(vpc); @@ -562,7 +565,7 @@ public class NetworkServiceImplTest { } } - @Test(expected = InvalidParameterValueException.class) + @Test(expected = CloudRuntimeException.class) public void testCreateNetworkDnsOfferingServiceFailure() { registerCallContext(); CreateNetworkCmd cmd = Mockito.mock(CreateNetworkCmd.class); @@ -577,7 +580,7 @@ public class NetworkServiceImplTest { } } - @Test(expected = InvalidParameterValueException.class) + @Test(expected = CloudRuntimeException.class) public void testCreateIp4NetworkIp6DnsFailure() { registerCallContext(); CreateNetworkCmd cmd = Mockito.mock(CreateNetworkCmd.class); @@ -770,4 +773,143 @@ public class NetworkServiceImplTest { networkServiceImplMock.validateIfServiceOfferingIsActiveAndSystemVmTypeIsDomainRouter(1l); } + + @Test + public void validateNotSharedNetworkRouterIPv4() { + NetworkOffering ntwkOff = Mockito.mock(NetworkOffering.class); + when(ntwkOff.getGuestType()).thenReturn(Network.GuestType.L2); + service.validateSharedNetworkRouterIPs(null, null, null, null, null, null, null, null, null, ntwkOff); + } + + @Test + public void validateSharedNetworkRouterIPs() { + String startIP = "10.0.16.2"; + String endIP = "10.0.16.100"; + String routerIPv4 = "10.0.16.100"; + String routerPv6 = "fd17:ac56:1234:2000::fb"; + String startIPv6 = "fd17:ac56:1234:2000::1"; + String endIPv6 = "fd17:ac56:1234:2000::fc"; + NetworkOffering ntwkOff = Mockito.mock(NetworkOffering.class); + when(ntwkOff.getGuestType()).thenReturn(Network.GuestType.Shared); + service.validateSharedNetworkRouterIPs(IP4_GATEWAY, startIP, endIP, IP4_NETMASK, routerIPv4, routerPv6, startIPv6, endIPv6, IP6_CIDR, ntwkOff); + } + + @Test + public void validateSharedNetworkWrongRouterIPv4() { + String startIP = "10.0.16.2"; + String endIP = "10.0.16.100"; + String routerIPv4 = "10.0.16.101"; + String routerPv6 = "fd17:ac56:1234:2000::fb"; + String startIPv6 = "fd17:ac56:1234:2000::1"; + String endIPv6 = "fd17:ac56:1234:2000::fc"; + NetworkOffering ntwkOff = Mockito.mock(NetworkOffering.class); + when(ntwkOff.getGuestType()).thenReturn(Network.GuestType.Shared); + boolean passing = false; + try { + service.validateSharedNetworkRouterIPs(IP4_GATEWAY, startIP, endIP, IP4_NETMASK, routerIPv4, routerPv6, startIPv6, endIPv6, IP6_CIDR, ntwkOff); + } catch (CloudRuntimeException e) { + Assert.assertTrue(e.getMessage().contains("Router IPv4 IP provided is not within the specified range: ")); + passing = true; + } + Assert.assertTrue(passing); + } + + @Test + public void validateSharedNetworkNoEndOfIPv6Range() { + String startIP = null; + String endIP = null; + String routerIPv4 = null; + String routerPv6 = "fd17:ac56:1234:2000::1"; + String startIPv6 = "fd17:ac56:1234:2000::1"; + String endIPv6 = null; + NetworkOffering ntwkOff = Mockito.mock(NetworkOffering.class); + when(ntwkOff.getGuestType()).thenReturn(Network.GuestType.Shared); + service.validateSharedNetworkRouterIPs(IP4_GATEWAY, startIP, endIP, IP4_NETMASK, routerIPv4, routerPv6, startIPv6, endIPv6, IP6_CIDR, ntwkOff); + } + + @Test + public void validateSharedNetworkIPv6RouterNotInRange() { + String routerIPv4 = null; + String routerIPv6 = "fd17:ac56:1234:2001::1"; + NetworkOffering ntwkOff = Mockito.mock(NetworkOffering.class); + when(ntwkOff.getGuestType()).thenReturn(Network.GuestType.Shared); + boolean passing = true; + try { + service.validateSharedNetworkRouterIPs(IP4_GATEWAY, null, null, IP4_NETMASK, routerIPv4, routerIPv6, null, null, IP6_CIDR, ntwkOff); + passing = false; + } catch (CloudRuntimeException e) { + Assert.assertTrue(e.getMessage().contains("Router IPv6 address provided is not with the network range")); + } + Assert.assertTrue(passing); + } + + @Test + public void invalidateSharedNetworkIPv6RouterAddress() { + String routerIPv6 = "fd17:ac56:1234:2000::fg"; + NetworkOffering ntwkOff = Mockito.mock(NetworkOffering.class); + when(ntwkOff.getGuestType()).thenReturn(Network.GuestType.Shared); + boolean passing = false; + try { + service.validateSharedNetworkRouterIPs(IP4_GATEWAY, null, null, IP4_NETMASK, null, routerIPv6, null, null, IP6_CIDR, ntwkOff); + } catch (CloudRuntimeException e) { + Assert.assertTrue(e.getMessage().contains("Router IPv6 address provided is of incorrect format")); + passing = true; + } + Assert.assertTrue(passing); + } + + @Test + public void invalidateSharedNetworkIPv4RouterAddress() { + String routerIPv4 = "10.100.1000.1"; + NetworkOffering ntwkOff = Mockito.mock(NetworkOffering.class); + when(ntwkOff.getGuestType()).thenReturn(Network.GuestType.Shared); + boolean passing = false; + try { + service.validateSharedNetworkRouterIPs(IP4_GATEWAY, null, null, IP4_NETMASK, routerIPv4, null, null, null, IP6_CIDR, ntwkOff); + } catch (CloudRuntimeException e) { + Assert.assertTrue(e.getMessage().contains("Router IPv4 IP provided is of incorrect format")); + passing = true; + } + Assert.assertTrue(passing); + } + + @Test + public void checkAndDontSetSourceNatIp() { + CreateNetworkCmd cmd = new CreateNetworkCmd(); + try { + service.checkAndSetRouterSourceNatIp(account, cmd, null); + } catch (InsufficientAddressCapacityException | ResourceAllocationException e) { + Assert.fail(e.getMessage()); + } + } + + @Test + public void checkAndSetSourceNatIp() { + String srcNatIp = "10.100.1000.10000"; + Long networkOfferingId = 2l; + Long zoneId = 3l; + registerCallContext(); + ReflectionTestUtils.setField(createNetworkCmd, "networkOfferingId", networkOfferingId); + ReflectionTestUtils.setField(createNetworkCmd, "sourceNatIP", srcNatIp); + ReflectionTestUtils.setField(createNetworkCmd, "zoneId", zoneId); + ReflectionTestUtils.setField(createNetworkCmd, "physicalNetworkId", null); + NetworkVO networkVO = Mockito.spy(NetworkVO.class); + IpAddress ipAddress = Mockito.mock(IPAddressVO.class); + NetworkOffering ntwkOff = Mockito.mock(NetworkOffering.class); + Long networkId = 7l; + when(networkVO.getId()).thenReturn(networkId); + when(networkVO.getGuestType()).thenReturn(Network.GuestType.Isolated); + when(networkDao.findById(networkId)).thenReturn(networkVO); + when(entityMgr.findById(NetworkOffering.class, networkOfferingId)).thenReturn(ntwkOff); + when(entityMgr.findById(eq(DataCenter.class), anyLong())).thenReturn(dc); + when(ipAddress.getId()).thenReturn(5l); + when(networkVO.getId()).thenReturn(networkId); + when(networkVO.getGuestType()).thenReturn(Network.GuestType.Isolated); + try { + when(ipAddressManager.allocateIp(any(), anyBoolean(), any(), anyLong(), any(), any(), eq(srcNatIp))).thenReturn(ipAddress); + service.checkAndSetRouterSourceNatIp(account, createNetworkCmd, networkVO); + } catch (InsufficientAddressCapacityException | ResourceAllocationException e) { + Assert.fail(e.getMessage()); + } + } } diff --git a/server/src/test/java/com/cloud/network/vpc/VpcManagerImplTest.java b/server/src/test/java/com/cloud/network/vpc/VpcManagerImplTest.java index c820026309d..13cee3ab3df 100644 --- a/server/src/test/java/com/cloud/network/vpc/VpcManagerImplTest.java +++ b/server/src/test/java/com/cloud/network/vpc/VpcManagerImplTest.java @@ -42,8 +42,10 @@ import java.util.UUID; import com.cloud.alert.AlertManager; import com.cloud.network.NetworkService; +import com.cloud.network.dao.FirewallRulesDao; import org.apache.cloudstack.acl.SecurityChecker; import org.apache.cloudstack.alert.AlertService; +import org.apache.cloudstack.api.command.user.vpc.UpdateVPCCmd; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.junit.After; @@ -149,6 +151,8 @@ public class VpcManagerImplTest { AlertManager alertManager; @Mock NetworkService networkServiceMock; + @Mock + FirewallRulesDao firewallDao; public static final long ACCOUNT_ID = 1; private AccountVO account; @@ -200,6 +204,7 @@ public class VpcManagerImplTest { manager._vpcOffDao = vpcOfferingDao; manager._dcDao = dataCenterDao; manager._ntwkSvc = networkServiceMock; + manager._firewallDao = firewallDao; CallContext.register(Mockito.mock(User.class), Mockito.mock(Account.class)); registerCallContext(); } @@ -354,9 +359,10 @@ public class VpcManagerImplTest { } @Test - public void testUpdateVpcNetwork() throws ResourceUnavailableException { + public void testUpdateVpcNetwork() throws ResourceUnavailableException, InsufficientCapacityException { long vpcId = 1L; Integer publicMtu = 1450; + String sourceNatIp = "1.2.3.4"; Account accountMock = Mockito.mock(Account.class); VpcVO vpcVO = new VpcVO(); @@ -398,9 +404,31 @@ public class VpcManagerImplTest { Mockito.when(networkDao.update(anyLong(), any())).thenReturn(true); Mockito.when(vpcDao.update(vpcId, vpcVO)).thenReturn(true); - manager.updateVpc(vpcId, null, null, null, true, publicMtu); - Assert.assertEquals(publicMtu, vpcVO.getPublicMtu()); + UpdateVPCCmd cmd = Mockito.mock(UpdateVPCCmd.class); + Mockito.when(cmd.getId()).thenReturn(vpcId); + Mockito.when(cmd.getVpcName()).thenReturn(null); + Mockito.when(cmd.getDisplayText()).thenReturn(null); + Mockito.when(cmd.getCustomId()).thenReturn(null); + Mockito.when(cmd.isDisplayVpc()).thenReturn(true); + Mockito.when(cmd.getPublicMtu()).thenReturn(publicMtu); + Mockito.when(cmd.getSourceNatIP()).thenReturn(sourceNatIp); + manager.updateVpc(cmd); + Assert.assertEquals(publicMtu, vpcVO.getPublicMtu()); + } + + @Test + public void verifySourceNatIp() { + String sourceNatIp = "1.2.3.4"; + VpcVO vpcVO = Mockito.mock(VpcVO.class); //new VpcVO(1l, "vpc", null, 10l, 1l, 1l, "10.1.0.0/16", null, false, false, false, null, null, null, null); + Mockito.when(vpcVO.getId()).thenReturn(1l); + IPAddressVO requestedIp = Mockito.mock(IPAddressVO.class);//new IPAddressVO(new Ip(sourceNatIp), 1l, 1l, 1l, true); + Mockito.when(ipAddressDao.findByIp(sourceNatIp)).thenReturn(requestedIp); + Mockito.when(requestedIp.getVpcId()).thenReturn(1l); + Mockito.when(requestedIp.getVpcId()).thenReturn(1l); + Mockito.when(firewallDao.countRulesByIpId(1l)).thenReturn(0l); + Assert.assertNull(manager.validateSourceNatip(vpcVO, null)); + Assert.assertEquals(requestedIp, manager.validateSourceNatip(vpcVO, sourceNatIp)); } @Test diff --git a/test/integration/smoke/test_network.py b/test/integration/smoke/test_network.py index 0c37c0836b8..56b434f3241 100644 --- a/test/integration/smoke/test_network.py +++ b/test/integration/smoke/test_network.py @@ -72,9 +72,6 @@ logger.addHandler(stream_handler) class TestPublicIP(cloudstackTestCase): - def setUp(self): - self.apiclient = self.testClient.getApiClient() - @classmethod def setUpClass(cls): testClient = super(TestPublicIP, cls).getClsTestClient() @@ -85,6 +82,7 @@ class TestPublicIP(cloudstackTestCase): cls.domain = get_domain(cls.apiclient) cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) cls.services['mode'] = cls.zone.networktype + cls._cleanup = [] # Create Accounts & networks cls.account = Account.create( cls.apiclient, @@ -92,18 +90,21 @@ class TestPublicIP(cloudstackTestCase): admin=True, domainid=cls.domain.id ) + cls._cleanup.append(cls.account) cls.user = Account.create( cls.apiclient, cls.services["account"], domainid=cls.domain.id ) + cls._cleanup.append(cls.user) cls.services["network"]["zoneid"] = cls.zone.id cls.network_offering = NetworkOffering.create( cls.apiclient, cls.services["network_offering"], ) + cls._cleanup.append(cls.network_offering) # Enable Network offering cls.network_offering.update(cls.apiclient, state='Enabled') @@ -114,17 +115,20 @@ class TestPublicIP(cloudstackTestCase): cls.account.name, cls.account.domainid ) + cls._cleanup.append(cls.account_network) cls.user_network = Network.create( cls.apiclient, cls.services["network"], cls.user.name, cls.user.domainid ) + cls._cleanup.append(cls.user_network) cls.service_offering = ServiceOffering.create( cls.apiclient, cls.services["service_offerings"]["tiny"], ) + cls._cleanup.append(cls.service_offering) cls.hypervisor = testClient.getHypervisorInfo() cls.template = get_test_template( @@ -146,6 +150,7 @@ class TestPublicIP(cloudstackTestCase): networkids=cls.account_network.id, serviceofferingid=cls.service_offering.id ) + cls._cleanup.append(cls.account_vm) cls.user_vm = VirtualMachine.create( cls.apiclient, @@ -156,6 +161,7 @@ class TestPublicIP(cloudstackTestCase): networkids=cls.user_network.id, serviceofferingid=cls.service_offering.id ) + cls._cleanup.append(cls.user_vm) # Create Source NAT IP addresses PublicIPAddress.create( @@ -170,25 +176,11 @@ class TestPublicIP(cloudstackTestCase): cls.zone.id, cls.user.domainid ) - cls._cleanup = [ - cls.account_vm, - cls.user_vm, - cls.account_network, - cls.user_network, - cls.account, - cls.user, - cls.network_offering - ] return @classmethod def tearDownClass(cls): - try: - # Cleanup resources used - cleanup_resources(cls.apiclient, cls._cleanup) - except Exception as e: - raise Exception("Warning: Exception during cleanup : %s" % e) - return + super(TestPublicIP, cls).tearDownClass() @attr(tags=["advanced", "advancedns", "smoke", "dvs"], required_hardware="false") def test_public_ip_admin_account(self): diff --git a/test/integration/smoke/test_set_sourcenat.py b/test/integration/smoke/test_set_sourcenat.py new file mode 100644 index 00000000000..6a3e1ee99cb --- /dev/null +++ b/test/integration/smoke/test_set_sourcenat.py @@ -0,0 +1,274 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# 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. +""" BVT tests for Network Life Cycle +""" +# Import Local Modules +from marvin.codes import (FAILED, STATIC_NAT_RULE, LB_RULE, + NAT_RULE, PASS) +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.lib.base import (Account, + VPC, + VpcOffering, + ServiceOffering, + PublicIPAddress, + Network, + NetworkOffering) +from marvin.lib.common import (get_domain, + get_free_vlan, + get_zone, + get_template, + get_test_template, + list_publicIP) + +from nose.plugins.attrib import attr +# Import System modules +import time +import logging + +_multiprocess_shared_ = True + +logger = logging.getLogger('TestSetSourceNatIp') +stream_handler = logging.StreamHandler() +logger.setLevel(logging.DEBUG) +logger.addHandler(stream_handler) + + +class TestSetSourceNatIp(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + testClient = super(TestSetSourceNatIp, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.services = testClient.getParsedTestDataConfig() + + # Get Zone, Domain and templates + cls.domain = get_domain(cls.apiclient) + cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) + cls.services['mode'] = cls.zone.networktype + cls._cleanup = [] + # Create Accounts & networks + cls.account = Account.create( + cls.apiclient, + cls.services["account"], + admin=True, + domainid=cls.domain.id + ) + cls._cleanup.append(cls.account) + + cls.services["network"]["zoneid"] = cls.zone.id + + cls.vpc_offering = VpcOffering.create( + cls.apiclient, + cls.services["vpc_offering"], + ) + cls._cleanup.append(cls.vpc_offering) + cls.vpc_offering.update(cls.apiclient, state='Enabled') + cls.services["vpc"]["vpcoffering"] = cls.vpc_offering.id + + cls.network_offering = NetworkOffering.create( + cls.apiclient, + cls.services["network_offering"], + ) + cls._cleanup.append(cls.network_offering) + # Enable Network offering + cls.network_offering.update(cls.apiclient, state='Enabled') + + cls.services["network"]["networkoffering"] = cls.network_offering.id + + cls.service_offering = ServiceOffering.create( + cls.apiclient, + cls.services["service_offerings"]["tiny"], + ) + cls._cleanup.append(cls.service_offering) + + cls.hypervisor = testClient.getHypervisorInfo() + cls.template = get_test_template( + cls.apiclient, + cls.zone.id, + cls.hypervisor + ) + if cls.template == FAILED: + assert False, "get_test_template() failed to return template" + + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + network = Network.create( + cls.apiclient, + cls.services["network"], + cls.account.name, + cls.account.domainid + ) + cls._cleanup.append(network) + ip_address = PublicIPAddress.create( + cls.apiclient, + cls.account.name, + cls.zone.id, + cls.account.domainid + ) + cls._cleanup.append(ip_address) + cls.ip_to_want = ip_address.ipaddress.ipaddress + cls.debug(f'==== my local ip: {cls.ip_to_want}') + ip_address.delete(cls.apiclient) + cls._cleanup.remove(ip_address) + network.delete(cls.apiclient) + cls._cleanup.remove(network) + return + + @classmethod + def tearDownClass(cls): + super(TestSetSourceNatIp, cls).tearDownClass() + + def setUp(self): + self.cleanup = [] + + def tearDown(self): + super(TestSetSourceNatIp, self).tearDown() + + @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="false") + def test_01_create_network_with_specified_source_nat_ip_address(self): + """ + For creation of network witjh a specified address + """ + + + self.services["network"]["networkoffering"] = self.network_offering.id + network = Network.create( + self.apiclient, + self.services["network"], + self.account.name, + self.account.domainid, + sourcenatipaddress = self.ip_to_want + ) + self.cleanup.append(network) + + self.validate_source_nat(network=network, ip=self.ip_to_want) + + + @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="false") + def test_02_change_source_nat_ip_address_for_network(self): + """ + Test changing a networks source NAT IP address + """ + network = Network.create( + self.apiclient, + self.services["network"], + self.account.name, + self.account.domainid, + sourcenatipaddress = self.ip_to_want + ) + self.cleanup.append(network) + second_ip = PublicIPAddress.create( + self.apiclient, + self.account.name, + self.zone.id, + self.account.domainid, + networkid=network.id + ) + self.cleanup.append(second_ip) + self.debug(f'==== second ip: {second_ip.ipaddress.ipaddress}') + + self.validate_source_nat(network=network, ip=self.ip_to_want) + + network.update(self.apiclient, sourcenatipaddress=second_ip.ipaddress.ipaddress) + + self.validate_source_nat(network=network, ip=second_ip.ipaddress.ipaddress) + + return + + + @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="false") + def test_03_create_vpc_with_specified_source_nat_ip_address(self): + """ + Test for creation of a VPC with a specified address + """ + + vpc = VPC.create( + self.apiclient, + self.services["vpc"], + self.vpc_offering.id, + self.zone.id, + sourcenatipaddress = self.ip_to_want + ) + self.cleanup.append(vpc) + + self.validate_source_nat(vpc=vpc, ip=self.ip_to_want) + + @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="false") + def test_04_change_source_nat_ip_address_for_vpc(self): + """ + Test changing a networks source NAT IP address + """ + vpc = VPC.create( + self.apiclient, + self.services["vpc"], + self.vpc_offering.id, + self.zone.id, + account=self.account.name, + domainid = self.account.domainid, + sourcenatipaddress = self.ip_to_want + ) + self.cleanup.append(vpc) + second_ip = PublicIPAddress.create( + self.apiclient, + self.account.name, + self.zone.id, + self.account.domainid, + vpcid=vpc.id + ) + self.debug(f'==== second ip: {second_ip.ipaddress.ipaddress}') + + self.validate_source_nat(vpc=vpc, ip=self.ip_to_want) + + vpc.update(self.apiclient, sourcenatipaddress=second_ip.ipaddress.ipaddress) + + self.validate_source_nat(vpc=vpc, ip=second_ip.ipaddress.ipaddress) + + return + + + def validate_source_nat(self, network=None, vpc=None, ip=None): + list_pub_ip_addr_resp = None + if network: + list_pub_ip_addr_resp = list_publicIP( + self.apiclient, + associatednetworkid=network.id, + listall=True, + issourcenat=True + ) + elif vpc: + list_pub_ip_addr_resp = list_publicIP( + self.apiclient, + vpc=vpc.id, + listall=True, + issourcenat=True + ) + self.assertEqual( + isinstance(list_pub_ip_addr_resp, list), + True, + "Check list response returns a valid list" + ) + self.assertNotEqual( + len(list_pub_ip_addr_resp), + 0, + "Check if new IP Address is associated" + ) + self.debug(f'==== my result {list_pub_ip_addr_resp[0]}') + self.assertEqual( + list_pub_ip_addr_resp[0].ipaddress, + ip, + f"Check Correct IP Address is returned in the List, expected {ip} but got {list_pub_ip_addr_resp[0].ipaddress}" + ) diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index 46015f7d7b7..d6556eef39d 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -3485,7 +3485,8 @@ class Network: networkofferingid=None, projectid=None, subdomainaccess=None, zoneid=None, gateway=None, netmask=None, vpcid=None, aclid=None, vlan=None, - externalid=None, bypassvlanoverlapcheck=None, associatednetworkid=None, publicmtu=None, privatemtu=None): + externalid=None, bypassvlanoverlapcheck=None, associatednetworkid=None, publicmtu=None, privatemtu=None, + sourcenatipaddress=None): """Create Network for account""" cmd = createNetwork.createNetworkCmd() cmd.name = services["name"] @@ -3567,6 +3568,8 @@ class Network: cmd.publicmtu = publicmtu if privatemtu: cmd.privatemtu = privatemtu + if sourcenatipaddress: + cmd.sourcenatipaddress = sourcenatipaddress return Network(apiclient.createNetwork(cmd).__dict__) def delete(self, apiclient): diff --git a/ui/public/locales/el_GR.json b/ui/public/locales/el_GR.json index ffe6aaf99ea..a72981d8f1b 100644 --- a/ui/public/locales/el_GR.json +++ b/ui/public/locales/el_GR.json @@ -1578,6 +1578,8 @@ "label.shared": "Κοινόχρηστο", "label.sharedexecutable": "Κοινόχρηστο", "label.sharedmountpoint": "Κοινόχρηστο μέσο", +"label.sharedrouterip": "Διεύθυνση IPv4 του δρομολογητή στο κοινόχρηστο δίκτυο ", +"label.sharedrouteripv6": "Διεύθυνση IPv6 του δρομολογητή στο κοινόχρηστο δίκτυο", "label.sharewith": "Κοινή χρήση με", "label.showing": "Προβολή", "label.shrinkok": "Συρρίκνωση OK", diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 14e709b7cd3..43c426a61cb 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -165,6 +165,7 @@ "label.action.router.health.checks": "Get health checks result", "label.action.run.diagnostics": "Run diagnostics", "label.action.secure.host": "Provision host security keys", +"label.action.set.as.source.nat.ip": "make source NAT", "label.action.setup.2FA.user.auth": "Setup User Two Factor Authentication", "label.action.start.instance": "Start instance", "label.action.start.router": "Start router", @@ -915,6 +916,7 @@ "label.haenable": "HA enabled", "label.haprovider": "HA provider", "label.hardware": "Hardware", +"label.hasrules":"FW rules defined", "label.hastate": "HA state", "label.header.backup.schedule": "You can set up recurring backup schedules by selecting from the available options below and applying your policy preference.", "label.header.volume.snapshot": "You can set up recurring snapshot schedules by selecting from the available options below and applying your policy preference.", @@ -1668,8 +1670,8 @@ "label.router.health.check.success": "Success", "label.router.health.checks": "Health checks", "label.routercount": "Total of virtual routers", -"label.routerip": "IPv4 address for the VR in this shared network.", -"label.routeripv6": "IPv6 address for the VR in this shared network.", +"label.routerip": "IPv4 address for the VR in this network.", +"label.routeripv6": "IPv6 address for the VR in this network.", "label.resourcegroup": "Resource group", "label.routing.policy": "Routing policy", "label.routing.policy.terms": "Routing policy terms", @@ -1775,6 +1777,8 @@ "label.shared": "Shared", "label.sharedexecutable": "Shared", "label.sharedmountpoint": "SharedMountPoint", +"label.sharedrouterip": "IPv4 address for the VR in this shared network.", +"label.sharedrouteripv6": "IPv6 address for the VR in this shared network.", "label.sharewith": "Share with", "label.showing": "Showing", "label.shrinkok": "Shrink OK", @@ -1803,6 +1807,7 @@ "label.sourceipaddress": "Source IP address", "label.sourceipaddressnetworkid": "Network ID of source IP address", "label.sourcenat": "Source NAT", +"label.sourcenatipaddress": "Source NAT IP address", "label.sourcenatsupported": "Source NAT supported", "label.sourcenattype": "Supported source NAT type", "label.sourceport": "Source port", @@ -1854,6 +1859,7 @@ "label.startport": "Start port", "label.startquota": "Quota value", "label.state": "State", +"label.staticnat": "Static NAT", "label.static.routes": "Static routes", "label.status": "Status", "label.step.1": "Step 1", @@ -2888,6 +2894,8 @@ "message.setup.physical.network.during.zone.creation.basic": "When adding a basic zone, you can set up one physical network, which corresponds to a NIC on the hypervisor. The network carries several types of traffic.

You may also add other traffic types onto the physical network.", "message.shared.network.offering.warning": "Domain admins and regular users can only create shared networks from network offering with the setting specifyvlan=false. Please contact an administrator to create a network offering if this list is empty.", "message.shutdown.triggered": "A shutdown has been triggered. CloudStack will not accept new jobs", +"message.sourcenatip.change.warning": "WARNING: Changing the sourcenat IP address of the network will cause connectivity downtime for the VMs with NICs in the network.", +"message.sourcenatip.change.inhibited": "Changing the sourcenat to this IP of the network to this address is inhibited as firewall rules are defined for it. This can include port forwarding or load balancing rules.\n - If this is an isolated network, please use updateNetwork/click the edit button.\n - If this is a VPC, first clear all other rules for this address.", "message.specify.tag.key": "Please specify a tag key.", "message.specify.tag.value": "Please specify a tag value.", "message.step.2.continue": "Please select a service offering to continue.", diff --git a/ui/public/locales/ja_JP.json b/ui/public/locales/ja_JP.json index 19938b90647..f1518286f80 100644 --- a/ui/public/locales/ja_JP.json +++ b/ui/public/locales/ja_JP.json @@ -2046,6 +2046,8 @@ "label.shared": "共有", "label.sharedexecutable": "共有", "label.sharedmountpoint": "SharedMountPoint", + "label.sharedrouterip": "共有ネットワークのルーターのIPv4アドレス", + "label.sharedrouteripv6": "共有ネットワークのルーターのIPv6アドレス", "label.sharewith": "共有", "label.show.ingress.rule": "受信ルールの表示", "label.showing": "表示中", diff --git a/ui/public/locales/ko_KR.json b/ui/public/locales/ko_KR.json index 68c6fe9e6d6..ef7b4f49254 100644 --- a/ui/public/locales/ko_KR.json +++ b/ui/public/locales/ko_KR.json @@ -1373,6 +1373,8 @@ "label.shared": "shared", "label.sharedexecutable": "\uacf5\uc720", "label.sharedmountpoint": "\uacf5\uc720 \ub9c8\uc6b4\ud2b8 \ud3ec\uc778\ud2b8", +"label.sharedrouterip": "\uc11c\ube44\uc2a4\uc6a9 \ub124\ud2b8\uc6cc\ud06c\uc758 \ub77c\uc6b0\ud130\uc5d0 \ub300\ud55c IPv4 \uc8fc\uc18c", +"label.sharedrouteripv6": "\uc11c\ube44\uc2a4\uc6a9 \ub124\ud2b8\uc6cc\ud06c\uc758 \ub77c\uc6b0\ud130\uc5d0 \ub300\ud55c IPv6 \uc8fc\uc18c", "label.sharewith": "\uacf5\uc720", "label.showing": "\ubcf4\uae30", "label.shrinkok": "\ubcc0\uacbd \uc644\ub8cc", diff --git a/ui/public/locales/pt_BR.json b/ui/public/locales/pt_BR.json index 712d9f13623..40256c8e340 100644 --- a/ui/public/locales/pt_BR.json +++ b/ui/public/locales/pt_BR.json @@ -1468,6 +1468,8 @@ "label.shared": "Compatilhado", "label.sharedexecutable": "Compatilhado", "label.sharedmountpoint": "SharedMountPoint", +"label.sharedrouterip": "Endere\u00e7os IPv4 para o roteador dentro da rede compartilhada", +"label.sharedrouteripv6": "Endere\u00e7os IPv6 para o roteador dentro da rede compartilhada", "label.sharewith": "Compartilhar com", "label.showing": "Exibindo", "label.shrinkok": "Encolhimento OK", diff --git a/ui/public/locales/zh_CN.json b/ui/public/locales/zh_CN.json index 7ba77fdf468..49fe7830e11 100644 --- a/ui/public/locales/zh_CN.json +++ b/ui/public/locales/zh_CN.json @@ -2333,6 +2333,8 @@ "label.shared": "\u5DF2\u5171\u4EAB", "label.sharedexecutable": "\u5DF2\u5171\u4EAB", "label.sharedmountpoint": "\u5171\u4EAB\u6302\u8F7D\u70B9", + "label.sharedrouterip": "\u5171\u4EAB\u7F51\u7EDC\u4E2D\u8DEF\u7531\u5668\u7684 IPv4 \u5730\u5740", + "label.sharedrouteripv6": "\u5171\u4EAB\u7F51\u7EDC\u4E2D\u8DEF\u7531\u5668\u7684 IPv6 \u5730\u5740", "label.sharewith": "\u5206\u4EAB", "label.show.ingress.rule": "\u663E\u793A\u5165\u53E3\u89C4\u5219", diff --git a/ui/src/config/section/network.js b/ui/src/config/section/network.js index 661a7258288..26cd469a920 100644 --- a/ui/src/config/section/network.js +++ b/ui/src/config/section/network.js @@ -228,7 +228,7 @@ export default { icon: 'edit-outlined', label: 'label.edit', dataView: true, - args: ['name', 'displaytext', 'publicmtu'] + args: ['name', 'displaytext', 'publicmtu', 'sourcenatipaddress'] }, { api: 'restartVPC', diff --git a/ui/src/core/lazy_lib/icons_use.js b/ui/src/core/lazy_lib/icons_use.js index ec2d67deaf9..bf40d1b6356 100644 --- a/ui/src/core/lazy_lib/icons_use.js +++ b/ui/src/core/lazy_lib/icons_use.js @@ -16,6 +16,7 @@ // under the License. import { + AimOutlined, ApartmentOutlined, ApiOutlined, AppstoreOutlined, @@ -170,6 +171,7 @@ import renderIcon from '@/utils/renderIcon' export default { install: (app) => { + app.component('AimOutlined', AimOutlined) app.component('ApartmentOutlined', ApartmentOutlined) app.component('ApiOutlined', ApiOutlined) app.component('AppstoreOutlined', AppstoreOutlined) diff --git a/ui/src/views/network/CreateIsolatedNetworkForm.vue b/ui/src/views/network/CreateIsolatedNetworkForm.vue index 33502d67963..3ed219eb97d 100644 --- a/ui/src/views/network/CreateIsolatedNetworkForm.vue +++ b/ui/src/views/network/CreateIsolatedNetworkForm.vue @@ -291,6 +291,36 @@ + + + + + + + + + + + +
service.name === 'SourceNat') + return sourcenatService && sourcenatService.length === 1 + } + return false } }, methods: { @@ -596,7 +634,7 @@ export default { displayText: values.displaytext, networkOfferingId: this.selectedNetworkOffering.id } - var usefulFields = ['gateway', 'netmask', 'startip', 'endip', 'dns1', 'dns2', 'ip6dns1', 'ip6dns2', 'externalid', 'vpcid', 'vlan', 'networkdomain'] + var usefulFields = ['gateway', 'netmask', 'startip', 'startipv4', 'endip', 'endipv4', 'dns1', 'dns2', 'ip6dns1', 'ip6dns2', 'sourcenatipaddress', 'externalid', 'vpcid', 'vlan', 'networkdomain'] for (var field of usefulFields) { if (this.isValidTextValueForKey(values, field)) { params[field] = values[field] diff --git a/ui/src/views/network/CreateVpc.vue b/ui/src/views/network/CreateVpc.vue index 79d166e3ecb..363dd440562 100644 --- a/ui/src/views/network/CreateVpc.vue +++ b/ui/src/views/network/CreateVpc.vue @@ -155,6 +155,14 @@ + + + +