diff --git a/api/src/main/java/com/cloud/network/Network.java b/api/src/main/java/com/cloud/network/Network.java index 0846306f70f9..e41eb880ffd5 100644 --- a/api/src/main/java/com/cloud/network/Network.java +++ b/api/src/main/java/com/cloud/network/Network.java @@ -510,4 +510,6 @@ public void setIp6Address(String ip6Address) { Integer getPrivateMtu(); Integer getNetworkCidrSize(); + + boolean getKeepMacAddressOnPublicNic(); } diff --git a/api/src/main/java/com/cloud/network/NetworkProfile.java b/api/src/main/java/com/cloud/network/NetworkProfile.java index 2e8efb489308..d690344a0e38 100644 --- a/api/src/main/java/com/cloud/network/NetworkProfile.java +++ b/api/src/main/java/com/cloud/network/NetworkProfile.java @@ -385,6 +385,11 @@ public Integer getNetworkCidrSize() { return networkCidrSize; } + @Override + public boolean getKeepMacAddressOnPublicNic() { + return true; + } + @Override public String toString() { return String.format("NetworkProfile %s", diff --git a/api/src/main/java/com/cloud/network/vpc/Vpc.java b/api/src/main/java/com/cloud/network/vpc/Vpc.java index b94089d2d433..a0686e2bf7d0 100644 --- a/api/src/main/java/com/cloud/network/vpc/Vpc.java +++ b/api/src/main/java/com/cloud/network/vpc/Vpc.java @@ -107,4 +107,6 @@ public enum State { String getIp6Dns2(); boolean useRouterIpAsResolver(); + + boolean getKeepMacAddressOnPublicNic(); } 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 c1546609d2b7..3d0ba43263f5 100644 --- a/api/src/main/java/com/cloud/network/vpc/VpcService.java +++ b/api/src/main/java/com/cloud/network/vpc/VpcService.java @@ -58,7 +58,7 @@ public interface VpcService { */ 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, Integer cidrSize, - Long asNumber, List bgpPeerIds, Boolean useVrIpResolver) throws ResourceAllocationException; + Long asNumber, List bgpPeerIds, Boolean useVrIpResolver, boolean keepMacAddressOnPublicNic) throws ResourceAllocationException; /** * Persists VPC record in the database @@ -104,7 +104,7 @@ Vpc createVpc(long zoneId, long vpcOffId, long vpcOwnerId, String vpcName, Strin * @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(long vpcId, String vpcName, String displayText, String customId, Boolean displayVpc, Integer mtu, String sourceNatIp) throws ResourceUnavailableException, InsufficientCapacityException; + Vpc updateVpc(long vpcId, String vpcName, String displayText, String customId, Boolean displayVpc, Integer mtu, String sourceNatIp, Boolean keepMacAddressOnPublicNic) throws ResourceUnavailableException, InsufficientCapacityException; /** * Lists VPC(s) based on the parameters passed to the API call 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 05c6098bc726..6ad1ea56f9f4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -1348,6 +1348,13 @@ public class ApiConstants { public static final String OBJECT_STORAGE_LIMIT = "objectstoragelimit"; public static final String OBJECT_STORAGE_TOTAL = "objectstoragetotal"; + public static final String KEEP_MAC_ADDRESS_ON_PUBLIC_NIC = "keepmacaddressonpublicnic"; + + public static final String PARAMETER_DESCRIPTION_KEEP_MAC_ADDRESS_ON_PUBLIC_NIC = + "Indicates whether to use the same MAC address for the public NIC of VRs on the same network. If \"true\", when creating redundant routers or recreating" + + " a VR, CloudStack will use the same MAC address for the public NIC of all VRs. Otherwise, if \"false\", new public NICs will always have " + + " a new MAC address."; + public static final String PARAMETER_DESCRIPTION_ACTIVATION_RULE = "Quota tariff's activation rule. It can receive a JS script that results in either " + "a boolean or a numeric value: if it results in a boolean value, the tariff value will be applied according to the result; if it results in a numeric value, the " + "numeric value will be applied; if the result is neither a boolean nor a numeric value, the tariff will not be applied. If the rule is not informed, the tariff " + 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 cbf6df081b3b..ee5b8568e835 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 @@ -199,6 +199,11 @@ public class CreateNetworkCmd extends BaseCmd implements UserCmd { @Parameter(name=ApiConstants.AS_NUMBER, type=CommandType.LONG, since = "4.20.0", description="the AS Number of the network") private Long asNumber; + @Parameter(name = ApiConstants.KEEP_MAC_ADDRESS_ON_PUBLIC_NIC, + description = ApiConstants.PARAMETER_DESCRIPTION_KEEP_MAC_ADDRESS_ON_PUBLIC_NIC, + type = CommandType.BOOLEAN, since = "4.23.0", authorized = {RoleType.Admin}) + private Boolean keepMacAddressOnPublicNic; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -286,6 +291,10 @@ public String getSourceNatIP() { return sourceNatIP; } + public Boolean getKeepMacAddressOnPublicNic() { + return keepMacAddressOnPublicNic; + } + @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 2e638f1e2f76..7b6841d60976 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 @@ -105,6 +105,11 @@ public class UpdateNetworkCmd extends BaseAsyncCustomIdCmd implements UserCmd { @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; + @Parameter(name = ApiConstants.KEEP_MAC_ADDRESS_ON_PUBLIC_NIC, + description = ApiConstants.PARAMETER_DESCRIPTION_KEEP_MAC_ADDRESS_ON_PUBLIC_NIC, + type = CommandType.BOOLEAN, since = "4.23.0", authorized = {RoleType.Admin}) + private Boolean keepMacAddressOnPublicNic; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -186,6 +191,10 @@ public String getSourceNatIP() { return sourceNatIP; } + public Boolean getKeepMacAddressOnPublicNic() { + return keepMacAddressOnPublicNic; + } + ///////////////////////////////////////////////////// /////////////// 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 2adbbd664085..86309d7f28a5 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 @@ -130,6 +130,11 @@ public class CreateVPCCmd extends BaseAsyncCreateCmd implements UserCmd { description="(optional) for NSX based VPCs: when set to true, use the VR IP as nameserver, otherwise use DNS1 and DNS2") private Boolean useVrIpResolver; + @Parameter(name = ApiConstants.KEEP_MAC_ADDRESS_ON_PUBLIC_NIC, + description = ApiConstants.PARAMETER_DESCRIPTION_KEEP_MAC_ADDRESS_ON_PUBLIC_NIC, + type = CommandType.BOOLEAN, since = "4.23.0", authorized = {RoleType.Admin}) + private boolean keepMacAddressOnPublicNic = true; + // /////////////////////////////////////////////////// // ///////////////// Accessors /////////////////////// // /////////////////////////////////////////////////// @@ -214,6 +219,10 @@ public boolean getUseVrIpResolver() { return BooleanUtils.toBoolean(useVrIpResolver); } + public boolean getKeepMacAddressOnPublicNic() { + return keepMacAddressOnPublicNic; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// 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 f2327c9073f3..7b2670ef49db 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 @@ -69,6 +69,15 @@ public class UpdateVPCCmd extends BaseAsyncCustomIdCmd implements UserCmd { since = "4.19") private String sourceNatIP; + @Parameter(name = ApiConstants.KEEP_MAC_ADDRESS_ON_PUBLIC_NIC, + description = ApiConstants.PARAMETER_DESCRIPTION_KEEP_MAC_ADDRESS_ON_PUBLIC_NIC, + type = CommandType.BOOLEAN, since = "4.23.0", authorized = {RoleType.Admin}) + private Boolean keepMacAddressOnPublicNic; + + public Boolean getKeepMacAddressOnPublicNic() { + return keepMacAddressOnPublicNic; + } + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java index 7c4b733a80f5..3a3663af2551 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java @@ -331,6 +331,10 @@ public class NetworkResponse extends BaseResponseWithAssociatedNetwork implement @Param(description = "The BGP peers for the network", since = "4.20.0") private Set bgpPeers; + @SerializedName(ApiConstants.KEEP_MAC_ADDRESS_ON_PUBLIC_NIC) + @Param(description = ApiConstants.PARAMETER_DESCRIPTION_KEEP_MAC_ADDRESS_ON_PUBLIC_NIC, since = "4.23.0") + private Boolean keepMacAddressOnPublicNic; + public NetworkResponse() {} public Boolean getDisplayNetwork() { @@ -702,4 +706,8 @@ public void setIpv6Dns1(String ipv6Dns1) { public void setIpv6Dns2(String ipv6Dns2) { this.ipv6Dns2 = ipv6Dns2; } + + public void setKeepMacAddressOnPublicNic(Boolean keepMacAddressOnPublicNic) { + this.keepMacAddressOnPublicNic = keepMacAddressOnPublicNic; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java index acfabb113502..34d50d5b9f92 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java @@ -185,6 +185,10 @@ public class VpcResponse extends BaseResponseWithAnnotations implements Controll @Param(description = "The BGP peers for the VPC", since = "4.20.0") private Set bgpPeers; + @SerializedName(ApiConstants.KEEP_MAC_ADDRESS_ON_PUBLIC_NIC) + @Param(description = ApiConstants.PARAMETER_DESCRIPTION_KEEP_MAC_ADDRESS_ON_PUBLIC_NIC, since = "4.23.0") + private Boolean keepMacAddressOnPublicNic; + public void setId(final String id) { this.id = id; } @@ -366,4 +370,8 @@ public void addBgpPeer(BgpPeerResponse bgpPeer) { } this.bgpPeers.add(bgpPeer); } + + public void setKeepMacAddressOnPublicNic(Boolean keepMacAddressOnPublicNic) { + this.keepMacAddressOnPublicNic = keepMacAddressOnPublicNic; + } } 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 acb2dc685976..9d56cffd6718 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 @@ -93,7 +93,7 @@ public void testExecute() throws ResourceUnavailableException, InsufficientCapac responseGenerator = Mockito.mock(ResponseGenerator.class); cmd._responseGenerator = responseGenerator; Mockito.verify(_vpcService, Mockito.times(0)).updateVpc(Mockito.anyLong(), Mockito.anyString(), Mockito.anyString(), - Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyInt(), Mockito.anyString()); + Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyInt(), Mockito.anyString(), Mockito.anyBoolean()); } } diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java index 947cbd8e6182..2d04f3571ac7 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java @@ -212,6 +212,11 @@ Network createGuestNetwork(long networkOfferingId, String name, String displayTe Boolean displayNetworkEnabled, String isolatedPvlan, Network.PVlanType isolatedPvlanType, String externalId, String routerIp, String routerIpv6, String ip4Dns1, String ip4Dns2, String ip6Dns1, String ip6Dns2, Pair vrIfaceMTUs, Integer networkCidrSize) throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException; + Network createGuestNetwork(long networkOfferingId, String name, String displayText, String gateway, String cidr, String vlanId, boolean bypassVlanOverlapCheck, String networkDomain, Account owner, + Long domainId, PhysicalNetwork physicalNetwork, long zoneId, ACLType aclType, Boolean subdomainAccess, Long vpcId, String ip6Gateway, String ip6Cidr, + Boolean displayNetworkEnabled, String isolatedPvlan, Network.PVlanType isolatedPvlanType, String externalId, String routerIp, String routerIpv6, + String ip4Dns1, String ip4Dns2, String ip6Dns1, String ip6Dns2, Pair vrIfaceMTUs, Integer networkCidrSize, Boolean keepMacAddressOnPublicNic) throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException; + UserDataServiceProvider getPasswordResetProvider(Network network); UserDataServiceProvider getSSHKeyResetProvider(Network network); diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java index 77ea965e50aa..e51d8cb44d28 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java @@ -858,6 +858,7 @@ private static NetworkVO getNetworkVO(long id, final NetworkOffering offering, f vpcId, offering.isRedundantRouter(), predefined.getExternalId()); vo.setDisplayNetwork(isDisplayNetworkEnabled == null || isDisplayNetworkEnabled); vo.setStrechedL2Network(offering.isSupportingStrechedL2()); + vo.setKeepMacAddressOnPublicNic(predefined.getKeepMacAddressOnPublicNic()); return vo; } @@ -2719,7 +2720,7 @@ public Network createPrivateNetwork(final long networkOfferingId, final String n return createGuestNetwork(networkOfferingId, name, displayText, gateway, cidr, vlanId, bypassVlanOverlapCheck, null, owner, null, pNtwk, pNtwk.getDataCenterId(), ACLType.Account, null, vpcId, null, null, true, null, null, null, true, null, null, - null, null, null, null, null, null); + null, null, null, null, null, null, null); } @Override @@ -2730,10 +2731,25 @@ public Network createGuestNetwork(final long networkOfferingId, final String nam final Boolean isDisplayNetworkEnabled, final String isolatedPvlan, Network.PVlanType isolatedPvlanType, String externalId, String routerIp, String routerIpv6, String ip4Dns1, String ip4Dns2, String ip6Dns1, String ip6Dns2, Pair vrIfaceMTUs, Integer networkCidrSize) throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException { + return createGuestNetwork(networkOfferingId, name, displayText, gateway, cidr, vlanId, bypassVlanOverlapCheck, + networkDomain, owner, domainId, pNtwk, zoneId, aclType, subdomainAccess, vpcId, ip6Gateway, ip6Cidr, + isDisplayNetworkEnabled, isolatedPvlan, isolatedPvlanType, externalId, false, routerIp, routerIpv6, + ip4Dns1, ip4Dns2, ip6Dns1, ip6Dns2, vrIfaceMTUs, networkCidrSize, true); + } + + @Override + @DB + public Network createGuestNetwork(final long networkOfferingId, final String name, final String displayText, final String gateway, final String cidr, String vlanId, + boolean bypassVlanOverlapCheck, String networkDomain, final Account owner, final Long domainId, final PhysicalNetwork pNtwk, + final long zoneId, final ACLType aclType, Boolean subdomainAccess, final Long vpcId, final String ip6Gateway, final String ip6Cidr, + final Boolean isDisplayNetworkEnabled, final String isolatedPvlan, Network.PVlanType isolatedPvlanType, String externalId, + String routerIp, String routerIpv6, String ip4Dns1, String ip4Dns2, String ip6Dns1, String ip6Dns2, + Pair vrIfaceMTUs, Integer networkCidrSize, Boolean keepMacAddressOnPublicNic) throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException { // create Isolated/Shared/L2 network return createGuestNetwork(networkOfferingId, name, displayText, gateway, cidr, vlanId, bypassVlanOverlapCheck, networkDomain, owner, domainId, pNtwk, zoneId, aclType, subdomainAccess, vpcId, ip6Gateway, ip6Cidr, - isDisplayNetworkEnabled, isolatedPvlan, isolatedPvlanType, externalId, false, routerIp, routerIpv6, ip4Dns1, ip4Dns2, ip6Dns1, ip6Dns2, vrIfaceMTUs, networkCidrSize); + isDisplayNetworkEnabled, isolatedPvlan, isolatedPvlanType, externalId, false, routerIp, routerIpv6, + ip4Dns1, ip4Dns2, ip6Dns1, ip6Dns2, vrIfaceMTUs, networkCidrSize, keepMacAddressOnPublicNic); } @DB @@ -2742,7 +2758,8 @@ private Network createGuestNetwork(final long networkOfferingId, final String na final long zoneId, final ACLType aclType, Boolean subdomainAccess, final Long vpcId, final String ip6Gateway, final String ip6Cidr, final Boolean isDisplayNetworkEnabled, final String isolatedPvlan, Network.PVlanType isolatedPvlanType, String externalId, final Boolean isPrivateNetwork, String routerIp, String routerIpv6, final String ip4Dns1, final String ip4Dns2, - final String ip6Dns1, final String ip6Dns2, Pair vrIfaceMTUs, Integer networkCidrSize) throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException { + final String ip6Dns1, final String ip6Dns2, Pair vrIfaceMTUs, Integer networkCidrSize, + Boolean keepMacAddressOnPublicNic) throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException { final NetworkOfferingVO ntwkOff = _networkOfferingDao.findById(networkOfferingId); final DataCenterVO zone = _dcDao.findById(zoneId); @@ -3095,6 +3112,7 @@ public Network doInTransaction(final TransactionStatus status) { } } userNetwork.setNetworkCidrSize(networkCidrSize); + userNetwork.setKeepMacAddressOnPublicNic(keepMacAddressOnPublicNic); final List networks = setupNetwork(owner, ntwkOff, userNetwork, plan, name, displayText, true, domainId, aclType, subdomainAccessFinal, vpcId, isDisplayNetworkEnabled); Network network; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/NetworkVO.java b/engine/schema/src/main/java/com/cloud/network/dao/NetworkVO.java index 02abaacd854e..f2572ba91c21 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/NetworkVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/NetworkVO.java @@ -203,9 +203,13 @@ public class NetworkVO implements Network { @Column(name = "private_mtu") Integer privateMtu; + @Column(name = "keep_mac_address_on_public_nic") + private boolean keepMacAddressOnPublicNic = true; + @Transient Integer networkCidrSize; + public NetworkVO() { uuid = UUID.randomUUID().toString(); } @@ -773,4 +777,13 @@ public Integer getNetworkCidrSize() { public void setNetworkCidrSize(Integer networkCidrSize) { this.networkCidrSize = networkCidrSize; } + + @Override + public boolean getKeepMacAddressOnPublicNic() { + return keepMacAddressOnPublicNic; + } + + public void setKeepMacAddressOnPublicNic(boolean keepMacAddressOnPublicNic) { + this.keepMacAddressOnPublicNic = keepMacAddressOnPublicNic; + } } diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/VpcVO.java b/engine/schema/src/main/java/com/cloud/network/vpc/VpcVO.java index e942eadb8ffb..742d3f2f82ee 100644 --- a/engine/schema/src/main/java/com/cloud/network/vpc/VpcVO.java +++ b/engine/schema/src/main/java/com/cloud/network/vpc/VpcVO.java @@ -108,6 +108,9 @@ public class VpcVO implements Vpc { @Column(name = "use_router_ip_resolver") boolean useRouterIpResolver = false; + @Column(name = "keep_mac_address_on_public_nic") + private boolean keepMacAddressOnPublicNic = true; + @Transient boolean rollingRestart = false; @@ -321,4 +324,13 @@ public boolean useRouterIpAsResolver() { public void setUseRouterIpResolver(boolean useRouterIpResolver) { this.useRouterIpResolver = useRouterIpResolver; } + + @Override + public boolean getKeepMacAddressOnPublicNic() { + return keepMacAddressOnPublicNic; + } + + public void setKeepMacAddressOnPublicNic(boolean keepMacAddressOnPublicNic) { + this.keepMacAddressOnPublicNic = keepMacAddressOnPublicNic; + } } diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql b/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql index 4cb9eb7cb2c4..dde60dd3660b 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql @@ -117,3 +117,7 @@ CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vpc_offerings','conserve_mode', 'tin --- Disable/enable NICs CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.nics','enabled', 'TINYINT(1) NOT NULL DEFAULT 1 COMMENT ''Indicates whether the NIC is enabled or not'' '); + +-- Add the 'keep_mac_address_on_public_nic' column to the 'cloud.networks' and 'cloud.vpc' tables +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.networks', 'keep_mac_address_on_public_nic', 'TINYINT(1) NOT NULL DEFAULT 1'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vpc', 'keep_mac_address_on_public_nic', 'TINYINT(1) NOT NULL DEFAULT 1'); diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index bd09d1de48b8..88677f81435b 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -2874,6 +2874,11 @@ public NetworkResponse createNetworkResponse(ResponseView view, Network network) } } + if (CallContext.current().getCallingAccount().getType() == Account.Type.ADMIN && + network.getVpcId() == null && network.getGuestType() == Network.GuestType.Isolated) { + response.setKeepMacAddressOnPublicNic(network.getKeepMacAddressOnPublicNic()); + } + response.setObjectName("network"); return response; } @@ -3637,6 +3642,9 @@ public VpcResponse createVpcResponse(ResponseView view, Vpc vpc) { } } + if (CallContext.current().getCallingAccount().getType() == Account.Type.ADMIN) { + response.setKeepMacAddressOnPublicNic(vpc.getKeepMacAddressOnPublicNic()); + } response.setObjectName("vpc"); return response; } diff --git a/server/src/main/java/com/cloud/network/NetworkMigrationManagerImpl.java b/server/src/main/java/com/cloud/network/NetworkMigrationManagerImpl.java index 450f08c46e35..a09867b8ffc3 100644 --- a/server/src/main/java/com/cloud/network/NetworkMigrationManagerImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkMigrationManagerImpl.java @@ -301,7 +301,7 @@ public Long makeCopyOfVpc(long vpcId, long vpcOfferingId) { 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(), null, null, null, vpc.useRouterIpAsResolver()); + vpc.getIp6Dns1(), vpc.getIp6Dns2(), vpc.isDisplay(), vpc.getPublicMtu(), null, null, null, vpc.useRouterIpAsResolver(), vpc.getKeepMacAddressOnPublicNic()); copyOfVpcId = copyOfVpc.getId(); //on resume of migration the uuid will be swapped already. So the copy will have the value of the original vpcid. diff --git a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java index 0a2e679b723e..5f6ca7694ce2 100644 --- a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java @@ -1547,6 +1547,8 @@ public Network createGuestNetwork(CreateNetworkCmd cmd) throws InsufficientCapac DataCenter zone = getAndValidateZone(cmd, pNtwk); + Boolean keepMacAddressOnPublicNic = getAndValidateSupportForKeepMacAddressOnPublicNicParameter(cmd.getKeepMacAddressOnPublicNic(), ntwkOff); + _accountMgr.checkAccess(owner, ntwkOff, zone); validateZoneAvailability(caller, zone); @@ -1832,7 +1834,7 @@ public Network createGuestNetwork(CreateNetworkCmd cmd) throws InsufficientCapac 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, routerIPv4, routerIPv6, associatedNetwork, ip4Dns1, ip4Dns2, ip6Dns1, ip6Dns2, interfaceMTUs, networkCidrSize); + externalId, routerIPv4, routerIPv6, associatedNetwork, ip4Dns1, ip4Dns2, ip6Dns1, ip6Dns2, interfaceMTUs, networkCidrSize, keepMacAddressOnPublicNic); // retrieve, acquire and associate the correct IP addresses checkAndSetRouterSourceNatIp(owner, cmd, network); @@ -1877,6 +1879,24 @@ private void validateNetworkCreationSupported(long zoneId, String zoneName, Gues } } + protected boolean getAndValidateSupportForKeepMacAddressOnPublicNicParameter(Boolean keepMacAddressOnPublicNic, NetworkOffering networkOffering) { + if (networkOffering.isForVpc() && keepMacAddressOnPublicNic != null) { + throw new InvalidParameterValueException( + String.format("The [%s] parameter cannot be specified on the creation of VPC tiers.", ApiConstants.KEEP_MAC_ADDRESS_ON_PUBLIC_NIC) + ); + } + + GuestType guestType = networkOffering.getGuestType(); + if (guestType != GuestType.Isolated && keepMacAddressOnPublicNic != null) { + throw new InvalidParameterValueException(String.format( + "The [%s] parameter can only be specified on the creation of [%s] networks.", + ApiConstants.KEEP_MAC_ADDRESS_ON_PUBLIC_NIC, GuestType.Isolated + )); + } + + return keepMacAddressOnPublicNic == null || keepMacAddressOnPublicNic; + } + @Override @DB @ActionEvent(eventType = EventTypes.EVENT_NETWORK_CREATE, eventDescription = "creating network") @@ -2280,7 +2300,7 @@ protected Network commitNetwork(final Long networkOfferingId, final String gatew 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, - final Integer networkCidrSize) throws InsufficientCapacityException, ResourceAllocationException { + final Integer networkCidrSize, final Boolean keepMacAddressOnPublicNic) throws InsufficientCapacityException, ResourceAllocationException { try { Network network = Transaction.execute(new TransactionCallbackWithException() { @Override @@ -2346,7 +2366,7 @@ public Network doInTransaction(TransactionStatus status) throws InsufficientCapa } network = _networkMgr.createGuestNetwork(networkOfferingId, name, displayText, gateway, cidr, vlanId, bypassVlanOverlapCheck, networkDomain, owner, sharedDomainId, pNtwk, zoneId, aclType, subdomainAccess, vpcId, ip6Gateway, ip6Cidr, displayNetwork, isolatedPvlan, isolatedPvlanType, externalId, routerIp, routerIpv6, ip4Dns1, ip4Dns2, - ip6Dns1, ip6Dns2, vrIfaceMTUs, networkCidrSize); + ip6Dns1, ip6Dns2, vrIfaceMTUs, networkCidrSize, keepMacAddressOnPublicNic); } if (createVlan && network != null) { @@ -3154,6 +3174,7 @@ public Network updateGuestNetwork(final UpdateNetworkCmd cmd) { String ip4Dns2 = cmd.getIp4Dns2(); String ip6Dns1 = cmd.getIp6Dns1(); String ip6Dns2 = cmd.getIp6Dns2(); + Boolean keepMacAddressOnPublicNic = cmd.getKeepMacAddressOnPublicNic(); boolean restartNetwork = false; @@ -3212,6 +3233,10 @@ public Network updateGuestNetwork(final UpdateNetworkCmd cmd) { network.setUuid(customId); } + if (keepMacAddressOnPublicNic != null) { + network.setKeepMacAddressOnPublicNic(getAndValidateSupportForKeepMacAddressOnPublicNicParameter(keepMacAddressOnPublicNic, offering)); + } + // display flag is not null and has changed if (displayNetwork != null && displayNetwork != network.getDisplayNetwork()) { // Update resource count if it needs to be updated diff --git a/server/src/main/java/com/cloud/network/router/NetworkHelperImpl.java b/server/src/main/java/com/cloud/network/router/NetworkHelperImpl.java index 684f196ce9fd..56534a8ee7df 100644 --- a/server/src/main/java/com/cloud/network/router/NetworkHelperImpl.java +++ b/server/src/main/java/com/cloud/network/router/NetworkHelperImpl.java @@ -737,55 +737,91 @@ protected LinkedHashMap> configureControlNic } protected LinkedHashMap> configurePublicNic(final RouterDeploymentDefinition routerDeploymentDefinition, final boolean hasGuestNic) throws InsufficientAddressCapacityException { - final LinkedHashMap> publicConfig = new LinkedHashMap>(3); - - if (routerDeploymentDefinition.isPublicNetwork()) { - logger.debug("Adding NIC for Virtual Router in Public network "); - // if source nat service is supported by the network, get the source - // nat ip address - final NicProfile defaultNic = new NicProfile(); - defaultNic.setDefaultNic(true); - final PublicIp sourceNatIp = routerDeploymentDefinition.getSourceNatIP(); - defaultNic.setIPv4Address(sourceNatIp.getAddress().addr()); - defaultNic.setIPv4Gateway(sourceNatIp.getGateway()); - defaultNic.setIPv4Netmask(sourceNatIp.getNetmask()); - defaultNic.setMacAddress(sourceNatIp.getMacAddress()); - // get broadcast from public network - final Network pubNet = _networkDao.findById(sourceNatIp.getNetworkId()); - if (pubNet.getBroadcastDomainType() == BroadcastDomainType.Vxlan) { - defaultNic.setBroadcastType(BroadcastDomainType.Vxlan); - defaultNic.setBroadcastUri(BroadcastDomainType.Vxlan.toUri(sourceNatIp.getVlanTag())); - defaultNic.setIsolationUri(BroadcastDomainType.Vxlan.toUri(sourceNatIp.getVlanTag())); - } else { - defaultNic.setBroadcastType(BroadcastDomainType.Vlan); - defaultNic.setBroadcastUri(sourceNatIp.getVlanTag() != null ? BroadcastDomainType.Vlan.toUri(sourceNatIp.getVlanTag()) : null); - defaultNic.setIsolationUri(sourceNatIp.getVlanTag() != null ? IsolationType.Vlan.toUri(sourceNatIp.getVlanTag()) : null); - } - - //If guest nic has already been added we will have 2 devices in the list. - if (hasGuestNic) { - defaultNic.setDeviceId(2); - } + final LinkedHashMap> publicConfig = new LinkedHashMap<>(3); + if (!routerDeploymentDefinition.isPublicNetwork()) { + return publicConfig; + } - final NetworkOffering publicOffering = _networkModel.getSystemAccountNetworkOfferings(NetworkOffering.SystemPublicNetwork).get(0); - final List publicNetworks = _networkMgr.setupNetwork(s_systemAccount, publicOffering, routerDeploymentDefinition.getPlan(), null, null, false); - final String publicIp = defaultNic.getIPv4Address(); - // We want to use the identical MAC address for RvR on public - // interface if possible - final NicVO peerNic = _nicDao.findByIp4AddressAndNetworkId(publicIp, publicNetworks.get(0).getId()); - if (peerNic != null) { - logger.info("Use same MAC as previous RvR, the MAC is " + peerNic.getMacAddress()); - defaultNic.setMacAddress(peerNic.getMacAddress()); - } - if (routerDeploymentDefinition.getGuestNetwork() != null) { - ipv6Service.updateNicIpv6(defaultNic, routerDeploymentDefinition.getDest().getDataCenter(), routerDeploymentDefinition.getGuestNetwork()); - } - publicConfig.put(publicNetworks.get(0), new ArrayList(Arrays.asList(defaultNic))); + final PublicIp sourceNatIp = routerDeploymentDefinition.getSourceNatIP(); + final NicProfile defaultNic = new NicProfile(); + configurePublicVrNicBasedOnSourceNatIp(defaultNic, sourceNatIp); + if (hasGuestNic) { + logger.debug("Guest NIC has already been configured, therefore setting device ID of new VR (with source NAT [{}]) public NIC to [2].", sourceNatIp); + defaultNic.setDeviceId(2); } + final NetworkOffering publicOffering = _networkModel.getSystemAccountNetworkOfferings(NetworkOffering.SystemPublicNetwork).get(0); + final List publicNetworks = _networkMgr.setupNetwork(s_systemAccount, publicOffering, routerDeploymentDefinition.getPlan(), null, null, false); + + setPublicNicMacAddressSameAsPeerNic(defaultNic, publicNetworks.get(0), routerDeploymentDefinition); + + if (routerDeploymentDefinition.getGuestNetwork() != null) { + ipv6Service.updateNicIpv6(defaultNic, routerDeploymentDefinition.getDest().getDataCenter(), routerDeploymentDefinition.getGuestNetwork()); + } + publicConfig.put(publicNetworks.get(0), List.of(defaultNic)); return publicConfig; } + /** + * Configures the public NIC of a Virtual Router based on the provided source NAT IP. + * @param nic Virtual Router public NIC to be configured. + * @param sourceNatIp Source NAT IP which should be used to configure the Virtual Router's public NIC. + */ + protected void configurePublicVrNicBasedOnSourceNatIp(NicProfile nic, PublicIp sourceNatIp) { + logger.debug("Configuring public NIC of VR with source NAT IP equal to [{}]", sourceNatIp.getAddress().addr()); + nic.setDefaultNic(true); + nic.setIPv4Address(sourceNatIp.getAddress().addr()); + nic.setIPv4Gateway(sourceNatIp.getGateway()); + nic.setIPv4Netmask(sourceNatIp.getNetmask()); + nic.setMacAddress(sourceNatIp.getMacAddress()); + + Network publicNetwork = _networkDao.findById(sourceNatIp.getNetworkId()); + if (publicNetwork.getBroadcastDomainType() == BroadcastDomainType.Vxlan) { + nic.setBroadcastType(BroadcastDomainType.Vxlan); + nic.setBroadcastUri(BroadcastDomainType.Vxlan.toUri(sourceNatIp.getVlanTag())); + nic.setIsolationUri(BroadcastDomainType.Vxlan.toUri(sourceNatIp.getVlanTag())); + } else { + nic.setBroadcastType(BroadcastDomainType.Vlan); + nic.setBroadcastUri(sourceNatIp.getVlanTag() != null ? BroadcastDomainType.Vlan.toUri(sourceNatIp.getVlanTag()) : null); + nic.setIsolationUri(sourceNatIp.getVlanTag() != null ? IsolationType.Vlan.toUri(sourceNatIp.getVlanTag()) : null); + } + } + + /** + * Sets the MAC address of the new VR's public NIC the same as the previous VR's public NIC MAC address if + * {@link RouterDeploymentDefinition#getKeepMacAddressOnPublicNic()} is set to {@code true} and a peer NIC is found; otherwise, + * does nothing. + */ + protected void setPublicNicMacAddressSameAsPeerNic(NicProfile defaultNic, Network publicNetwork, RouterDeploymentDefinition routerDeploymentDefinition) throws InsufficientAddressCapacityException { + String publicIp = defaultNic.getIPv4Address(); + logger.info("Verifying if we will use same MAC address for public NIC of new VR (with source NAT [{}]).", publicIp); + + logger.debug("Searching for peer NIC for public IP [{}] and network [{}].", publicIp, publicNetwork.getUuid()); + NicVO peerNic = _nicDao.findByIp4AddressAndNetworkId(publicIp, publicNetwork.getId()); + if (peerNic == null) { + logger.info("We will not use the same MAC address for public NIC of new VR as we have not found a peer NIC for public IP [{}] and network [{}].", + publicIp, publicNetwork.getUuid()); + return; + } + + logger.info("Found peer NIC [{}] for public IP [{}] and network [{}].", peerNic.getUuid(), publicIp, publicNetwork.getUuid()); + boolean keepMacAddressOnPublicNic = routerDeploymentDefinition.getKeepMacAddressOnPublicNic(); + String macAddressLog = String.format("same MAC address for public NIC of new VR (with source NAT [%s]) as the [keep_mac_address_on_public_nic] property is configured as [%s].", publicIp, keepMacAddressOnPublicNic); + if (keepMacAddressOnPublicNic) { + logger.info("Using {}", macAddressLog); + defaultNic.setMacAddress(peerNic.getMacAddress()); + return; + } + + logger.info("Not using {}", macAddressLog); + boolean fetchNewMacAddress = peerNic.getMacAddress().equals(defaultNic.getMacAddress()); + if (fetchNewMacAddress) { + logger.debug("Fetching new MAC address for public NIC of new VR, since the MAC address of the peer NIC [UUID: {}, MAC address: {}] is equal to the predefined MAC address of the current NIC.", peerNic.getUuid(), peerNic.getMacAddress()); + routerDeploymentDefinition.findSourceNatIP(); + configurePublicVrNicBasedOnSourceNatIp(defaultNic, routerDeploymentDefinition.getSourceNatIP()); + } + } + @Override public LinkedHashMap> configureDefaultNics(final RouterDeploymentDefinition routerDeploymentDefinition) throws ConcurrentOperationException, InsufficientAddressCapacityException { 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 156e71e72b8f..34d5ca848753 100644 --- a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java +++ b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java @@ -1566,7 +1566,7 @@ public List getVpcOfferingZones(Long vpcOfferingId) { @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, - final Integer cidrSize, final Long asNumber, final List bgpPeerIds, Boolean useVrIpResolver) throws ResourceAllocationException { + final Integer cidrSize, final Long asNumber, final List bgpPeerIds, Boolean useVrIpResolver, boolean keepMacAddressOnPublicNic) throws ResourceAllocationException { final Account caller = CallContext.current().getCallingAccount(); final Account owner = _accountMgr.getAccount(vpcOwnerId); @@ -1669,6 +1669,7 @@ public Vpc createVpc(final long zoneId, final long vpcOffId, final long vpcOwner vpc.setPublicMtu(publicMtu); vpc.setDisplay(Boolean.TRUE.equals(displayVpc)); vpc.setUseRouterIpResolver(Boolean.TRUE.equals(useVrIpResolver)); + vpc.setKeepMacAddressOnPublicNic(keepMacAddressOnPublicNic); if (vpc.getCidr() == null && cidrSize != null) { // Allocate a CIDR for VPC @@ -1727,7 +1728,8 @@ public Vpc createVpc(CreateVPCCmd cmd) throws ResourceAllocationException { List bgpPeerIds = (cmd instanceof CreateVPCCmdByAdmin) ? ((CreateVPCCmdByAdmin)cmd).getBgpPeerIds() : null; 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(), cmd.getCidrSize(), cmd.getAsNumber(), bgpPeerIds, cmd.getUseVrIpResolver()); + cmd.getIp6Dns2(), cmd.isDisplay(), cmd.getPublicMtu(), cmd.getCidrSize(), cmd.getAsNumber(), bgpPeerIds, + cmd.getUseVrIpResolver(), cmd.getKeepMacAddressOnPublicNic()); String sourceNatIP = cmd.getSourceNatIP(); boolean forNsx = isVpcForProvider(Provider.Nsx, vpc); @@ -1939,12 +1941,14 @@ public void doInTransactionWithoutResult(final TransactionStatus status) { @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()); + return updateVpc(cmd.getId(), cmd.getVpcName(), cmd.getDisplayText(), cmd.getCustomId(), + cmd.isDisplayVpc(), cmd.getPublicMtu(), cmd.getSourceNatIP(), cmd.getKeepMacAddressOnPublicNic()); } @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, String sourceNatIp) throws ResourceUnavailableException, InsufficientCapacityException { + public Vpc updateVpc(final long vpcId, final String vpcName, final String displayText, final String customId, + final Boolean displayVpc, Integer mtu, String sourceNatIp, Boolean keepMacAddressOnPublicNic) throws ResourceUnavailableException, InsufficientCapacityException { final Account caller = CallContext.current().getCallingAccount(); // Verify input parameters @@ -1976,6 +1980,10 @@ public Vpc updateVpc(final long vpcId, final String vpcName, final String displa vpc.setDisplay(displayVpc); } + if (keepMacAddressOnPublicNic != null) { + vpc.setKeepMacAddressOnPublicNic(keepMacAddressOnPublicNic); + } + mtu = validateMtu(vpcToUpdate, mtu); if (mtu != null) { updateMtuOfVpcNetwork(vpcToUpdate, vpc, mtu); diff --git a/server/src/main/java/org/apache/cloudstack/network/router/deployment/RouterDeploymentDefinition.java b/server/src/main/java/org/apache/cloudstack/network/router/deployment/RouterDeploymentDefinition.java index 4187586736d7..77cd7890e413 100644 --- a/server/src/main/java/org/apache/cloudstack/network/router/deployment/RouterDeploymentDefinition.java +++ b/server/src/main/java/org/apache/cloudstack/network/router/deployment/RouterDeploymentDefinition.java @@ -394,7 +394,7 @@ protected void executeDeployment() } } - protected void findSourceNatIP() throws InsufficientAddressCapacityException, ConcurrentOperationException { + public void findSourceNatIP() throws InsufficientAddressCapacityException, ConcurrentOperationException { sourceNatIp = null; DataCenter zone = dest.getDataCenter(); Long zoneId = null; @@ -548,4 +548,8 @@ protected boolean routersNeedReset() { return needReset; } + + public boolean getKeepMacAddressOnPublicNic() { + return guestNetwork == null || guestNetwork.getKeepMacAddressOnPublicNic(); + } } diff --git a/server/src/main/java/org/apache/cloudstack/network/router/deployment/VpcRouterDeploymentDefinition.java b/server/src/main/java/org/apache/cloudstack/network/router/deployment/VpcRouterDeploymentDefinition.java index 063565ebf468..9e432265f2a1 100644 --- a/server/src/main/java/org/apache/cloudstack/network/router/deployment/VpcRouterDeploymentDefinition.java +++ b/server/src/main/java/org/apache/cloudstack/network/router/deployment/VpcRouterDeploymentDefinition.java @@ -117,7 +117,7 @@ protected boolean prepareDeployment() { } @Override - protected void findSourceNatIP() throws InsufficientAddressCapacityException, ConcurrentOperationException { + public void findSourceNatIP() throws InsufficientAddressCapacityException, ConcurrentOperationException { sourceNatIp = null; DataCenter zone = dest.getDataCenter(); Long zoneId = null; @@ -219,4 +219,9 @@ public boolean isRedundant() { public boolean isRollingRestart() { return vpc.isRollingRestart(); } + + @Override + public boolean getKeepMacAddressOnPublicNic() { + return vpc == null || vpc.getKeepMacAddressOnPublicNic(); + } } diff --git a/server/src/test/java/com/cloud/network/NetworkServiceImplTest.java b/server/src/test/java/com/cloud/network/NetworkServiceImplTest.java index 51b2dad3decd..0186f28dbcb3 100644 --- a/server/src/test/java/com/cloud/network/NetworkServiceImplTest.java +++ b/server/src/test/java/com/cloud/network/NetworkServiceImplTest.java @@ -1330,4 +1330,52 @@ public void addProjectNetworksConditionToSearch_includesSpecificProjectWhenProje Mockito.verify(accountJoin).addAnd("type", SearchCriteria.Op.EQ, Account.Type.PROJECT); Mockito.verify(sc).addAnd("id", SearchCriteria.Op.SC, accountJoin); } + + @Test(expected = InvalidParameterValueException.class) + public void getAndValidateSupportForKeepMacAddressOnPublicNicParameterTestThrowExceptionWhenParamIsSpecifiedOnTiersCreation() { + networkOfferingVO = Mockito.mock(NetworkOfferingVO.class); + Mockito.when(networkOfferingVO.isForVpc()).thenReturn(true); + + service.getAndValidateSupportForKeepMacAddressOnPublicNicParameter(true, networkOfferingVO); + } + + @Test + public void getAndValidateSupportForKeepMacAddressOnPublicNicParameterTestReturnTrueByDefaultOnTiersCreation() { + networkOfferingVO = Mockito.mock(NetworkOfferingVO.class); + Mockito.when(networkOfferingVO.isForVpc()).thenReturn(true); + + Assert.assertTrue(service.getAndValidateSupportForKeepMacAddressOnPublicNicParameter(null, networkOfferingVO)); + } + + @Test(expected = InvalidParameterValueException.class) + public void getAndValidateSupportForKeepMacAddressOnPublicNicParameterTestThrowExceptionWhenParamIsSpecifiedOnNonIsolatedNetworksCreation() { + networkOfferingVO = Mockito.mock(NetworkOfferingVO.class); + Mockito.when(networkOfferingVO.getGuestType()).thenReturn(Network.GuestType.Shared); + + service.getAndValidateSupportForKeepMacAddressOnPublicNicParameter(true, networkOfferingVO); + } + + @Test + public void getAndValidateSupportForKeepMacAddressOnPublicNicParameterTestReturnTrueByDefaultOnNonIsolatedNetworksCreation() { + networkOfferingVO = Mockito.mock(NetworkOfferingVO.class); + Mockito.when(networkOfferingVO.getGuestType()).thenReturn(Network.GuestType.L2); + + Assert.assertTrue(service.getAndValidateSupportForKeepMacAddressOnPublicNicParameter(null, networkOfferingVO)); + } + + @Test + public void getAndValidateSupportForKeepMacAddressOnPublicNicParameterTestReturnTrueByDefaultOnIsolatedNetworksCreation() { + networkOfferingVO = Mockito.mock(NetworkOfferingVO.class); + Mockito.when(networkOfferingVO.getGuestType()).thenReturn(Network.GuestType.Isolated); + + Assert.assertTrue(service.getAndValidateSupportForKeepMacAddressOnPublicNicParameter(null, networkOfferingVO)); + } + + @Test + public void getAndValidateSupportForKeepMacAddressOnPublicNicParameterTestReturnSpecifiedValueOnIsolatedNetworksCreation() { + networkOfferingVO = Mockito.mock(NetworkOfferingVO.class); + Mockito.when(networkOfferingVO.getGuestType()).thenReturn(Network.GuestType.Isolated); + + Assert.assertFalse(service.getAndValidateSupportForKeepMacAddressOnPublicNicParameter(false, networkOfferingVO)); + } } diff --git a/server/src/test/java/com/cloud/network/router/NetworkHelperImplTest.java b/server/src/test/java/com/cloud/network/router/NetworkHelperImplTest.java index 4237ef7f6f65..e4773d01de92 100644 --- a/server/src/test/java/com/cloud/network/router/NetworkHelperImplTest.java +++ b/server/src/test/java/com/cloud/network/router/NetworkHelperImplTest.java @@ -24,18 +24,31 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.cloud.deploy.DeployDestination; +import com.cloud.exception.InsufficientAddressCapacityException; +import com.cloud.network.Ipv6Service; +import com.cloud.network.Network; +import com.cloud.network.Networks; +import com.cloud.network.addr.PublicIp; +import com.cloud.network.dao.NetworkVO; +import com.cloud.offering.NetworkOffering; +import com.cloud.utils.net.Ip; +import com.cloud.vm.NicProfile; +import com.cloud.vm.NicVO; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.network.router.deployment.RouterDeploymentDefinition; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import com.cloud.agent.AgentManager; @@ -57,6 +70,9 @@ import com.cloud.vm.dao.DomainRouterDao; import com.cloud.vm.dao.NicDao; +import java.util.List; +import java.util.Map; + @RunWith(MockitoJUnitRunner.class) public class NetworkHelperImplTest { @@ -69,32 +85,59 @@ public class NetworkHelperImplTest { @Mock DomainRouterDao routerDao; + @Spy @InjectMocks - protected NetworkHelperImpl nwHelper = new NetworkHelperImpl(); + protected NetworkHelperImpl networkHelperSpy = new NetworkHelperImpl(); + @Mock NetworkOrchestrationService networkOrchestrationService; + @Mock NetworkDao networkDao; + @Mock NetworkModel networkModel; + @Mock - NicDao nicDao; + private NicDao nicDaoMock; @Mock private RouterDeploymentDefinition routerDeploymentDefinition; + @Mock private VirtualRouterProvider virtualProvider; + @Mock private Account owner; + @Mock private ServiceOfferingVO routerOffering; + @Mock private VMTemplateVO template; + @Mock + private PublicIp publicIpMock; + + @Mock + private RouterDeploymentDefinition routerDeploymentDefinitionMock; + + @Mock + private Ipv6Service ipv6ServiceMock; + + @Mock + private NicVO nicVoMock; + + @Mock + private Network networkMock; + + private NicProfile nicProfile = new NicProfile(); + @Before public void setUp() { - nwHelper._networkDao = networkDao; - nwHelper._networkModel = networkModel; + networkHelperSpy._networkDao = networkDao; + networkHelperSpy._networkModel = networkModel; + when(template.getId()).thenReturn(1L); when(template.isDynamicallyScalable()).thenReturn(true); when(virtualProvider.getId()).thenReturn(1L); @@ -107,7 +150,7 @@ public void setUp() { public void testSendCommandsToRouterWrongRouterVersion() throws AgentUnavailableException, OperationTimedoutException, ResourceUnavailableException { // Prepare - NetworkHelperImpl nwHelperUT = spy(this.nwHelper); + NetworkHelperImpl nwHelperUT = networkHelperSpy; VirtualRouter vr = mock(VirtualRouter.class); doReturn(false).when(nwHelperUT).checkRouterVersion(vr); @@ -122,7 +165,7 @@ public void testSendCommandsToRouterWrongRouterVersion() public void testSendCommandsToRouter() throws AgentUnavailableException, OperationTimedoutException, ResourceUnavailableException { // Prepare - NetworkHelperImpl nwHelperUT = spy(this.nwHelper); + NetworkHelperImpl nwHelperUT = networkHelperSpy; VirtualRouter vr = mock(VirtualRouter.class); when(vr.getHostId()).thenReturn(HOST_ID); doReturn(true).when(nwHelperUT).checkRouterVersion(vr); @@ -160,7 +203,7 @@ public void testSendCommandsToRouter() public void testSendCommandsToRouterWithTrueResult() throws AgentUnavailableException, OperationTimedoutException, ResourceUnavailableException { // Prepare - NetworkHelperImpl nwHelperUT = spy(this.nwHelper); + NetworkHelperImpl nwHelperUT = networkHelperSpy; VirtualRouter vr = mock(VirtualRouter.class); when(vr.getHostId()).thenReturn(HOST_ID); doReturn(true).when(nwHelperUT).checkRouterVersion(vr); @@ -198,7 +241,7 @@ public void testSendCommandsToRouterWithTrueResult() public void testSendCommandsToRouterWithNoAnswers() throws AgentUnavailableException, OperationTimedoutException, ResourceUnavailableException { // Prepare - NetworkHelperImpl nwHelperUT = spy(this.nwHelper); + NetworkHelperImpl nwHelperUT = networkHelperSpy; VirtualRouter vr = mock(VirtualRouter.class); when(vr.getHostId()).thenReturn(HOST_ID); doReturn(true).when(nwHelperUT).checkRouterVersion(vr); @@ -227,7 +270,7 @@ public void testCreateDomainRouter_New() { boolean offerHA = false; Long vpcId = 900L; when(routerDao.persist(any(DomainRouterVO.class))).thenAnswer(invocation -> invocation.getArgument(0)); - DomainRouterVO result = nwHelper.createOrUpdateDomainRouter( + DomainRouterVO result = networkHelperSpy.createOrUpdateDomainRouter( null, id, routerDeploymentDefinition, owner, userId, routerOffering, offerHA, vpcId, template); assertNotNull(result); assertEquals(id, result.getId()); @@ -261,11 +304,234 @@ public void testUpdateDomainRouter() { owner.getDomainId(), owner.getId(), userId, routerDeploymentDefinition.isRedundant(), VirtualRouter.RedundantState.UNKNOWN, offerHA, false, vpcId); existing.setDynamicallyScalable(false); - DomainRouterVO result = nwHelper.createOrUpdateDomainRouter( + DomainRouterVO result = networkHelperSpy.createOrUpdateDomainRouter( existing, id, routerDeploymentDefinition, owner, userId, routerOffering, offerHA, vpcId, template); verify(routerDao).update(existing.getId(), existing); assertEquals(template.getId(), result.getTemplateId()); assertEquals(Hypervisor.HypervisorType.KVM, result.getHypervisorType()); assertTrue(result.isDynamicallyScalable()); } + + + private NicProfile getExpectedNicProfile(boolean vxlan, String vlanTag) { + NicProfile nic = new NicProfile(); + nic.setDefaultNic(true); + nic.setIPv4Address("192.168.0.10"); + nic.setIPv4Gateway("192.168.0.1"); + nic.setIPv4Netmask("255.255.255.0"); + nic.setMacAddress("ff-ff-ff-ff-ff-ff"); + + if (vxlan) { + nic.setBroadcastType(Networks.BroadcastDomainType.Vxlan); + nic.setBroadcastUri(Networks.BroadcastDomainType.Vxlan.toUri(vlanTag)); + nic.setIsolationUri(Networks.BroadcastDomainType.Vxlan.toUri(vlanTag)); + } else { + nic.setBroadcastType(Networks.BroadcastDomainType.Vlan); + nic.setBroadcastUri(vlanTag != null ? Networks.BroadcastDomainType.Vlan.toUri(vlanTag) : null); + nic.setIsolationUri(vlanTag != null ? Networks.IsolationType.Vlan.toUri(vlanTag) : null); + } + + return nic; + } + + @Test + public void configurePublicVrNicBasedOnSourceNatIpTestConfigureVxLanNic() { + String vlanTag = "200"; + NicProfile expected = getExpectedNicProfile(true, vlanTag); + + Ip ipMock = Mockito.mock(Ip.class); + NetworkVO publicNetworkMock = Mockito.mock(NetworkVO.class); + long networkId = 1L; + Mockito.when(publicIpMock.getAddress()).thenReturn(ipMock); + Mockito.when(ipMock.addr()).thenReturn(expected.getIPv4Address()); + Mockito.when(publicIpMock.getGateway()).thenReturn(expected.getIPv4Gateway()); + Mockito.when(publicIpMock.getNetmask()).thenReturn(expected.getIPv4Netmask()); + Mockito.when(publicIpMock.getMacAddress()).thenReturn(expected.getMacAddress()); + Mockito.when(publicIpMock.getNetworkId()).thenReturn(networkId); + Mockito.when(networkDao.findById(networkId)).thenReturn(publicNetworkMock); + Mockito.when(publicNetworkMock.getBroadcastDomainType()).thenReturn(Networks.BroadcastDomainType.Vxlan); + Mockito.when(publicIpMock.getVlanTag()).thenReturn(vlanTag); + + networkHelperSpy.configurePublicVrNicBasedOnSourceNatIp(nicProfile, publicIpMock); + + Assert.assertTrue(nicProfile.isDefaultNic()); + Assert.assertEquals(expected.getIPv4Address(), nicProfile.getIPv4Address()); + Assert.assertEquals(expected.getIPv4Gateway(), nicProfile.getIPv4Gateway()); + Assert.assertEquals(expected.getIPv4Netmask(), nicProfile.getIPv4Netmask()); + Assert.assertEquals(expected.getMacAddress(), nicProfile.getMacAddress()); + Assert.assertEquals(expected.getBroadcastType(), nicProfile.getBroadcastType()); + Assert.assertEquals(expected.getBroadCastUri(), nicProfile.getBroadCastUri()); + Assert.assertEquals(expected.getIsolationUri(), nicProfile.getIsolationUri()); + } + + @Test + public void configurePublicVrNicBasedOnSourceNatIpTestConfigureVlanNicWithVlanTag() { + String vlanTag = "200"; + NicProfile expected = getExpectedNicProfile(false, vlanTag); + + Ip ipMock = Mockito.mock(Ip.class); + NetworkVO publicNetworkMock = Mockito.mock(NetworkVO.class); + long networkId = 1L; + Mockito.when(publicIpMock.getAddress()).thenReturn(ipMock); + Mockito.when(ipMock.addr()).thenReturn(expected.getIPv4Address()); + Mockito.when(publicIpMock.getGateway()).thenReturn(expected.getIPv4Gateway()); + Mockito.when(publicIpMock.getNetmask()).thenReturn(expected.getIPv4Netmask()); + Mockito.when(publicIpMock.getMacAddress()).thenReturn(expected.getMacAddress()); + Mockito.when(publicIpMock.getNetworkId()).thenReturn(networkId); + Mockito.when(networkDao.findById(networkId)).thenReturn(publicNetworkMock); + Mockito.when(publicNetworkMock.getBroadcastDomainType()).thenReturn(Networks.BroadcastDomainType.Vlan); + Mockito.when(publicIpMock.getVlanTag()).thenReturn(vlanTag); + + networkHelperSpy.configurePublicVrNicBasedOnSourceNatIp(nicProfile, publicIpMock); + + Assert.assertTrue(nicProfile.isDefaultNic()); + Assert.assertEquals(expected.getIPv4Address(), nicProfile.getIPv4Address()); + Assert.assertEquals(expected.getIPv4Gateway(), nicProfile.getIPv4Gateway()); + Assert.assertEquals(expected.getIPv4Netmask(), nicProfile.getIPv4Netmask()); + Assert.assertEquals(expected.getMacAddress(), nicProfile.getMacAddress()); + Assert.assertEquals(expected.getBroadcastType(), nicProfile.getBroadcastType()); + Assert.assertEquals(expected.getBroadCastUri(), nicProfile.getBroadCastUri()); + Assert.assertEquals(expected.getIsolationUri(), nicProfile.getIsolationUri()); + } + + @Test + public void configurePublicVrNicBasedOnSourceNatIpTestConfigureVlanNicWithNoVlanTag() { + String vlanTag = null; + NicProfile expected = getExpectedNicProfile(false, vlanTag); + + Ip ipMock = Mockito.mock(Ip.class); + NetworkVO publicNetworkMock = Mockito.mock(NetworkVO.class); + long networkId = 1L; + Mockito.when(publicIpMock.getAddress()).thenReturn(ipMock); + Mockito.when(ipMock.addr()).thenReturn(expected.getIPv4Address()); + Mockito.when(publicIpMock.getGateway()).thenReturn(expected.getIPv4Gateway()); + Mockito.when(publicIpMock.getNetmask()).thenReturn(expected.getIPv4Netmask()); + Mockito.when(publicIpMock.getMacAddress()).thenReturn(expected.getMacAddress()); + Mockito.when(publicIpMock.getNetworkId()).thenReturn(networkId); + Mockito.when(networkDao.findById(networkId)).thenReturn(publicNetworkMock); + Mockito.when(publicNetworkMock.getBroadcastDomainType()).thenReturn(Networks.BroadcastDomainType.Vlan); + Mockito.when(publicIpMock.getVlanTag()).thenReturn(vlanTag); + + networkHelperSpy.configurePublicVrNicBasedOnSourceNatIp(nicProfile, publicIpMock); + + Assert.assertTrue(nicProfile.isDefaultNic()); + Assert.assertEquals(expected.getIPv4Address(), nicProfile.getIPv4Address()); + Assert.assertEquals(expected.getIPv4Gateway(), nicProfile.getIPv4Gateway()); + Assert.assertEquals(expected.getIPv4Netmask(), nicProfile.getIPv4Netmask()); + Assert.assertEquals(expected.getMacAddress(), nicProfile.getMacAddress()); + Assert.assertEquals(expected.getBroadcastType(), nicProfile.getBroadcastType()); + Assert.assertEquals(expected.getBroadCastUri(), nicProfile.getBroadCastUri()); + Assert.assertEquals(expected.getIsolationUri(), nicProfile.getIsolationUri()); + } + + @Test + public void setPublicNicMacAddressSameAsPeerNicTestDoNothingWhenThereIsNoPeer() throws InsufficientAddressCapacityException { + String newMacAddress = "ff-ff-ff-ff-ff-ff"; + nicProfile.setIPv4Address("10.0.0.1"); + nicProfile.setMacAddress(newMacAddress); + Mockito.when(nicDaoMock.findByIp4AddressAndNetworkId(Mockito.anyString(), Mockito.anyLong())).thenReturn(null); + + networkHelperSpy.setPublicNicMacAddressSameAsPeerNic(nicProfile, networkMock, routerDeploymentDefinitionMock); + + Assert.assertEquals(newMacAddress, nicProfile.getMacAddress()); + } + + @Test + public void setPublicNicMacAddressSameAsPeerNicTestKeepMacAddress() throws InsufficientAddressCapacityException { + String peerMacAddress = "ff-ff-ff-ff-ff-ff"; + nicProfile.setIPv4Address("10.0.0.1"); + nicProfile.setMacAddress("ff-ff-ff-ff-ff-f1"); + + Mockito.when(nicDaoMock.findByIp4AddressAndNetworkId(Mockito.anyString(), Mockito.anyLong())).thenReturn(nicVoMock); + Mockito.when(nicVoMock.getMacAddress()).thenReturn(peerMacAddress); + Mockito.when(routerDeploymentDefinitionMock.getKeepMacAddressOnPublicNic()).thenReturn(true); + + networkHelperSpy.setPublicNicMacAddressSameAsPeerNic(nicProfile, networkMock, routerDeploymentDefinitionMock); + + Assert.assertEquals(peerMacAddress, nicProfile.getMacAddress()); + } + + @Test + public void setPublicNicMacAddressSameAsPeerNicTestDifferentMacAddressFetchingNewSourceNatIp() throws InsufficientAddressCapacityException { + String macAddress = "ff-ff-ff-ff-ff-f1"; + nicProfile.setIPv4Address("10.0.0.1"); + nicProfile.setMacAddress(macAddress); + PublicIp publicIpMock = Mockito.mock(PublicIp.class); + + Mockito.when(nicDaoMock.findByIp4AddressAndNetworkId(Mockito.anyString(), Mockito.anyLong())).thenReturn(nicVoMock); + Mockito.when(nicVoMock.getMacAddress()).thenReturn(macAddress); + Mockito.when(routerDeploymentDefinitionMock.getKeepMacAddressOnPublicNic()).thenReturn(false); + Mockito.when(routerDeploymentDefinitionMock.getSourceNatIP()).thenReturn(publicIpMock); + Mockito.doNothing().when(networkHelperSpy).configurePublicVrNicBasedOnSourceNatIp(nicProfile, publicIpMock); + + networkHelperSpy.setPublicNicMacAddressSameAsPeerNic(nicProfile, networkMock, routerDeploymentDefinitionMock); + + Mockito.verify(routerDeploymentDefinitionMock).findSourceNatIP(); + Mockito.verify(networkHelperSpy).configurePublicVrNicBasedOnSourceNatIp(nicProfile, publicIpMock); + } + + @Test + public void setPublicNicMacAddressSameAsPeerNicTestDifferentMacAddressNotFetchingNewSourceNatIp() throws InsufficientAddressCapacityException { + String newMacAddress = "ff-ff-ff-ff-ff-ff"; + nicProfile.setIPv4Address("10.0.0.1"); + nicProfile.setMacAddress(newMacAddress); + + Mockito.when(nicDaoMock.findByIp4AddressAndNetworkId(Mockito.anyString(), Mockito.anyLong())).thenReturn(nicVoMock); + Mockito.when(nicVoMock.getMacAddress()).thenReturn("ff-ff-ff-ff-ff-f1"); + Mockito.when(routerDeploymentDefinitionMock.getKeepMacAddressOnPublicNic()).thenReturn(false); + + networkHelperSpy.setPublicNicMacAddressSameAsPeerNic(nicProfile, networkMock, routerDeploymentDefinitionMock); + + Mockito.verify(routerDeploymentDefinitionMock, Mockito.never()).findSourceNatIP(); + Mockito.verify(networkHelperSpy, Mockito.never()).configurePublicVrNicBasedOnSourceNatIp(Mockito.any(), Mockito.any()); + Assert.assertEquals(newMacAddress, nicProfile.getMacAddress()); + } + + @Test + public void configurePublicNicTestReturnEmptyMapWhenNetworkIsNotPublic() throws InsufficientAddressCapacityException { + Mockito.when(routerDeploymentDefinitionMock.isPublicNetwork()).thenReturn(false); + + Map> nic = networkHelperSpy.configurePublicNic(routerDeploymentDefinitionMock, false); + Assert.assertTrue(nic.isEmpty()); + } + + @Test + public void configurePublicNicTestConfigureDeviceId() throws InsufficientAddressCapacityException { + PublicIp publicIpMock = Mockito.mock(PublicIp.class); + NetworkOffering networkOfferingMock = Mockito.mock(NetworkOffering.class); + + Mockito.when(routerDeploymentDefinitionMock.isPublicNetwork()).thenReturn(true); + Mockito.when(routerDeploymentDefinitionMock.getSourceNatIP()).thenReturn(publicIpMock); + Mockito.doNothing().when(networkHelperSpy).configurePublicVrNicBasedOnSourceNatIp(Mockito.any(), Mockito.any()); + Mockito.doReturn(List.of(networkOfferingMock)).when(networkModel).getSystemAccountNetworkOfferings(Mockito.any()); + Mockito.doReturn(List.of(networkMock)).when(networkOrchestrationService).setupNetwork( + Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyBoolean() + ); + Mockito.doNothing().when(networkHelperSpy).setPublicNicMacAddressSameAsPeerNic(Mockito.any(), Mockito.any(), Mockito.any()); + + Map> nic = networkHelperSpy.configurePublicNic(routerDeploymentDefinitionMock, true); + Integer nicDeviceId = nic.get(networkMock).get(0).getDeviceId(); + Assert.assertEquals(2, nicDeviceId.intValue()); + } + + @Test + public void configurePublicNicTestUpdateGuestNetworksIpv6Nic() throws InsufficientAddressCapacityException { + PublicIp publicIpMock = Mockito.mock(PublicIp.class); + NetworkOffering networkOfferingMock = Mockito.mock(NetworkOffering.class); + DeployDestination deployDestinationMock = Mockito.mock(DeployDestination.class); + + Mockito.when(routerDeploymentDefinitionMock.isPublicNetwork()).thenReturn(true); + Mockito.when(routerDeploymentDefinitionMock.getSourceNatIP()).thenReturn(publicIpMock); + Mockito.doNothing().when(networkHelperSpy).configurePublicVrNicBasedOnSourceNatIp(Mockito.any(), Mockito.any()); + Mockito.doReturn(List.of(networkOfferingMock)).when(networkModel).getSystemAccountNetworkOfferings(Mockito.any()); + Mockito.doReturn(List.of(networkMock)).when(networkOrchestrationService).setupNetwork( + Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyBoolean() + ); + Mockito.doNothing().when(networkHelperSpy).setPublicNicMacAddressSameAsPeerNic(Mockito.any(), Mockito.any(), Mockito.any()); + Mockito.when(routerDeploymentDefinitionMock.getGuestNetwork()).thenReturn(networkMock); + Mockito.when(routerDeploymentDefinitionMock.getDest()).thenReturn(deployDestinationMock); + + Map> nic = networkHelperSpy.configurePublicNic(routerDeploymentDefinitionMock, false); + Mockito.verify(ipv6ServiceMock).updateNicIpv6(Mockito.eq(nic.get(networkMock).get(0)), Mockito.any(), Mockito.eq(networkMock)); + } } 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 ff34d72c218d..4d2df5cf5597 100644 --- a/server/src/test/java/com/cloud/network/vpc/VpcManagerImplTest.java +++ b/server/src/test/java/com/cloud/network/vpc/VpcManagerImplTest.java @@ -492,7 +492,7 @@ public void testCreateVpcDnsOfferingServiceFailure() { try { doNothing().when(resourceLimitService).checkResourceLimit(account, Resource.ResourceType.vpc); manager.createVpc(zoneId, vpcOfferingId, vpcOwnerId, vpcName, vpcName, ip4Cidr, vpcDomain, - ip4Dns[0], null, null, null, true, 1500, null, null, null, false); + ip4Dns[0], null, null, null, true, 1500, null, null, null, false, true); } catch (ResourceAllocationException e) { Assert.fail(String.format("failure with exception: %s", e.getMessage())); } @@ -504,7 +504,7 @@ public void testCreateVpcDnsIpv6OfferingFailure() { try { doNothing().when(resourceLimitService).checkResourceLimit(account, Resource.ResourceType.vpc); manager.createVpc(zoneId, vpcOfferingId, vpcOwnerId, vpcName, vpcName, ip4Cidr, vpcDomain, - ip4Dns[0], ip4Dns[1], ip6Dns[0], null, true, 1500, null, null, null, false); + ip4Dns[0], ip4Dns[1], ip6Dns[0], null, true, 1500, null, null, null, false, true); } catch (ResourceAllocationException e) { Assert.fail(String.format("failure with exception: %s", e.getMessage())); } @@ -519,7 +519,7 @@ public void testCreateVpc() { try { doNothing().when(resourceLimitService).checkResourceLimit(account, Resource.ResourceType.vpc); manager.createVpc(zoneId, vpcOfferingId, vpcOwnerId, vpcName, vpcName, ip4Cidr, vpcDomain, - ip4Dns[0], ip4Dns[1], null, null, true, 1500, null, null, null, false); + ip4Dns[0], ip4Dns[1], null, null, true, 1500, null, null, null, false, true); } catch (ResourceAllocationException e) { Assert.fail(String.format("failure with exception: %s", e.getMessage())); } @@ -536,7 +536,7 @@ public void testCreateRoutedVpc() { try { doNothing().when(resourceLimitService).checkResourceLimit(account, Resource.ResourceType.vpc); manager.createVpc(zoneId, vpcOfferingId, vpcOwnerId, vpcName, vpcName, ip4Cidr, vpcDomain, - ip4Dns[0], ip4Dns[1], null, null, true, 1500, null, null, null, false); + ip4Dns[0], ip4Dns[1], null, null, true, 1500, null, null, null, false, true); } catch (ResourceAllocationException e) { Assert.fail(String.format("failure with exception: %s", e.getMessage())); } @@ -559,7 +559,7 @@ public void testCreateRoutedVpcWithDynamicRouting() { try { doNothing().when(resourceLimitService).checkResourceLimit(account, Resource.ResourceType.vpc); manager.createVpc(zoneId, vpcOfferingId, vpcOwnerId, vpcName, vpcName, null, vpcDomain, - ip4Dns[0], ip4Dns[1], null, null, true, 1500, 24, null, bgpPeerIds, false); + ip4Dns[0], ip4Dns[1], null, null, true, 1500, 24, null, bgpPeerIds, false, true); } catch (ResourceAllocationException e) { Assert.fail(String.format("failure with exception: %s", e.getMessage())); } diff --git a/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java index de768388b449..ce9cb41b1de2 100644 --- a/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java +++ b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java @@ -704,11 +704,19 @@ public Network createPrivateNetwork(final long networkOfferingId, final String n public Network createGuestNetwork(long networkOfferingId, String name, String displayText, String gateway, String cidr, String vlanId, boolean bypassVlanOverlapCheck, String networkDomain, Account owner, Long domainId, PhysicalNetwork physicalNetwork, long zoneId, ACLType aclType, Boolean subdomainAccess, Long vpcId, String gatewayv6, String cidrv6, Boolean displayNetworkEnabled, String isolatedPvlan, Network.PVlanType isolatedPvlanType, String externalId, String routerIp, String routerIpv6, - String ip4Dns1, String ip4Dns2, String ip6Dns1, String ip6Dns2, Pair vrIfaceMTUs, Integer networkCidrSize) throws ConcurrentOperationException, ResourceAllocationException { + String ip4Dns1, String ip4Dns2, String ip6Dns1, String ip6Dns2, Pair vrIfaceMTUs, Integer networkCidrSize) throws ConcurrentOperationException { // TODO Auto-generated method stub return null; } + @Override + public Network createGuestNetwork(long networkOfferingId, String name, String displayText, String gateway, String cidr, String vlanId, boolean bypassVlanOverlapCheck, String networkDomain, + Account owner, Long domainId, PhysicalNetwork physicalNetwork, long zoneId, ACLType aclType, Boolean subdomainAccess, Long vpcId, String gatewayv6, + String cidrv6, Boolean displayNetworkEnabled, String isolatedPvlan, Network.PVlanType isolatedPvlanType, String externalId, String routerIp, String routerIpv6, + String ip4Dns1, String ip4Dns2, String ip6Dns1, String ip6Dns2, Pair vrIfaceMTUs, Integer networkCidrSize, Boolean keepMacAddressOnPublicNic) throws ConcurrentOperationException { + return null; + } + /* (non-Javadoc) * @see com.cloud.network.NetworkManager#getPasswordResetProvider(com.cloud.network.Network) */ diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 6f3a623b8092..48fb3f882aa4 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -1429,6 +1429,7 @@ "label.javadistribution": "Java Runtime Distribution", "label.javaversion": "Java Runtime Version", "label.keep": "Keep", +"label.keep.mac.address.on.public.nic": "Use same MAC address for public NIC of VRs", "label.kernelversion": "Kernel Version", "label.key": "Key", "label.keyboard": "Keyboard language", diff --git a/ui/public/locales/pt_BR.json b/ui/public/locales/pt_BR.json index c7ca36c1278d..d2910a3a2b98 100644 --- a/ui/public/locales/pt_BR.json +++ b/ui/public/locales/pt_BR.json @@ -904,6 +904,7 @@ "label.items.selected": "item(ns) selecionados", "label.japanese.keyboard": "Teclado japon\u00eas", "label.keep": "Manter", +"label.keep.mac.address.on.public.nic": "Utilizar o mesmo endere\u00e7o MAC para a NIC p\u00fablica dos VRs", "label.key": "Chave", "label.keyboard": "Linguagem do teclado", "label.keyboardtype": "Tipo de teclado", diff --git a/ui/src/config/section/network.js b/ui/src/config/section/network.js index 37cdd0c8b98a..50c2ff4250b0 100644 --- a/ui/src/config/section/network.js +++ b/ui/src/config/section/network.js @@ -49,9 +49,14 @@ export default { return fields }, details: () => { - var fields = ['name', 'id', 'description', 'type', 'traffictype', 'vpcid', 'vlan', 'broadcasturi', 'cidr', 'ip6cidr', 'netmask', 'gateway', 'asnumber', 'aclname', 'ispersistent', 'restartrequired', 'reservediprange', 'redundantrouter', 'networkdomain', 'egressdefaultpolicy', 'zonename', 'account', 'domainpath', 'associatednetwork', 'associatednetworkid', 'ip4routing', 'ip6firewall', 'ip6routing', 'ip6routes', 'dns1', 'dns2', 'ip6dns1', 'ip6dns2', 'publicmtu', 'privatemtu'] - if (!isAdmin()) { - fields = fields.filter(function (e) { return e !== 'broadcasturi' }) + const fields = ['name', 'id', 'description', 'type', 'traffictype', 'vpcid', 'vlan', 'cidr', 'ip6cidr', 'netmask', 'gateway', 'asnumber', 'aclname', 'ispersistent', 'restartrequired', 'reservediprange', 'redundantrouter', 'networkdomain', 'egressdefaultpolicy', 'zonename', 'account', 'domainpath', 'associatednetwork', 'associatednetworkid', 'ip4routing', 'ip6firewall', 'ip6routing', 'ip6routes', 'dns1', 'dns2', 'ip6dns1', 'ip6dns2', 'publicmtu', 'privatemtu'] + if (isAdmin()) { + const vlanIndex = fields.findIndex(detail => detail === 'vlan') + fields.splice(vlanIndex + 1, 0, 'broadcasturi') + fields.push({ + field: 'keepmacaddressonpublicnic', + customTitle: 'keep.mac.address.on.public.nic' + }) } return fields }, @@ -233,7 +238,16 @@ export default { fields.push(...['domain', 'zonename']) return fields }, - details: ['name', 'id', 'displaytext', 'cidr', 'networkdomain', 'ip4routing', 'ip4routes', 'ip6routes', 'ispersistent', 'redundantvpcrouter', 'restartrequired', 'zonename', 'account', 'domain', 'dns1', 'dns2', 'ip6dns1', 'ip6dns2', 'publicmtu'], + details: () => { + const fields = ['name', 'id', 'displaytext', 'cidr', 'networkdomain', 'ip4routing', 'ip4routes', 'ip6routes', 'ispersistent', 'redundantvpcrouter', 'restartrequired', 'zonename', 'account', 'domain', 'dns1', 'dns2', 'ip6dns1', 'ip6dns2', 'publicmtu'] + if (isAdmin()) { + fields.push({ + field: 'keepmacaddressonpublicnic', + customTitle: 'keep.mac.address.on.public.nic' + }) + } + return fields + }, searchFilters: ['name', 'zoneid', 'domainid', 'account', 'restartrequired', 'tags'], related: [{ name: 'vm', @@ -268,7 +282,13 @@ export default { icon: 'edit-outlined', label: 'label.edit', dataView: true, - args: ['name', 'displaytext', 'publicmtu', 'sourcenatipaddress'] + args: () => { + const fields = ['name', 'displaytext', 'publicmtu', 'sourcenatipaddress'] + if (isAdmin()) { + fields.push('keepmacaddressonpublicnic') + } + return fields + } }, { api: 'restartVPC', diff --git a/ui/src/views/AutogenView.vue b/ui/src/views/AutogenView.vue index f4aa842d8f2b..066226980c5f 100644 --- a/ui/src/views/AutogenView.vue +++ b/ui/src/views/AutogenView.vue @@ -294,6 +294,11 @@ :title="$t('label.default.network.' + field.name + '.isolated.network')" :tooltip="field.description" /> + + + + +
+ + + + + + + +