diff --git a/api/src/main/java/com/cloud/network/vpc/VpcOffering.java b/api/src/main/java/com/cloud/network/vpc/VpcOffering.java index f84602232159..faeba6280ab8 100644 --- a/api/src/main/java/com/cloud/network/vpc/VpcOffering.java +++ b/api/src/main/java/com/cloud/network/vpc/VpcOffering.java @@ -86,4 +86,6 @@ public enum State { Boolean isSpecifyAsNumber(); boolean isConserveMode(); + + Integer getPublicNetworkRate(); } diff --git a/api/src/main/java/com/cloud/network/vpc/VpcProvisioningService.java b/api/src/main/java/com/cloud/network/vpc/VpcProvisioningService.java index 891cfb02d9df..8bde0fefeb34 100644 --- a/api/src/main/java/com/cloud/network/vpc/VpcProvisioningService.java +++ b/api/src/main/java/com/cloud/network/vpc/VpcProvisioningService.java @@ -42,7 +42,7 @@ VpcOffering createVpcOffering(String name, String displayText, List supp Map serviceCapabilitystList, NetUtils.InternetProtocol internetProtocol, Long serviceOfferingId, String externalProvider, NetworkOffering.NetworkMode networkMode, List domainIds, List zoneIds, VpcOffering.State state, - NetworkOffering.RoutingMode routingMode, boolean specifyAsNumber, boolean conserveMode); + NetworkOffering.RoutingMode routingMode, boolean specifyAsNumber, boolean conserveMode, Integer publicNetworkRate); Pair,Integer> listVpcOfferings(ListVPCOfferingsCmd cmd); diff --git a/api/src/main/java/com/cloud/vm/Nic.java b/api/src/main/java/com/cloud/vm/Nic.java index 3722e5769c92..08ef5ea82469 100644 --- a/api/src/main/java/com/cloud/vm/Nic.java +++ b/api/src/main/java/com/cloud/vm/Nic.java @@ -168,5 +168,7 @@ public enum ReservationStrategy { Integer getMtu(); + Integer getNetworkRate(); + boolean isEnabled(); } 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 f74c46161180..da0398ebd058 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -729,6 +729,7 @@ public class ApiConstants { public static final String IS_USER_DEFINED = "isuserdefined"; public static final String AVAILABILITY = "availability"; public static final String NETWORKRATE = "networkrate"; + public static final String PUBLIC_NETWORK_RATE = "publicnetworkrate"; public static final String HOST_TAGS = "hosttags"; public static final String SSH_KEYPAIR = "keypair"; public static final String SSH_KEYPAIRS = "keypairs"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java index 2b934a60da7a..0b13d6f6035e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java @@ -165,6 +165,11 @@ public class CreateVPCOfferingCmd extends BaseAsyncCreateCmd { description = "True if the VPC offering is IP conserve mode enabled, allowing public IPs to be used across multiple VPC tiers. Default value is false") private Boolean conserveMode; + @Parameter(name = ApiConstants.PUBLIC_NETWORK_RATE, type = CommandType.INTEGER, + since = "24.0.0", + description = "Data transfer rate in megabits per second allowed for a VPC's public gateway (internet-facing network), created with this offering. Use -1 for unlimited. If not specified, VPCs created with this offering use the zone level setting vpc.public.network.throttling.rate") + private Integer publicNetworkRate; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// @@ -318,6 +323,10 @@ public boolean isConserveMode() { return BooleanUtils.toBoolean(conserveMode); } + public Integer getPublicNetworkRate() { + return publicNetworkRate; + } + @Override public void create() throws ResourceAllocationException { VpcOffering vpcOff = _vpcProvSvc.createVpcOffering(this); 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 aeaf333540c7..75cc22a4d109 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 @@ -311,6 +311,10 @@ public class NetworkResponse extends BaseResponseWithAssociatedNetwork implement @Param(description = "MTU configured on the network VR's private interfaces") private Integer privateMtu; + @SerializedName(ApiConstants.NETWORKRATE) + @Param(description = "Network rate (in Mb/s) configured for the Guest interface of this network; -1 if unlimited", since = "24.0.0") + private Integer networkRate; + @SerializedName(ApiConstants.IP6_DNS1) @Param(description = "The first IPv6 DNS for the network", since = "4.18.0") private String ipv6Dns1; @@ -707,6 +711,14 @@ public void setPrivateMtu(Integer privateMtu) { this.privateMtu = privateMtu; } + public Integer getNetworkRate() { + return networkRate; + } + + public void setNetworkRate(Integer networkRate) { + this.networkRate = networkRate; + } + public void setIpv6Dns1(String ipv6Dns1) { this.ipv6Dns1 = ipv6Dns1; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/NicResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/NicResponse.java index 5ad41ad62244..df0aae3128d8 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/NicResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/NicResponse.java @@ -138,6 +138,10 @@ public class NicResponse extends BaseResponse { @Param(description = "MTU configured on the NIC", since="4.18.0") private Integer mtu; + @SerializedName(ApiConstants.NETWORKRATE) + @Param(description = "Network rate (in Mb/s) configured for the NIC; -1 if unlimited", since = "24.0.0") + private Integer networkRate; + @SerializedName(ApiConstants.PUBLIC_IP_ID) @Param(description = "Public IP address ID associated with this NIC via Static NAT rule") private String publicIpId; @@ -413,6 +417,14 @@ public void setMtu(Integer mtu) { this.mtu = mtu; } + public Integer getNetworkRate() { + return networkRate; + } + + public void setNetworkRate(Integer networkRate) { + this.networkRate = networkRate; + } + public String getVpcId() { return vpcId; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VpcOfferingResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VpcOfferingResponse.java index 2e821dae52de..ebf95ab2e99d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/VpcOfferingResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/VpcOfferingResponse.java @@ -106,6 +106,10 @@ public class VpcOfferingResponse extends BaseResponse { @Param(description = "True if the VPC offering is IP conserve mode enabled, allowing public IP services to be used across multiple VPC tiers.", since = "4.23.0") private Boolean conserveMode; + @SerializedName(ApiConstants.PUBLIC_NETWORK_RATE) + @Param(description = "Data transfer rate in megabits per second allowed for a VPC's public gateway (internet-facing network), created with this offering; -1 if unlimited. Not returned if no rate is set on the offering, in which case VPCs created with it use the zone level setting vpc.public.network.throttling.rate", since = "24.0.0") + private Integer publicNetworkRate; + public void setId(String id) { this.id = id; } @@ -213,4 +217,8 @@ public Boolean getConserveMode() { public void setConserveMode(Boolean conserveMode) { this.conserveMode = conserveMode; } + + public void setPublicNetworkRate(Integer publicNetworkRate) { + this.publicNetworkRate = publicNetworkRate; + } } 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 34d50d5b9f92..c68324661142 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 @@ -77,6 +77,10 @@ public class VpcResponse extends BaseResponseWithAnnotations implements Controll @Param(description = "true if VPC offering is ip conserve mode enabled", since = "4.23") private Boolean vpcOfferingConserveMode; + @SerializedName(ApiConstants.PUBLIC_NETWORK_RATE) + @Param(description = "Data transfer rate in megabits per second allowed for this VPC's public gateway (internet-facing network); -1 if unlimited", since = "24.0.0") + private Integer publicNetworkRate; + @SerializedName(ApiConstants.CREATED) @Param(description = "The date this VPC was created") private Date created; @@ -209,6 +213,10 @@ public void setVpcOfferingConserveMode(Boolean vpcOfferingConserveMode) { this.vpcOfferingConserveMode = vpcOfferingConserveMode; } + public void setPublicNetworkRate(Integer publicNetworkRate) { + this.publicNetworkRate = publicNetworkRate; + } + public void setCreated(final Date created) { this.created = created; } diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmdTest.java index 290a2850c9a6..27a9e803ea39 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmdTest.java @@ -71,4 +71,16 @@ public void testCreateVPCOfferingWithEmptyDisplayText() { Assert.assertEquals(cmd.getDisplayText(), netName); } + @Test + public void testPublicNetworkRateIsNullWhenNotSpecified() { + Assert.assertNull(new CreateVPCOfferingCmd().getPublicNetworkRate()); + } + + @Test + public void testPublicNetworkRateReturnsSpecifiedValue() { + CreateVPCOfferingCmd cmd = new CreateVPCOfferingCmd(); + ReflectionTestUtils.setField(cmd, "publicNetworkRate", 100); + Assert.assertEquals(Integer.valueOf(100), cmd.getPublicNetworkRate()); + } + } diff --git a/api/src/test/java/org/apache/cloudstack/api/response/NetworkRateResponseTest.java b/api/src/test/java/org/apache/cloudstack/api/response/NetworkRateResponseTest.java new file mode 100644 index 000000000000..3cf436277395 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/response/NetworkRateResponseTest.java @@ -0,0 +1,80 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.response; + +import org.junit.Assert; +import org.junit.Test; +import org.springframework.test.util.ReflectionTestUtils; + +public class NetworkRateResponseTest { + + @Test + public void networkResponseNetworkRateIsUnsetByDefault() { + Assert.assertNull(new NetworkResponse().getNetworkRate()); + } + + @Test + public void networkResponseKeepsNetworkRate() { + final NetworkResponse response = new NetworkResponse(); + + response.setNetworkRate(250); + Assert.assertEquals(Integer.valueOf(250), response.getNetworkRate()); + + response.setNetworkRate(-1); + Assert.assertEquals(Integer.valueOf(-1), response.getNetworkRate()); + } + + @Test + public void nicResponseNetworkRateIsUnsetByDefault() { + Assert.assertNull(new NicResponse().getNetworkRate()); + } + + @Test + public void nicResponseKeepsNetworkRate() { + final NicResponse response = new NicResponse(); + + response.setNetworkRate(120); + Assert.assertEquals(Integer.valueOf(120), response.getNetworkRate()); + + response.setNetworkRate(-1); + Assert.assertEquals(Integer.valueOf(-1), response.getNetworkRate()); + } + + @Test + public void vpcResponseKeepsPublicNetworkRate() { + final VpcResponse response = new VpcResponse(); + Assert.assertNull(ReflectionTestUtils.getField(response, "publicNetworkRate")); + + response.setPublicNetworkRate(300); + Assert.assertEquals(300, ReflectionTestUtils.getField(response, "publicNetworkRate")); + + response.setPublicNetworkRate(-1); + Assert.assertEquals(-1, ReflectionTestUtils.getField(response, "publicNetworkRate")); + } + + @Test + public void vpcOfferingResponseKeepsPublicNetworkRate() { + final VpcOfferingResponse response = new VpcOfferingResponse(); + Assert.assertNull(ReflectionTestUtils.getField(response, "publicNetworkRate")); + + response.setPublicNetworkRate(300); + Assert.assertEquals(300, ReflectionTestUtils.getField(response, "publicNetworkRate")); + + response.setPublicNetworkRate(-1); + Assert.assertEquals(-1, ReflectionTestUtils.getField(response, "publicNetworkRate")); + } +} 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 109a44488ec4..10d7c0e959f0 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 @@ -136,7 +136,10 @@ public interface NetworkOrchestrationService { Scope.Global); ConfigKey VmNetworkThrottlingRate = new ConfigKey("Network", Integer.class, "vm.network.throttling.rate", "200", - "Default data transfer rate in megabits per second allowed in User vm's default network.", true, ConfigKey.Scope.Zone); + "Default data transfer rate in megabits per second allowed in User vm's networks, used when the compute offering does not specify a rate.", true, ConfigKey.Scope.Zone); + + ConfigKey VpcPublicNetworkThrottlingRate = new ConfigKey<>("Network", Integer.class, "vpc.public.network.throttling.rate", "-1", + "Default data transfer rate in megabits per second allowed for a VPC's public/internet-facing network, used when the VPC offering does not specify a rate. -1 means unlimited.", true, ConfigKey.Scope.Zone); List setupNetwork(Account owner, NetworkOffering offering, DeploymentPlan plan, String name, String displayText, boolean isDefault) throws ConcurrentOperationException; diff --git a/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java b/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java index 5909d098db8b..73213c10be33 100644 --- a/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java +++ b/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java @@ -82,6 +82,8 @@ public interface ConfigurationManager { Integer getServiceOfferingNetworkRate(long serviceOfferingId, Long dataCenterId); + Integer getVpcOfferingNetworkRate(long vpcOfferingId, Long dataCenterId); + /** * Updates a configuration entry with a new value */ 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 8af75562b31c..b65005b05eb9 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 @@ -777,6 +777,11 @@ private void updateRouterIpInNetworkDetails(Long networkId, String routerIp, Str } } + private void saveNetworkRateInDetails(long networkId, NetworkOffering offering, long dataCenterId) { + Integer rate = _configMgr.getNetworkOfferingNetworkRate(offering.getId(), dataCenterId); + networkDetailsDao.addDetail(networkId, ApiConstants.NETWORKRATE, String.valueOf(rate), true); + } + @Override public List setupNetwork(final Account owner, final NetworkOffering offering, final DeploymentPlan plan, final String name, final String displayText, final boolean isDefault) throws ConcurrentOperationException { @@ -852,6 +857,7 @@ public void doInTransactionWithoutResult(final TransactionStatus status) { } updateRouterIpInNetworkDetails(networkPersisted.getId(), network.getRouterIp(), network.getRouterIpv6()); + saveNetworkRateInDetails(networkPersisted.getId(), offering, plan.getDataCenterId()); if (predefined instanceof NetworkVO && guru instanceof NetworkGuruAdditionalFunctions) { final NetworkGuruAdditionalFunctions functions = (NetworkGuruAdditionalFunctions) guru; @@ -1227,14 +1233,15 @@ public Pair allocateNic(final NicProfile requested, final N NicVO vo = checkForRaceAndAllocateNic(requested, network, isDefaultNic, deviceId, vm); final Integer networkRate = _networkModel.getNetworkRate(network.getId(), vm.getId()); + vo.setNetworkRate(networkRate); final NicProfile vmNic = new NicProfile(vo, network, vo.getBroadcastUri(), vo.getIsolationUri(), networkRate, _networkModel.isSecurityGroupSupportedInNetwork(network), _networkModel.getNetworkTag(vm.getHypervisorType(), network)); if (vm.getType() == Type.DomainRouter) { Pair networks = getGuestNetworkRouterAndVpcDetails(vm.getId()); setMtuDetailsInVRNic(networks, network, vo); - _nicDao.update(vo.getId(), vo); setMtuInVRNicProfile(networks, network.getTrafficType(), vmNic); } + _nicDao.update(vo.getId(), vo); return new Pair<>(vmNic, Integer.valueOf(deviceId)); } @@ -2300,6 +2307,8 @@ public NicProfile prepareNic(final VirtualMachineProfile vmProfile, final Deploy nic.setState(Nic.State.Reserved); } + nic.setNetworkRate(networkRate); + if (vmProfile.getType() == Type.DomainRouter) { Pair networks = getGuestNetworkRouterAndVpcDetails(vmProfile.getId()); setMtuDetailsInVRNic(networks, network, nic); @@ -4962,6 +4971,8 @@ public NicVO doInTransaction(TransactionStatus status) { } final Integer networkRate = _networkModel.getNetworkRate(network.getId(), vm.getId()); + vo.setNetworkRate(networkRate); + _nicDao.update(vo.getId(), vo); final NicProfile vmNic = new NicProfile(vo, network, vo.getBroadcastUri(), vo.getIsolationUri(), networkRate, _networkModel.isSecurityGroupSupportedInNetwork(network), _networkModel.getNetworkTag(vm.getHypervisorType(), network)); @@ -5066,7 +5077,7 @@ public String getConfigComponentName() { @Override public ConfigKey[] getConfigKeys() { return new ConfigKey[]{NetworkGcWait, NetworkGcInterval, NetworkLockTimeout, DeniedRoutes, - GuestDomainSuffix, NetworkThrottlingRate, VmNetworkThrottlingRate, MinVRVersion, DhcpLeaseTimeout, + GuestDomainSuffix, NetworkThrottlingRate, VmNetworkThrottlingRate, VpcPublicNetworkThrottlingRate, MinVRVersion, DhcpLeaseTimeout, PromiscuousMode, MacAddressChanges, ForgedTransmits, MacLearning, RollingRestartEnabled, TUNGSTEN_ENABLED, NSX_ENABLED, NETRIS_ENABLED, NETWORK_LB_HAPROXY_MAX_CONN, NETWORK_LB_HAPROXY_IDLE_TIMEOUT}; diff --git a/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestratorTest.java b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestratorTest.java index 66f5b699cc46..356dde7350dd 100644 --- a/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestratorTest.java +++ b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestratorTest.java @@ -32,10 +32,12 @@ import java.util.List; import java.util.Map; +import com.cloud.configuration.ConfigurationManager; import com.cloud.dc.DataCenter; import com.cloud.exception.InsufficientVirtualNetworkCapacityException; import com.cloud.network.IpAddressManager; import com.cloud.utils.Pair; +import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.extension.Extension; import org.apache.cloudstack.extension.ExtensionHelper; import org.apache.cloudstack.framework.extensions.network.NetworkExtensionElement; @@ -68,6 +70,7 @@ import com.cloud.network.dao.IPAddressDao; import com.cloud.network.dao.IPAddressVO; import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkDetailsDao; import com.cloud.network.dao.NetworkServiceMapDao; import com.cloud.network.dao.NetworkVO; import com.cloud.network.dao.PhysicalNetworkVO; @@ -602,6 +605,8 @@ private void encodeVlanIdIntoBroadcastUriPrepareAndTest(String vlanId, String is Assert.assertEquals(expectedUri, resultUri.toString()); } + private NicVO preparedNicVO; + private NicProfile prepareMocksAndRunPrepareNic(VirtualMachine.Type vmType, boolean isDefaultNic, boolean isVpcRouter, boolean routerResourceHasCustomDns) { Hypervisor.HypervisorType hypervisorType = Hypervisor.HypervisorType.KVM; Long nicId = 1L; @@ -609,6 +614,7 @@ private NicProfile prepareMocksAndRunPrepareNic(VirtualMachine.Type vmType, bool Long networkId = 1L; Integer networkRate = 200; Network network = Mockito.mock(Network.class); + Mockito.when(network.getId()).thenReturn(networkId); Mockito.when(network.getGuruName()).thenReturn(GuestNetworkGuru.class.getSimpleName()); Mockito.when(network.getDns1()).thenReturn(ip4Dns[0]); Mockito.when(network.getDns2()).thenReturn(ip4Dns[1]); @@ -616,6 +622,7 @@ private NicProfile prepareMocksAndRunPrepareNic(VirtualMachine.Type vmType, bool Mockito.when(network.getIp6Dns2()).thenReturn(ip6Dns[1]); Mockito.when(testOrchestrator._networkModel.getNetworkRate(networkId, vmId)).thenReturn(networkRate); NicVO nicVO = Mockito.mock(NicVO.class); + preparedNicVO = nicVO; Mockito.when(nicVO.isDefaultNic()).thenReturn(isDefaultNic); Mockito.when(testOrchestrator._nicDao.findById(nicId)).thenReturn(nicVO); Mockito.when(testOrchestrator._nicDao.update(nicId, nicVO)).thenReturn(true); @@ -680,6 +687,46 @@ private NicProfile prepareMocksAndRunPrepareNic(VirtualMachine.Type vmType, bool return profile; } + @Test + public void testPrepareNicStoresNetworkRateOnUserVmNic() { + prepareMocksAndRunPrepareNic(Type.User, true, false, false); + + Mockito.verify(preparedNicVO).setNetworkRate(200); + } + + @Test + public void testPrepareNicStoresNetworkRateOnVpcRouterNic() { + prepareMocksAndRunPrepareNic(Type.DomainRouter, true, true, true); + + Mockito.verify(preparedNicVO).setNetworkRate(200); + } + + @Test + public void testSaveNetworkRateInDetailsStoresOfferingRate() { + final NetworkDetailsDao networkDetailsDao = mock(NetworkDetailsDao.class); + final ConfigurationManager configMgr = mock(ConfigurationManager.class); + testOrchestrator.networkDetailsDao = networkDetailsDao; + testOrchestrator._configMgr = configMgr; + Mockito.when(configMgr.getNetworkOfferingNetworkRate(networkOfferingId, 7L)).thenReturn(300); + + ReflectionTestUtils.invokeMethod(testOrchestrator, "saveNetworkRateInDetails", 11L, networkOffering, 7L); + + Mockito.verify(networkDetailsDao).addDetail(11L, ApiConstants.NETWORKRATE, "300", true); + } + + @Test + public void testSaveNetworkRateInDetailsStoresUnlimitedRate() { + final NetworkDetailsDao networkDetailsDao = mock(NetworkDetailsDao.class); + final ConfigurationManager configMgr = mock(ConfigurationManager.class); + testOrchestrator.networkDetailsDao = networkDetailsDao; + testOrchestrator._configMgr = configMgr; + Mockito.when(configMgr.getNetworkOfferingNetworkRate(networkOfferingId, 7L)).thenReturn(-1); + + ReflectionTestUtils.invokeMethod(testOrchestrator, "saveNetworkRateInDetails", 11L, networkOffering, 7L); + + Mockito.verify(networkDetailsDao).addDetail(11L, ApiConstants.NETWORKRATE, "-1", true); + } + @Test public void testPrepareNicUserVm() { NicProfile profile = prepareMocksAndRunPrepareNic(Type.User, false, false, false); diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/VpcOfferingVO.java b/engine/schema/src/main/java/com/cloud/network/vpc/VpcOfferingVO.java index b913468384e4..0f7bbb7f11fd 100644 --- a/engine/schema/src/main/java/com/cloud/network/vpc/VpcOfferingVO.java +++ b/engine/schema/src/main/java/com/cloud/network/vpc/VpcOfferingVO.java @@ -94,6 +94,9 @@ public class VpcOfferingVO implements VpcOffering { @Column(name = "conserve_mode") private boolean conserveMode; + @Column(name = "public_nw_rate") + private Integer publicNetworkRate; + public VpcOfferingVO() { this.uuid = UUID.randomUUID().toString(); } @@ -254,4 +257,13 @@ public boolean isConserveMode() { public void setConserveMode(boolean conserveMode) { this.conserveMode = conserveMode; } + + @Override + public Integer getPublicNetworkRate() { + return publicNetworkRate; + } + + public void setPublicNetworkRate(Integer publicNetworkRate) { + this.publicNetworkRate = publicNetworkRate; + } } diff --git a/engine/schema/src/main/java/com/cloud/upgrade/NetworkRateBackfill.java b/engine/schema/src/main/java/com/cloud/upgrade/NetworkRateBackfill.java new file mode 100644 index 000000000000..81efb122c5f4 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/upgrade/NetworkRateBackfill.java @@ -0,0 +1,273 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.upgrade; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.framework.config.dao.ConfigurationDaoImpl; +import org.apache.cloudstack.resourcedetail.dao.VpcDetailsDao; +import org.apache.cloudstack.resourcedetail.dao.VpcDetailsDaoImpl; + +import com.cloud.dc.DataCenterDetailVO; +import com.cloud.dc.dao.DataCenterDetailsDaoImpl; +import com.cloud.network.Networks.TrafficType; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkDaoImpl; +import com.cloud.network.dao.NetworkDetailsDao; +import com.cloud.network.dao.NetworkDetailsDaoImpl; +import com.cloud.network.dao.NetworkVO; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.service.dao.ServiceOfferingDaoImpl; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.dao.VMInstanceDao; +import com.cloud.vm.dao.VMInstanceDaoImpl; + +/** + * Backfills {@code nics.network_rate} and the {@code network_details} "networkrate" entry for + * pre-existing NICs/networks, deliberately frozen to the pre-feature precedence of + * {@link com.cloud.network.NetworkModelImpl#getNetworkRate} - do not redirect this to call the + * live method, whose precedence will keep evolving. Also backfills the {@code vpc_details} + * "publicnetworkrate" entry for pre-existing VPCs with a fixed "unlimited" value, since both + * {@code vpc_offerings.public_nw_rate} and the "vpc.public.network.throttling.rate" config are + * introduced by this same release and can't yet hold a pre-existing value. + */ +public class NetworkRateBackfill { + protected static Logger LOGGER = LogManager.getLogger(NetworkRateBackfill.class); + + private static final String CONFIG_NETWORK_THROTTLING_RATE = "network.throttling.rate"; + private static final String CONFIG_VM_NETWORK_THROTTLING_RATE = "vm.network.throttling.rate"; + private static final String NETWORKRATE_DETAIL_NAME = "networkrate"; + private static final String PUBLIC_NETWORK_RATE_DETAIL_NAME = "publicnetworkrate"; + private static final int DEFAULT_THROTTLING_RATE = 200; + private static final int UNLIMITED_RATE = -1; + + private final Connection conn; + + private final VMInstanceDao vmInstanceDao = new VMInstanceDaoImpl(); + private final NetworkDao networkDao = new NetworkDaoImpl(); + private final NetworkDetailsDao networkDetailsDao = new NetworkDetailsDaoImpl(); + private final VpcDetailsDao vpcDetailsDao = new VpcDetailsDaoImpl(); + private final ServiceOfferingDao serviceOfferingDao = new ServiceOfferingDaoImpl(); + private final DataCenterDetailsDaoImpl dataCenterDetailsDao = new DataCenterDetailsDaoImpl(); + private final ConfigurationDao configurationDao = new ConfigurationDaoImpl(); + + public NetworkRateBackfill(Connection conn) { + this.conn = conn; + } + + public void backfillNetworkRates() { + backfillNicNetworkRates(); + backfillNetworkDetailsRates(); + backfillVpcPublicNetworkRates(); + } + + private void backfillNicNetworkRates() { + final String sql = "SELECT id, network_id, instance_id, default_nic FROM nics " + + "WHERE removed IS NULL AND network_rate IS NULL AND instance_id IS NOT NULL"; + try (PreparedStatement pstmt = conn.prepareStatement(sql); + ResultSet rs = pstmt.executeQuery()) { + while (rs.next()) { + final long nicId = rs.getLong("id"); + final long networkId = rs.getLong("network_id"); + final long instanceId = rs.getLong("instance_id"); + final boolean defaultNic = rs.getBoolean("default_nic"); + try { + final Integer rate = computeLegacyNicNetworkRate(networkId, instanceId, defaultNic); + if (rate != null && rate != 0) { + updateNicNetworkRate(nicId, rate); + } + } catch (Exception e) { + LOGGER.warn("Failed to backfill network_rate for nic id=" + nicId + ": " + e.getMessage()); + } + } + } catch (SQLException e) { + LOGGER.warn("Failed to backfill nic network rates: " + e.getMessage()); + } + } + + private void updateNicNetworkRate(long nicId, int rate) throws SQLException { + try (PreparedStatement pstmt = conn.prepareStatement( + "UPDATE nics SET network_rate = ? WHERE id = ?")) { + pstmt.setInt(1, rate); + pstmt.setLong(2, nicId); + pstmt.executeUpdate(); + } + } + + private Integer computeLegacyNicNetworkRate(long networkId, long instanceId, boolean defaultNic) { + final NetworkVO network = networkDao.findById(networkId); + if (network == null) { + return null; + } + final VMInstanceVO vm = vmInstanceDao.findById(instanceId); + if (vm != null) { + if (vm.getType() == VirtualMachine.Type.User) { + if (defaultNic) { + return getServiceOfferingNetworkRate(vm.getServiceOfferingId(), network.getDataCenterId()); + } + } else if (vm.getType() == VirtualMachine.Type.DomainRouter) { + if (TrafficType.Guest.equals(network.getTrafficType())) { + return getNetworkOfferingNetworkRate(network.getNetworkOfferingId(), network.getDataCenterId()); + } else if (TrafficType.Public.equals(network.getTrafficType())) { + // the public side of a VPC was never throttled before this release + if (isVpcRouter(vm.getId())) { + return UNLIMITED_RATE; + } + final Integer rate = findRouterGuestNetworkRate(vm.getId(), network.getDataCenterId()); + if (rate != null) { + return rate; + } + } + } else if (vm.getType() == VirtualMachine.Type.ConsoleProxy || vm.getType() == VirtualMachine.Type.SecondaryStorageVm) { + return UNLIMITED_RATE; + } + } + return getNetworkOfferingNetworkRate(network.getNetworkOfferingId(), network.getDataCenterId()); + } + + private boolean isVpcRouter(long routerInstanceId) { + try (PreparedStatement pstmt = conn.prepareStatement("SELECT 1 FROM domain_router WHERE id = ? AND vpc_id IS NOT NULL")) { + pstmt.setLong(1, routerInstanceId); + try (ResultSet rs = pstmt.executeQuery()) { + return rs.next(); + } + } catch (SQLException e) { + LOGGER.warn("Failed to check whether router id=" + routerInstanceId + " belongs to a VPC: " + e.getMessage()); + return false; + } + } + + // Raw SQL instead of NicDao.listByVmId(): its SearchBuilder isn't safe to use on a DAO built outside Spring here. + private Integer findRouterGuestNetworkRate(long routerInstanceId, long dataCenterId) { + final String sql = "SELECT n.network_offering_id FROM nics ni JOIN networks n ON ni.network_id = n.id " + + "WHERE ni.instance_id = ? AND ni.removed IS NULL AND n.traffic_type = 'Guest' LIMIT 1"; + try (PreparedStatement pstmt = conn.prepareStatement(sql)) { + pstmt.setLong(1, routerInstanceId); + try (ResultSet rs = pstmt.executeQuery()) { + if (rs.next()) { + return getNetworkOfferingNetworkRate(rs.getLong(1), dataCenterId); + } + } + } catch (SQLException e) { + LOGGER.warn("Failed to find router's guest network for instance id=" + routerInstanceId + ": " + e.getMessage()); + } + return null; + } + + private void backfillNetworkDetailsRates() { + final String sql = "SELECT n.id, n.network_offering_id, n.data_center_id FROM networks n " + + "WHERE n.removed IS NULL AND NOT EXISTS " + + "(SELECT 1 FROM network_details d WHERE d.network_id = n.id AND d.name = ?)"; + try (PreparedStatement pstmt = conn.prepareStatement(sql)) { + pstmt.setString(1, NETWORKRATE_DETAIL_NAME); + try (ResultSet rs = pstmt.executeQuery()) { + while (rs.next()) { + final long networkId = rs.getLong("id"); + final long networkOfferingId = rs.getLong("network_offering_id"); + final long dataCenterId = rs.getLong("data_center_id"); + try { + final int rate = getNetworkOfferingNetworkRate(networkOfferingId, dataCenterId); + networkDetailsDao.addDetail(networkId, NETWORKRATE_DETAIL_NAME, String.valueOf(rate), true); + } catch (Exception e) { + LOGGER.warn("Failed to backfill network_details rate for network id=" + networkId + ": " + e.getMessage()); + } + } + } + } catch (SQLException e) { + LOGGER.warn("Failed to backfill network details rates: " + e.getMessage()); + } + } + + // vpc_offerings.public_nw_rate and the "vpc.public.network.throttling.rate" config are both introduced + // by this same release, so no pre-existing VPC offering or config value can be set at backfill + // time - every pre-existing VPC's effective public network rate is unconditionally unlimited. + private void backfillVpcPublicNetworkRates() { + final String sql = "SELECT v.id FROM vpc v " + + "WHERE v.removed IS NULL AND NOT EXISTS " + + "(SELECT 1 FROM vpc_details d WHERE d.vpc_id = v.id AND d.name = ?)"; + try (PreparedStatement pstmt = conn.prepareStatement(sql)) { + pstmt.setString(1, PUBLIC_NETWORK_RATE_DETAIL_NAME); + try (ResultSet rs = pstmt.executeQuery()) { + while (rs.next()) { + final long vpcId = rs.getLong("id"); + try { + vpcDetailsDao.addDetail(vpcId, PUBLIC_NETWORK_RATE_DETAIL_NAME, String.valueOf(UNLIMITED_RATE), true); + } catch (Exception e) { + LOGGER.warn("Failed to backfill vpc_details public network rate for vpc id=" + vpcId + ": " + e.getMessage()); + } + } + } + } catch (SQLException e) { + LOGGER.warn("Failed to backfill vpc public network rates: " + e.getMessage()); + } + } + + private int getServiceOfferingNetworkRate(long serviceOfferingId, long dataCenterId) { + final ServiceOfferingVO offering = serviceOfferingDao.findById(serviceOfferingId); + Integer rate = offering == null ? null : offering.getRateMbps(); + if (rate == null) { + final String vmType = offering == null ? null : offering.getVmType(); + final String configName = "DomainRouter".equalsIgnoreCase(vmType) ? CONFIG_NETWORK_THROTTLING_RATE : CONFIG_VM_NETWORK_THROTTLING_RATE; + rate = getZoneScopedConfigValue(configName, dataCenterId); + } + return normalizeRate(rate); + } + + private int getNetworkOfferingNetworkRate(long networkOfferingId, long dataCenterId) { + Integer rate = getNetworkOfferingRateMbps(networkOfferingId); + if (rate == null) { + rate = getZoneScopedConfigValue(CONFIG_NETWORK_THROTTLING_RATE, dataCenterId); + } + return normalizeRate(rate); + } + + // NetworkOfferingDaoImpl's constructor is protected, so it can't be instantiated here directly. + private Integer getNetworkOfferingRateMbps(long networkOfferingId) { + try (PreparedStatement pstmt = conn.prepareStatement( + "SELECT nw_rate FROM network_offerings WHERE id = ?")) { + pstmt.setLong(1, networkOfferingId); + try (ResultSet rs = pstmt.executeQuery()) { + if (rs.next()) { + final Object nwRate = rs.getObject(1); + return nwRate == null ? null : ((Number) nwRate).intValue(); + } + } + } catch (SQLException e) { + LOGGER.warn("Failed to read nw_rate for network offering id=" + networkOfferingId + ": " + e.getMessage()); + } + return null; + } + + private int normalizeRate(int rate) { + return rate == 0 ? -1 : rate; + } + + private int getZoneScopedConfigValue(String name, long dataCenterId) { + final DataCenterDetailVO detail = dataCenterDetailsDao.findDetail(dataCenterId, name); + final String value = detail != null ? detail.getValue() : configurationDao.getValue(name); + return value != null ? Integer.parseInt(value) : DEFAULT_THROTTLING_RATE; + } +} diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42300to2400.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42300to2400.java index ce217cef9e75..e393d7b8a1c3 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42300to2400.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42300to2400.java @@ -16,6 +16,12 @@ // under the License. package com.cloud.upgrade.dao; +import java.io.InputStream; +import java.sql.Connection; + +import com.cloud.upgrade.NetworkRateBackfill; +import com.cloud.utils.exception.CloudRuntimeException; + public class Upgrade42300to2400 extends DbUpgradeAbstractImpl implements DbUpgrade, DbUpgradeSystemVmTemplate { @Override @@ -27,4 +33,19 @@ public String[] getUpgradableVersionRange() { public String getUpgradedVersion() { return "24.0.0"; } + + @Override + public InputStream[] getPrepareScripts() { + final String scriptFile = "META-INF/db/schema-42300to2400.sql"; + final InputStream script = Thread.currentThread().getContextClassLoader().getResourceAsStream(scriptFile); + if (script == null) { + throw new CloudRuntimeException("Unable to find " + scriptFile); + } + return new InputStream[]{script}; + } + + @Override + public void performDataMigration(Connection conn) { + new NetworkRateBackfill(conn).backfillNetworkRates(); + } } diff --git a/engine/schema/src/main/java/com/cloud/vm/NicVO.java b/engine/schema/src/main/java/com/cloud/vm/NicVO.java index 65946b8d8210..dd405381cb9b 100644 --- a/engine/schema/src/main/java/com/cloud/vm/NicVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/NicVO.java @@ -131,6 +131,9 @@ protected NicVO() { @Column(name = "mtu") Integer mtu; + @Column(name = "network_rate") + Integer networkRate; + @Column(name = "enabled") boolean enabled; @@ -426,4 +429,12 @@ public Integer getMtu() { public void setMtu(Integer mtu) { this.mtu = mtu; } + + public Integer getNetworkRate() { + return networkRate; + } + + public void setNetworkRate(Integer networkRate) { + this.networkRate = networkRate; + } } diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42300to2400.sql b/engine/schema/src/main/resources/META-INF/db/schema-42300to2400.sql index 7c11013a17d2..c111166df78a 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42300to2400.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42300to2400.sql @@ -18,3 +18,7 @@ --; -- Schema upgrade from 4.23.0.0 to 24.0.0 --; + +ALTER TABLE `cloud`.`nics` ADD COLUMN `network_rate` int DEFAULT NULL COMMENT 'effective network rate in Mb/s for this NIC, -1 means unlimited'; + +ALTER TABLE `cloud`.`vpc_offerings` ADD COLUMN `public_nw_rate` int DEFAULT NULL COMMENT 'public gateway (internet-facing) network rate throttle mbits/s'; diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.domain_router_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.domain_router_view.sql index d5f17606cb41..524048b9198f 100644 --- a/engine/schema/src/main/resources/META-INF/db/views/cloud.domain_router_view.sql +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.domain_router_view.sql @@ -77,6 +77,7 @@ select nics.isolation_uri isolation_uri, nics.mtu mtu, nics.enabled is_nic_enabled, + nics.network_rate nic_network_rate, vpc.id vpc_id, vpc.uuid vpc_uuid, vpc.name vpc_name, diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql index fbf126608eae..5f6c31043596 100644 --- a/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql @@ -144,6 +144,7 @@ SELECT `nics`.`broadcast_uri` AS `broadcast_uri`, `nics`.`isolation_uri` AS `isolation_uri`, `nics`.`enabled` AS `is_nic_enabled`, + `nics`.`network_rate` AS `nic_network_rate`, `nic_details`.`value` AS `nic_dns_name`, `vpc`.`id` AS `vpc_id`, `vpc`.`uuid` AS `vpc_uuid`, diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.vpc_offering_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.vpc_offering_view.sql index 3669bb10122b..161410dc377d 100644 --- a/engine/schema/src/main/resources/META-INF/db/views/cloud.vpc_offering_view.sql +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.vpc_offering_view.sql @@ -39,6 +39,7 @@ select `vpc_offerings`.`routing_mode` AS `routing_mode`, `vpc_offerings`.`specify_as_number` AS `specify_as_number`, `vpc_offerings`.`conserve_mode` AS `conserve_mode`, + `vpc_offerings`.`public_nw_rate` AS `public_nw_rate`, group_concat(distinct `domain`.`id` separator ',') AS `domain_id`, group_concat(distinct `domain`.`uuid` separator ',') AS `domain_uuid`, group_concat(distinct `domain`.`name` separator ',') AS `domain_name`, diff --git a/engine/schema/src/test/java/com/cloud/network/vpc/VpcOfferingVOTest.java b/engine/schema/src/test/java/com/cloud/network/vpc/VpcOfferingVOTest.java new file mode 100644 index 000000000000..afcaa9bbd7e3 --- /dev/null +++ b/engine/schema/src/test/java/com/cloud/network/vpc/VpcOfferingVOTest.java @@ -0,0 +1,42 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.network.vpc; + +import org.junit.Assert; +import org.junit.Test; + +public class VpcOfferingVOTest { + + @Test + public void publicNetworkRateIsUnsetByDefault() { + Assert.assertNull(new VpcOfferingVO().getPublicNetworkRate()); + } + + @Test + public void publicNetworkRateCanBeSetAndCleared() { + final VpcOfferingVO offering = new VpcOfferingVO(); + + offering.setPublicNetworkRate(100); + Assert.assertEquals(Integer.valueOf(100), offering.getPublicNetworkRate()); + + offering.setPublicNetworkRate(-1); + Assert.assertEquals(Integer.valueOf(-1), offering.getPublicNetworkRate()); + + offering.setPublicNetworkRate(null); + Assert.assertNull(offering.getPublicNetworkRate()); + } +} diff --git a/engine/schema/src/test/java/com/cloud/upgrade/NetworkRateBackfillTest.java b/engine/schema/src/test/java/com/cloud/upgrade/NetworkRateBackfillTest.java new file mode 100644 index 000000000000..35b6a843a98e --- /dev/null +++ b/engine/schema/src/test/java/com/cloud/upgrade/NetworkRateBackfillTest.java @@ -0,0 +1,529 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.upgrade; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.cloudstack.framework.config.dao.ConfigurationDaoImpl; +import org.apache.cloudstack.resourcedetail.dao.VpcDetailsDaoImpl; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.MockedConstruction; +import org.mockito.Mockito; + +import com.cloud.dc.DataCenterDetailVO; +import com.cloud.dc.dao.DataCenterDetailsDaoImpl; +import com.cloud.network.Networks.TrafficType; +import com.cloud.network.dao.NetworkDaoImpl; +import com.cloud.network.dao.NetworkDetailsDaoImpl; +import com.cloud.network.dao.NetworkVO; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDaoImpl; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.dao.VMInstanceDaoImpl; + +public class NetworkRateBackfillTest { + + private static final String SQL_NICS = "default_nic FROM nics"; + private static final String SQL_UPDATE_NIC = "UPDATE nics"; + private static final String SQL_VPC_ROUTER = "FROM domain_router"; + private static final String SQL_ROUTER_GUEST = "FROM nics ni JOIN"; + private static final String SQL_OFFERING_RATE = "nw_rate FROM network_offerings"; + private static final String SQL_NETWORKS = "FROM networks n"; + private static final String SQL_VPCS = "FROM vpc v"; + + private static final long NIC_ID = 1L; + private static final long NETWORK_ID = 2L; + private static final long VM_ID = 3L; + private static final long OFFERING_ID = 4L; + private static final long ZONE_ID = 5L; + private static final long SERVICE_OFFERING_ID = 6L; + + private Connection conn; + private NetworkRateBackfill backfill; + private final Map statements = new LinkedHashMap<>(); + + private MockedConstruction vmDaoConstruction; + private MockedConstruction networkDaoConstruction; + private MockedConstruction networkDetailsDaoConstruction; + private MockedConstruction vpcDetailsDaoConstruction; + private MockedConstruction serviceOfferingDaoConstruction; + private MockedConstruction zoneDetailsDaoConstruction; + private MockedConstruction configDaoConstruction; + + private VMInstanceDaoImpl vmDao; + private NetworkDaoImpl networkDao; + private NetworkDetailsDaoImpl networkDetailsDao; + private VpcDetailsDaoImpl vpcDetailsDao; + private ServiceOfferingDaoImpl serviceOfferingDao; + private DataCenterDetailsDaoImpl zoneDetailsDao; + private ConfigurationDaoImpl configDao; + + @Before + public void setUp() throws SQLException { + vmDaoConstruction = Mockito.mockConstruction(VMInstanceDaoImpl.class, (m, c) -> vmDao = m); + networkDaoConstruction = Mockito.mockConstruction(NetworkDaoImpl.class, (m, c) -> networkDao = m); + networkDetailsDaoConstruction = Mockito.mockConstruction(NetworkDetailsDaoImpl.class, (m, c) -> networkDetailsDao = m); + vpcDetailsDaoConstruction = Mockito.mockConstruction(VpcDetailsDaoImpl.class, (m, c) -> vpcDetailsDao = m); + serviceOfferingDaoConstruction = Mockito.mockConstruction(ServiceOfferingDaoImpl.class, (m, c) -> serviceOfferingDao = m); + zoneDetailsDaoConstruction = Mockito.mockConstruction(DataCenterDetailsDaoImpl.class, (m, c) -> zoneDetailsDao = m); + configDaoConstruction = Mockito.mockConstruction(ConfigurationDaoImpl.class, (m, c) -> configDao = m); + + conn = Mockito.mock(Connection.class); + Mockito.when(conn.prepareStatement(Mockito.anyString())).thenAnswer(inv -> { + final String sql = inv.getArgument(0); + for (Map.Entry e : statements.entrySet()) { + if (sql.contains(e.getKey())) { + return e.getValue(); + } + } + return statement(rows()); + }); + backfill = new NetworkRateBackfill(conn); + } + + @After + public void tearDown() { + vmDaoConstruction.close(); + networkDaoConstruction.close(); + networkDetailsDaoConstruction.close(); + vpcDetailsDaoConstruction.close(); + serviceOfferingDaoConstruction.close(); + zoneDetailsDaoConstruction.close(); + configDaoConstruction.close(); + } + + @SafeVarargs + private static List> rows(Map... rows) { + return new ArrayList<>(List.of(rows)); + } + + private static Map row(Object... keyValues) { + final Map row = new HashMap<>(); + for (int i = 0; i < keyValues.length; i += 2) { + row.put((String) keyValues[i], keyValues[i + 1]); + } + return row; + } + + private static ResultSet resultSet(List> rows) throws SQLException { + final ResultSet rs = Mockito.mock(ResultSet.class); + final int[] position = {-1}; + Mockito.when(rs.next()).thenAnswer(inv -> ++position[0] < rows.size()); + Mockito.when(rs.getLong(Mockito.anyString())).thenAnswer(inv -> ((Number) rows.get(position[0]).get(inv.getArgument(0))).longValue()); + Mockito.when(rs.getBoolean(Mockito.anyString())).thenAnswer(inv -> (Boolean) rows.get(position[0]).get(inv.getArgument(0))); + Mockito.when(rs.getLong(Mockito.anyInt())).thenAnswer(inv -> ((Number) rows.get(position[0]).get("1")).longValue()); + Mockito.when(rs.getObject(Mockito.anyInt())).thenAnswer(inv -> rows.get(position[0]).get("1")); + return rs; + } + + private static PreparedStatement statement(List> rows) throws SQLException { + final PreparedStatement pstmt = Mockito.mock(PreparedStatement.class); + Mockito.when(pstmt.executeQuery()).thenAnswer(inv -> resultSet(rows)); + return pstmt; + } + + private PreparedStatement given(String sqlFragment, List> rows) throws SQLException { + final PreparedStatement pstmt = statement(rows); + statements.put(sqlFragment, pstmt); + return pstmt; + } + + private void givenFailing(String sqlFragment) throws SQLException { + final PreparedStatement pstmt = Mockito.mock(PreparedStatement.class); + Mockito.when(pstmt.executeQuery()).thenThrow(new SQLException("boom")); + statements.put(sqlFragment, pstmt); + } + + private PreparedStatement givenNic(boolean defaultNic) throws SQLException { + given(SQL_NICS, rows(row("id", NIC_ID, "network_id", NETWORK_ID, "instance_id", VM_ID, "default_nic", defaultNic))); + return given(SQL_UPDATE_NIC, rows()); + } + + private NetworkVO givenNetwork(TrafficType trafficType) { + final NetworkVO network = Mockito.mock(NetworkVO.class); + Mockito.when(network.getTrafficType()).thenReturn(trafficType); + Mockito.when(network.getNetworkOfferingId()).thenReturn(OFFERING_ID); + Mockito.when(network.getDataCenterId()).thenReturn(ZONE_ID); + Mockito.when(networkDao.findById(NETWORK_ID)).thenReturn(network); + return network; + } + + private VMInstanceVO givenVm(VirtualMachine.Type type) { + final VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); + Mockito.when(vm.getType()).thenReturn(type); + Mockito.when(vm.getId()).thenReturn(VM_ID); + Mockito.when(vm.getServiceOfferingId()).thenReturn(SERVICE_OFFERING_ID); + Mockito.when(vmDao.findById(VM_ID)).thenReturn(vm); + return vm; + } + + private void givenNetworkOfferingRate(Integer rate) throws SQLException { + given(SQL_OFFERING_RATE, rate == null ? rows(row("1", null)) : rows(row("1", rate))); + } + + private void givenServiceOffering(Integer rate, String vmType) { + final ServiceOfferingVO offering = Mockito.mock(ServiceOfferingVO.class); + Mockito.when(offering.getRateMbps()).thenReturn(rate); + Mockito.when(offering.getVmType()).thenReturn(vmType); + Mockito.when(serviceOfferingDao.findById(SERVICE_OFFERING_ID)).thenReturn(offering); + } + + private void run() { + backfill.backfillNetworkRates(); + } + + private void verifyNicRate(PreparedStatement update, int rate) throws SQLException { + Mockito.verify(update).setInt(1, rate); + Mockito.verify(update).setLong(2, NIC_ID); + Mockito.verify(update).executeUpdate(); + } + + private void verifyNoNicUpdate(PreparedStatement update) throws SQLException { + Mockito.verify(update, Mockito.never()).executeUpdate(); + } + + @Test + public void userVmDefaultNicUsesComputeOfferingRate() throws SQLException { + final PreparedStatement update = givenNic(true); + givenNetwork(TrafficType.Guest); + givenVm(VirtualMachine.Type.User); + givenServiceOffering(300, "User"); + + run(); + + verifyNicRate(update, 300); + } + + @Test + public void userVmDefaultNicWithoutOfferingRateUsesVmConfigAndNormalizesZero() throws SQLException { + final PreparedStatement update = givenNic(true); + givenNetwork(TrafficType.Guest); + givenVm(VirtualMachine.Type.User); + givenServiceOffering(null, "User"); + Mockito.when(configDao.getValue("vm.network.throttling.rate")).thenReturn("0"); + + run(); + + verifyNicRate(update, -1); + } + + @Test + public void routerOfferingWithoutRateUsesNetworkThrottlingConfig() throws SQLException { + final PreparedStatement update = givenNic(true); + givenNetwork(TrafficType.Guest); + givenVm(VirtualMachine.Type.User); + givenServiceOffering(null, "DomainRouter"); + Mockito.when(configDao.getValue("network.throttling.rate")).thenReturn("120"); + + run(); + + verifyNicRate(update, 120); + } + + @Test + public void zoneDetailOverridesGlobalConfig() throws SQLException { + final PreparedStatement update = givenNic(true); + givenNetwork(TrafficType.Guest); + givenVm(VirtualMachine.Type.User); + givenServiceOffering(null, "User"); + final DataCenterDetailVO detail = Mockito.mock(DataCenterDetailVO.class); + Mockito.when(detail.getValue()).thenReturn("77"); + Mockito.when(zoneDetailsDao.findDetail(ZONE_ID, "vm.network.throttling.rate")).thenReturn(detail); + Mockito.when(configDao.getValue("vm.network.throttling.rate")).thenReturn("500"); + + run(); + + verifyNicRate(update, 77); + } + + @Test + public void missingConfigFallsBackToBuiltInDefault() throws SQLException { + final PreparedStatement update = givenNic(true); + givenNetwork(TrafficType.Guest); + givenVm(VirtualMachine.Type.User); + // no service offering found at all + + run(); + + verifyNicRate(update, 200); + } + + @Test + public void userVmAdditionalNicUsesNetworkOfferingRate() throws SQLException { + final PreparedStatement update = givenNic(false); + givenNetwork(TrafficType.Guest); + givenVm(VirtualMachine.Type.User); + givenNetworkOfferingRate(150); + + run(); + + verifyNicRate(update, 150); + } + + @Test + public void routerGuestNicUsesNetworkOfferingRate() throws SQLException { + final PreparedStatement update = givenNic(true); + givenNetwork(TrafficType.Guest); + givenVm(VirtualMachine.Type.DomainRouter); + givenNetworkOfferingRate(90); + + run(); + + verifyNicRate(update, 90); + } + + @Test + public void vpcRouterPublicNicIsUnlimited() throws SQLException { + final PreparedStatement update = givenNic(false); + givenNetwork(TrafficType.Public); + givenVm(VirtualMachine.Type.DomainRouter); + given(SQL_VPC_ROUTER, rows(row("1", 1))); + givenNetworkOfferingRate(90); + + run(); + + verifyNicRate(update, -1); + } + + @Test + public void nonVpcRouterPublicNicUsesGuestNetworkOfferingRate() throws SQLException { + final PreparedStatement update = givenNic(false); + givenNetwork(TrafficType.Public); + givenVm(VirtualMachine.Type.DomainRouter); + given(SQL_VPC_ROUTER, rows()); + given(SQL_ROUTER_GUEST, rows(row("1", 9L))); + givenNetworkOfferingRate(60); + + run(); + + verifyNicRate(update, 60); + } + + @Test + public void nonVpcRouterPublicNicWithoutGuestNicUsesOwnNetworkOfferingRate() throws SQLException { + final PreparedStatement update = givenNic(false); + givenNetwork(TrafficType.Public); + givenVm(VirtualMachine.Type.DomainRouter); + given(SQL_VPC_ROUTER, rows()); + given(SQL_ROUTER_GUEST, rows()); + givenNetworkOfferingRate(45); + + run(); + + verifyNicRate(update, 45); + } + + @Test + public void vpcCheckFailureTreatsRouterAsNonVpc() throws SQLException { + final PreparedStatement update = givenNic(false); + givenNetwork(TrafficType.Public); + givenVm(VirtualMachine.Type.DomainRouter); + givenFailing(SQL_VPC_ROUTER); + given(SQL_ROUTER_GUEST, rows(row("1", 9L))); + givenNetworkOfferingRate(60); + + run(); + + verifyNicRate(update, 60); + } + + @Test + public void routerGuestLookupFailureFallsBackToOwnNetworkOfferingRate() throws SQLException { + final PreparedStatement update = givenNic(false); + givenNetwork(TrafficType.Public); + givenVm(VirtualMachine.Type.DomainRouter); + given(SQL_VPC_ROUTER, rows()); + givenFailing(SQL_ROUTER_GUEST); + givenNetworkOfferingRate(45); + + run(); + + verifyNicRate(update, 45); + } + + @Test + public void consoleProxyNicIsUnlimited() throws SQLException { + final PreparedStatement update = givenNic(true); + givenNetwork(TrafficType.Management); + givenVm(VirtualMachine.Type.ConsoleProxy); + + run(); + + verifyNicRate(update, -1); + } + + @Test + public void secondaryStorageVmNicIsUnlimited() throws SQLException { + final PreparedStatement update = givenNic(true); + givenNetwork(TrafficType.Management); + givenVm(VirtualMachine.Type.SecondaryStorageVm); + + run(); + + verifyNicRate(update, -1); + } + + @Test + public void nicOfUnknownVmUsesNetworkOfferingRate() throws SQLException { + final PreparedStatement update = givenNic(true); + givenNetwork(TrafficType.Guest); + givenNetworkOfferingRate(25); + + run(); + + verifyNicRate(update, 25); + } + + @Test + public void nicOfOtherVmTypeUsesNetworkOfferingRate() throws SQLException { + final PreparedStatement update = givenNic(true); + givenNetwork(TrafficType.Guest); + givenVm(VirtualMachine.Type.Instance); + givenNetworkOfferingRate(25); + + run(); + + verifyNicRate(update, 25); + } + + @Test + public void nicOfUnknownNetworkIsSkipped() throws SQLException { + final PreparedStatement update = givenNic(true); + + run(); + + verifyNoNicUpdate(update); + } + + @Test + public void failureOnOneNicDoesNotStopTheOthers() throws SQLException { + given(SQL_NICS, rows( + row("id", 10L, "network_id", 20L, "instance_id", VM_ID, "default_nic", true), + row("id", NIC_ID, "network_id", NETWORK_ID, "instance_id", VM_ID, "default_nic", false))); + final PreparedStatement update = given(SQL_UPDATE_NIC, rows()); + Mockito.when(networkDao.findById(20L)).thenThrow(new RuntimeException("boom")); + givenNetwork(TrafficType.Guest); + givenNetworkOfferingRate(150); + + run(); + + verifyNicRate(update, 150); + } + + @Test + public void nicQueryFailureIsSwallowed() throws SQLException { + givenFailing(SQL_NICS); + + run(); + + Mockito.verifyNoInteractions(networkDao); + } + + @Test + public void networkDetailRateIsBackfilledFromOfferingRate() throws SQLException { + given(SQL_NETWORKS, rows(row("id", NETWORK_ID, "network_offering_id", OFFERING_ID, "data_center_id", ZONE_ID))); + givenNetworkOfferingRate(150); + + run(); + + Mockito.verify(networkDetailsDao).addDetail(NETWORK_ID, "networkrate", "150", true); + } + + @Test + public void networkDetailRateFallsBackToZoneConfig() throws SQLException { + given(SQL_NETWORKS, rows(row("id", NETWORK_ID, "network_offering_id", OFFERING_ID, "data_center_id", ZONE_ID))); + givenNetworkOfferingRate(null); + Mockito.when(configDao.getValue("network.throttling.rate")).thenReturn("0"); + + run(); + + Mockito.verify(networkDetailsDao).addDetail(NETWORK_ID, "networkrate", "-1", true); + } + + @Test + public void networkDetailFailureIsSwallowed() throws SQLException { + given(SQL_NETWORKS, rows( + row("id", 7L, "network_offering_id", OFFERING_ID, "data_center_id", ZONE_ID), + row("id", NETWORK_ID, "network_offering_id", OFFERING_ID, "data_center_id", ZONE_ID))); + givenNetworkOfferingRate(150); + Mockito.doThrow(new RuntimeException("boom")).when(networkDetailsDao).addDetail(7L, "networkrate", "150", true); + + run(); + + Mockito.verify(networkDetailsDao).addDetail(NETWORK_ID, "networkrate", "150", true); + } + + @Test + public void networkDetailQueryFailureIsSwallowed() throws SQLException { + givenFailing(SQL_NETWORKS); + + run(); + + Mockito.verifyNoInteractions(networkDetailsDao); + } + + @Test + public void offeringRateQueryFailureFallsBackToZoneConfig() throws SQLException { + given(SQL_NETWORKS, rows(row("id", NETWORK_ID, "network_offering_id", OFFERING_ID, "data_center_id", ZONE_ID))); + givenFailing(SQL_OFFERING_RATE); + Mockito.when(configDao.getValue("network.throttling.rate")).thenReturn("33"); + + run(); + + Mockito.verify(networkDetailsDao).addDetail(NETWORK_ID, "networkrate", "33", true); + } + + @Test + public void vpcPublicRateIsBackfilledAsUnlimited() throws SQLException { + given(SQL_VPCS, rows(row("id", 11L), row("id", 12L))); + + run(); + + Mockito.verify(vpcDetailsDao).addDetail(11L, "publicnetworkrate", "-1", true); + Mockito.verify(vpcDetailsDao).addDetail(12L, "publicnetworkrate", "-1", true); + } + + @Test + public void vpcDetailFailureIsSwallowed() throws SQLException { + given(SQL_VPCS, rows(row("id", 11L), row("id", 12L))); + Mockito.doThrow(new RuntimeException("boom")).when(vpcDetailsDao).addDetail(11L, "publicnetworkrate", "-1", true); + + run(); + + Mockito.verify(vpcDetailsDao).addDetail(12L, "publicnetworkrate", "-1", true); + } + + @Test + public void vpcQueryFailureIsSwallowed() throws SQLException { + givenFailing(SQL_VPCS); + + run(); + + Mockito.verifyNoInteractions(vpcDetailsDao); + } +} diff --git a/engine/schema/src/test/java/com/cloud/upgrade/dao/Upgrade42300to2400Test.java b/engine/schema/src/test/java/com/cloud/upgrade/dao/Upgrade42300to2400Test.java new file mode 100644 index 000000000000..595edd8cd6ff --- /dev/null +++ b/engine/schema/src/test/java/com/cloud/upgrade/dao/Upgrade42300to2400Test.java @@ -0,0 +1,78 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.upgrade.dao; + +import java.io.IOException; +import java.io.InputStream; +import java.sql.Connection; +import java.util.ArrayList; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.mockito.MockedConstruction; +import org.mockito.Mockito; + +import com.cloud.upgrade.NetworkRateBackfill; +import com.cloud.utils.exception.CloudRuntimeException; + +public class Upgrade42300to2400Test { + + private final Upgrade42300to2400 upgrade = new Upgrade42300to2400(); + + @Test + public void upgradesFrom4230To2400() { + Assert.assertArrayEquals(new String[]{"4.23.0.0", "24.0.0"}, upgrade.getUpgradableVersionRange()); + Assert.assertEquals("24.0.0", upgrade.getUpgradedVersion()); + } + + @Test + public void prepareScriptIsFound() throws IOException { + final InputStream[] scripts = upgrade.getPrepareScripts(); + + Assert.assertEquals(1, scripts.length); + Assert.assertNotNull(scripts[0]); + scripts[0].close(); + } + + @Test(expected = CloudRuntimeException.class) + public void prepareScriptMissingFails() { + final ClassLoader original = Thread.currentThread().getContextClassLoader(); + // a loader that has no parent can't see the script + Thread.currentThread().setContextClassLoader(new ClassLoader(null) { }); + try { + upgrade.getPrepareScripts(); + } finally { + Thread.currentThread().setContextClassLoader(original); + } + } + + @Test + public void dataMigrationBackfillsNetworkRatesOnTheUpgradeConnection() { + final Connection conn = Mockito.mock(Connection.class); + + final List constructorArgs = new ArrayList<>(); + try (MockedConstruction backfills = Mockito.mockConstruction(NetworkRateBackfill.class, + (mock, context) -> constructorArgs.addAll(context.arguments()))) { + upgrade.performDataMigration(conn); + + Assert.assertEquals(1, backfills.constructed().size()); + Assert.assertEquals(List.of(conn), constructorArgs); + Mockito.verify(backfills.constructed().get(0)).backfillNetworkRates(); + } + } +} diff --git a/engine/schema/src/test/java/com/cloud/vm/NicVOTest.java b/engine/schema/src/test/java/com/cloud/vm/NicVOTest.java new file mode 100644 index 000000000000..79b3f3368ffe --- /dev/null +++ b/engine/schema/src/test/java/com/cloud/vm/NicVOTest.java @@ -0,0 +1,46 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.vm; + +import org.junit.Assert; +import org.junit.Test; + +public class NicVOTest { + + private NicVO nic() { + return new NicVO("test", 1L, 2L, VirtualMachine.Type.User); + } + + @Test + public void networkRateIsUnsetByDefault() { + Assert.assertNull(nic().getNetworkRate()); + } + + @Test + public void networkRateCanBeSetAndCleared() { + final NicVO nic = nic(); + + nic.setNetworkRate(200); + Assert.assertEquals(Integer.valueOf(200), nic.getNetworkRate()); + + nic.setNetworkRate(-1); + Assert.assertEquals(Integer.valueOf(-1), nic.getNetworkRate()); + + nic.setNetworkRate(null); + Assert.assertNull(nic.getNetworkRate()); + } +} diff --git a/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/management/ContrailManagerImpl.java b/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/management/ContrailManagerImpl.java index 8badb916eeda..11917a8aa50c 100644 --- a/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/management/ContrailManagerImpl.java +++ b/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/management/ContrailManagerImpl.java @@ -293,7 +293,7 @@ private VpcOffering locateVpcOffering() { } serviceProviderMap.put(svc, providerSet); } - vpcOffer = _vpcProvSvc.createVpcOffering(juniperVPCOfferingName, juniperVPCOfferingDisplayText, services, serviceProviderMap, null, null, null, null, null, null, null, VpcOffering.State.Enabled, null, false, false); + vpcOffer = _vpcProvSvc.createVpcOffering(juniperVPCOfferingName, juniperVPCOfferingDisplayText, services, serviceProviderMap, null, null, null, null, null, null, null, VpcOffering.State.Enabled, null, false, false, null); long id = vpcOffer.getId(); _vpcOffDao.update(id, (VpcOfferingVO)vpcOffer); return _vpcOffDao.findById(id); diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index f56cda6e557a..98c54007260e 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -225,6 +225,8 @@ import org.apache.cloudstack.region.PortableIp; import org.apache.cloudstack.region.PortableIpRange; import org.apache.cloudstack.region.Region; +import org.apache.cloudstack.resourcedetail.VpcDetailVO; +import org.apache.cloudstack.resourcedetail.dao.VpcDetailsDao; import org.apache.cloudstack.secstorage.heuristics.Heuristic; import org.apache.cloudstack.storage.datastore.db.ObjectStoreDao; import org.apache.cloudstack.storage.datastore.db.ObjectStoreVO; @@ -243,6 +245,7 @@ import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -500,6 +503,8 @@ public class ApiResponseHelper implements ResponseGenerator, ResourceIdSupport { @Inject NetworkDetailsDao networkDetailsDao; @Inject + VpcDetailsDao vpcDetailsDao; + @Inject private VMSnapshotDao vmSnapshotDao; @Inject private BackupOfferingDao backupOfferingDao; @@ -2652,14 +2657,20 @@ public NetworkResponse createNetworkResponse(ResponseView view, Network network) response.setVlan(vlan); } - // return network details only to Root admin + int networkRate; + // return network details only to Root admin, and take the network rate from them to save a lookup if (view == ResponseView.Full) { Map details = new HashMap<>(); for (NetworkDetailVO detail: networkDetailsDao.listDetails(network.getId())) { details.put(detail.getName(),detail.getValue()); } response.setDetails(details); + networkRate = NumberUtils.toInt(details.get(ApiConstants.NETWORKRATE), -1); + } else { + NetworkDetailVO networkRateDetail = networkDetailsDao.findDetail(network.getId(), ApiConstants.NETWORKRATE); + networkRate = networkRateDetail != null ? NumberUtils.toInt(networkRateDetail.getValue(), -1) : -1; } + response.setNetworkRate(networkRate > 0 ? networkRate : -1); Pair dnsZoneAndSubDomain = ApiDBUtils.findDnsZoneByNetworkId(network.getId()); if (StringUtils.isNotBlank(dnsZoneAndSubDomain.first())) { @@ -3591,6 +3602,9 @@ public VpcResponse createVpcResponse(ResponseView view, Vpc vpc) { response.setVpcOfferingName(voff.getName()); response.setVpcOfferingConserveMode(voff.isConserveMode()); } + VpcDetailVO publicNetworkRateDetail = vpcDetailsDao.findDetail(vpc.getId(), ApiConstants.PUBLIC_NETWORK_RATE); + Integer publicNetworkRate = publicNetworkRateDetail != null ? NumberUtils.toInt(publicNetworkRateDetail.getValue(), -1) : null; + response.setPublicNetworkRate((publicNetworkRate == null || publicNetworkRate <= 0) ? -1 : publicNetworkRate); response.setCidr(vpc.getCidr()); response.setRestartRequired(vpc.isRestartRequired()); response.setNetworkDomain(vpc.getNetworkDomain()); @@ -4905,6 +4919,10 @@ public NicResponse createNicResponse(Nic result) { } response.setEnabled(result.isEnabled()); + + Integer nicNetworkRate = result.getNetworkRate(); + response.setNetworkRate(nicNetworkRate != null && nicNetworkRate > 0 ? nicNetworkRate : -1); + return response; } diff --git a/server/src/main/java/com/cloud/api/query/dao/DomainRouterJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/DomainRouterJoinDaoImpl.java index 9bc409d455e7..d4ae4f1db240 100644 --- a/server/src/main/java/com/cloud/api/query/dao/DomainRouterJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/DomainRouterJoinDaoImpl.java @@ -198,6 +198,8 @@ public DomainRouterResponse newDomainRouterResponse(DomainRouterJoinVO router, A nicResponse.setIsDefault(router.isDefaultNic()); nicResponse.setEnabled(router.isNicEnabled()); nicResponse.setObjectName("nic"); + Integer routerNicNetworkRate = router.getNicNetworkRate(); + nicResponse.setNetworkRate(routerNicNetworkRate != null && routerNicNetworkRate > 0 ? routerNicNetworkRate : -1); routerResponse.addNic(nicResponse); } } @@ -292,6 +294,8 @@ public DomainRouterResponse setDomainRouterResponse(DomainRouterResponse vrData, nicResponse.setIsDefault(vr.isDefaultNic()); nicResponse.setEnabled(vr.isNicEnabled()); nicResponse.setObjectName("nic"); + Integer vrNicNetworkRate = vr.getNicNetworkRate(); + nicResponse.setNetworkRate(vrNicNetworkRate != null && vrNicNetworkRate > 0 ? vrNicNetworkRate : -1); vrData.addNic(nicResponse); } return vrData; diff --git a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java index ebc2326a72d5..67ab53b61132 100644 --- a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java @@ -447,6 +447,8 @@ public UserVmResponse newUserVmResponse(ResponseView view, String objectName, Us .collect(Collectors.toList()); nicResponse.setExtraDhcpOptions(nicExtraDhcpOptionResponses); + Integer nicNetworkRate = userVm.getNicNetworkRate(); + nicResponse.setNetworkRate(nicNetworkRate != null && nicNetworkRate > 0 ? nicNetworkRate : -1); userVmResponse.addNic(nicResponse); } } @@ -744,6 +746,9 @@ public UserVmResponse setUserVmResponse(ResponseView view, UserVmResponse userVm .map(vo -> new NicExtraDhcpOptionResponse(Dhcp.DhcpOptionCode.valueOfInt(vo.getCode()).getName(), vo.getCode(), vo.getValue())) .collect(Collectors.toList()); nicResponse.setExtraDhcpOptions(nicExtraDhcpOptionResponses); + + Integer nicNetworkRate = uvo.getNicNetworkRate(); + nicResponse.setNetworkRate(nicNetworkRate != null && nicNetworkRate > 0 ? nicNetworkRate : -1); userVmData.addNic(nicResponse); } diff --git a/server/src/main/java/com/cloud/api/query/dao/VpcOfferingJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/VpcOfferingJoinDaoImpl.java index e7fe07a18c78..43a8562f714d 100644 --- a/server/src/main/java/com/cloud/api/query/dao/VpcOfferingJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/VpcOfferingJoinDaoImpl.java @@ -78,6 +78,8 @@ public VpcOfferingResponse newVpcOfferingResponse(VpcOffering offering) { offeringResponse.setSpecifyAsNumber(offering.isSpecifyAsNumber()); } offeringResponse.setConserveMode(offering.isConserveMode()); + Integer pubNetworkRate = offering.getPublicNetworkRate(); + offeringResponse.setPublicNetworkRate(pubNetworkRate); if (offering instanceof VpcOfferingJoinVO) { VpcOfferingJoinVO offeringJoinVO = (VpcOfferingJoinVO) offering; offeringResponse.setDomainId(offeringJoinVO.getDomainUuid()); diff --git a/server/src/main/java/com/cloud/api/query/vo/DomainRouterJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/DomainRouterJoinVO.java index db7f75b6f2bb..7cdeb370a2a9 100644 --- a/server/src/main/java/com/cloud/api/query/vo/DomainRouterJoinVO.java +++ b/server/src/main/java/com/cloud/api/query/vo/DomainRouterJoinVO.java @@ -210,6 +210,9 @@ public class DomainRouterJoinVO extends BaseViewVO implements ControlledViewEnti @Column(name = "isolation_uri") private URI isolationUri; + @Column(name = "nic_network_rate") + private Integer nicNetworkRate; + @Column(name = "network_id") private long networkId; @@ -450,6 +453,10 @@ public URI getIsolationUri() { return isolationUri; } + public Integer getNicNetworkRate() { + return nicNetworkRate; + } + public long getNetworkId() { return networkId; } diff --git a/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java index 6f84bd9b1e3e..5e63e4383e09 100644 --- a/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java +++ b/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java @@ -355,6 +355,9 @@ public class UserVmJoinVO extends BaseViewWithTagInformationVO implements Contro @Column(name = "is_nic_enabled") private boolean isNicEnabled; + @Column(name = "nic_network_rate") + private Integer nicNetworkRate; + @Column(name = "ip_address") private String ipAddress; @@ -1115,6 +1118,10 @@ public boolean isNicEnabled() { return isNicEnabled; } + public Integer getNicNetworkRate() { + return nicNetworkRate; + } + public String getNicDnsName() { return nicDnsName; } diff --git a/server/src/main/java/com/cloud/api/query/vo/VpcOfferingJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/VpcOfferingJoinVO.java index 9d65c19479fb..144fa6e0f490 100644 --- a/server/src/main/java/com/cloud/api/query/vo/VpcOfferingJoinVO.java +++ b/server/src/main/java/com/cloud/api/query/vo/VpcOfferingJoinVO.java @@ -115,6 +115,9 @@ public class VpcOfferingJoinVO implements VpcOffering { @Column(name = "conserve_mode") private boolean conserveMode; + @Column(name = "public_nw_rate") + private Integer publicNetworkRate; + public VpcOfferingJoinVO() { } @@ -186,6 +189,11 @@ public boolean isConserveMode() { return conserveMode; } + @Override + public Integer getPublicNetworkRate() { + return publicNetworkRate; + } + public void setSpecifyAsNumber(Boolean specifyAsNumber) { this.specifyAsNumber = specifyAsNumber; } diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index c68dc390df2a..e24f7f3c7c12 100644 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -261,6 +261,7 @@ import com.cloud.network.netris.NetrisService; import com.cloud.network.rules.LoadBalancerContainer.Scheme; import com.cloud.network.vpc.VpcManager; +import com.cloud.network.vpc.VpcOffering; import com.cloud.offering.DiskOffering; import com.cloud.offering.NetworkOffering; import com.cloud.offering.NetworkOffering.Availability; @@ -1528,6 +1529,11 @@ protected String validateValueRange(String name, String value, Class type, Co return String.format("[%s] value should be between 0 and 255. 0 value will disable this feature.", name); } } + if (NetworkOrchestrationService.VpcPublicNetworkThrottlingRate.key().equalsIgnoreCase(name)) { + if (val < -1) { + return String.format("Please enter -1 (unlimited) or a positive value for the configuration parameter: [%s].", name); + } + } if (UnmanagedVMsManager.ThreadsOnMSToImportVMwareVMFiles.key().equalsIgnoreCase(name) || UnmanagedVMsManager.ThreadsOnKVMHostToImportVMwareVMFiles.key().equalsIgnoreCase(name)) { if (val < -1 || val > 10) { @@ -9064,6 +9070,30 @@ public Integer getNetworkOfferingNetworkRate(final long networkOfferingId, final return networkRate; } + @Override + public Integer getVpcOfferingNetworkRate(final long vpcOfferingId, final Long dataCenterId) { + + final VpcOffering vpcOffering = _entityMgr.findById(VpcOffering.class, vpcOfferingId); + if (vpcOffering == null) { + throw new InvalidParameterValueException("Unable to find VPC offering by id=" + vpcOfferingId); + } + + Integer networkRate; + if (vpcOffering.getPublicNetworkRate() != null) { + networkRate = vpcOffering.getPublicNetworkRate(); + } else { + networkRate = NetworkOrchestrationService.VpcPublicNetworkThrottlingRate.valueIn(dataCenterId); + } + + // 0 also means unlimited; we convert it to -1, so we are consistent with + // all our other resources where -1 means unlimited + if (networkRate == 0) { + networkRate = -1; + } + + return networkRate; + } + @Override public Account getVlanAccount(final long vlanId) { final Vlan vlan = _vlanDao.findById(vlanId); diff --git a/server/src/main/java/com/cloud/network/NetworkMigrationManager.java b/server/src/main/java/com/cloud/network/NetworkMigrationManager.java index 98bf849cc681..71d39c15d19d 100644 --- a/server/src/main/java/com/cloud/network/NetworkMigrationManager.java +++ b/server/src/main/java/com/cloud/network/NetworkMigrationManager.java @@ -67,6 +67,14 @@ public interface NetworkMigrationManager { */ void deleteCopyOfNetwork(long networkCopyId, long originalNetworkId); + /** + * Copies the details of the source vpc to the destination vpc, once the migration of the vpc to the new vpc offering is done. + * Details derived from the vpc offering (like the public network rate) are not copied, the destination vpc keeps its own. + * @param srcVpcId the vpc id of the vpc to copy the details from + * @param dstVpcId the vpc id of the vpc to copy the details to + */ + void copyVpcDetails(long srcVpcId, long dstVpcId); + /** * Deletes the copy of a vpc which was previously created by the networkMigrationManager * For deletion the copy of the old UUID of the original vpc is used to assure that plugins, using the UUID, clean up the vpc correctly. diff --git a/server/src/main/java/com/cloud/network/NetworkMigrationManagerImpl.java b/server/src/main/java/com/cloud/network/NetworkMigrationManagerImpl.java index a09867b8ffc3..50c022748d57 100644 --- a/server/src/main/java/com/cloud/network/NetworkMigrationManagerImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkMigrationManagerImpl.java @@ -24,6 +24,7 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; +import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.cloud.entity.api.db.VMNetworkMapVO; import org.apache.cloudstack.engine.cloud.entity.api.db.dao.VMNetworkMapDao; @@ -246,10 +247,14 @@ public class NetworkMigrationManagerImpl implements NetworkMigrationManager { } @DB - private void copyNetworkDetails(long srcNetworkId, long dstNetworkId) { + protected void copyNetworkDetails(long srcNetworkId, long dstNetworkId) { List networkDetails = _networkDetailsDao.listDetails(srcNetworkId); for (NetworkDetailVO networkDetail : networkDetails) { + // the network rate of the copy is already set from its offering, and it is updated when the migrated network moves to the new offering + if (ApiConstants.NETWORKRATE.equals(networkDetail.getName())) { + continue; + } _networkDetailsDao.persist(new NetworkDetailVO(dstNetworkId, networkDetail.getName(), networkDetail.getValue(), networkDetail.isDisplay())); } } @@ -311,7 +316,6 @@ public Long makeCopyOfVpc(long vpcId, long vpcOfferingId) { swapUuids(vpc, copyVpcVO); reassignACLRulesToNewVpc(vpcId, copyOfVpcId); reassignPublicIpsToNewVpc(vpcId, copyOfVpc); - copyVpcDetails(vpcId, copyOfVpcId); reassignGatewayToNewVpc(vpcId, copyOfVpcId); copyVpcResourceTagsToNewVpc(vpcId, copyOfVpcId); if (logger.isDebugEnabled()) { @@ -337,11 +341,16 @@ public void startVpc(Vpc vpc) { } - private void copyVpcDetails(long srcVpcId, long dstVpcId) { + @Override + public void copyVpcDetails(long srcVpcId, long dstVpcId) { List vpcDetails = _vpcDetailsDao.listDetails(srcVpcId); for (VpcDetailVO vpcDetail : vpcDetails) { - _vpcDetailsDao.persist(new VpcDetailVO(dstVpcId, vpcDetail.getName(), vpcDetail.getValue(), vpcDetail.isDisplay())); + // The public network rate is derived from the vpc offering, and was already set on the destination vpc from its (new) offering + if (ApiConstants.PUBLIC_NETWORK_RATE.equals(vpcDetail.getName())) { + continue; + } + _vpcDetailsDao.addDetail(dstVpcId, vpcDetail.getName(), vpcDetail.getValue(), vpcDetail.isDisplay()); } } @@ -484,6 +493,8 @@ public Network upgradeNetworkToNewNetworkOffering(long networkId, long newPhysic network.setVpcId(vpcId); } _networksDao.update(network.getId(), network, _networkMgr.finalizeServicesAndProvidersForNetwork(_entityMgr.findById(NetworkOffering.class, networkOfferingId), newPhysicalNetworkId)); + Integer networkRate = _networkModel.getNetworkRate(network.getId(), null); + _networkDetailsDao.addDetail(network.getId(), ApiConstants.NETWORKRATE, String.valueOf(networkRate), true); return network; } diff --git a/server/src/main/java/com/cloud/network/NetworkModelImpl.java b/server/src/main/java/com/cloud/network/NetworkModelImpl.java index f47046cdc434..5409932cbe6a 100644 --- a/server/src/main/java/com/cloud/network/NetworkModelImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkModelImpl.java @@ -123,6 +123,8 @@ import com.cloud.projects.ProjectAccount; import com.cloud.projects.dao.ProjectAccountDao; import com.cloud.projects.dao.ProjectDao; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.user.Account; import com.cloud.user.AccountManager; import com.cloud.user.AccountVO; @@ -141,6 +143,7 @@ import com.cloud.utils.db.SearchCriteria.Op; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.NetUtils; +import com.cloud.vm.DomainRouterVO; import com.cloud.vm.Nic; import com.cloud.vm.NicProfile; import com.cloud.vm.NicVO; @@ -148,6 +151,7 @@ import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine.Type; import com.cloud.vm.VirtualMachineManager; +import com.cloud.vm.dao.DomainRouterDao; import com.cloud.vm.dao.NicDao; import com.cloud.vm.dao.NicSecondaryIpDao; import com.cloud.vm.dao.VMInstanceDao; @@ -173,6 +177,8 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi @Inject ConfigurationManager _configMgr; @Inject + ServiceOfferingDao _serviceOfferingDao; + @Inject NetworkOfferingDao _networkOfferingDao = null; @Inject NetworkDao _networksDao = null; @@ -190,6 +196,8 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi VpcDao vpcDao; @Inject VpcOfferingServiceMapDao _vpcOffSvcMapDao; + @Inject + DomainRouterDao _routerDao; private List networkElements; @@ -1202,9 +1210,11 @@ public Integer getNetworkRate(long networkId, Long vmId) { final Network network = getNetwork(networkId); final NetworkOffering ntwkOff = _entityMgr.findById(NetworkOffering.class, network.getNetworkOfferingId()); - // For user VM: For default nic use network rate from the service/compute offering, + // For user VM: Use network rate from the service/compute offering for every nic (default or not), // or on NULL from vm.network.throttling.rate global setting - // For router: Get network rate for guest and public networks from the guest network offering + // For router: For guest networks, use network rate from the router's own system offering first, + // falling back to the guest network offering, or on NULL from network.throttling.rate + // For public networks, use network rate from the router's guest network offering, // or on NULL from network.throttling.rate // For others: Use network rate from their network offering, // or on NULL from network.throttling.rate setting at zone > global level @@ -1213,7 +1223,7 @@ public Integer getNetworkRate(long networkId, Long vmId) { switch (vm.getType()) { case User: final Nic nic = _nicDao.findByNtwkIdAndInstanceId(networkId, vmId); - if (nic != null && nic.isDefaultNic()) { + if (nic != null) { return _configMgr.getServiceOfferingNetworkRate(vm.getServiceOfferingId(), network.getDataCenterId()); } break; @@ -1221,9 +1231,23 @@ public Integer getNetworkRate(long networkId, Long vmId) { if (TrafficType.Guest.equals(network.getTrafficType())) { final Nic routerNic = _nicDao.findByNtwkIdAndInstanceId(networkId, vmId); if (routerNic != null) { + final ServiceOfferingVO routerOffering = _serviceOfferingDao.findById(vm.getServiceOfferingId()); + if (routerOffering != null && routerOffering.getRateMbps() != null) { + final int systemOfferingRate = routerOffering.getRateMbps(); + return systemOfferingRate > 0 ? systemOfferingRate : -1; + } return _configMgr.getNetworkOfferingNetworkRate(network.getNetworkOfferingId(), network.getDataCenterId()); } } else if (TrafficType.Public.equals(network.getTrafficType())) { + // Use the router's own vpc_id: the guest NIC isn't persisted yet when this runs for the public NIC during initial VR deployment. + final DomainRouterVO routerVO = _routerDao.findById(vmId); + final Long vpcId = routerVO != null ? routerVO.getVpcId() : null; + if (vpcId != null) { + final Vpc vpc = vpcDao.findById(vpcId); + if (vpc != null) { + return _configMgr.getVpcOfferingNetworkRate(vpc.getVpcOfferingId(), network.getDataCenterId()); + } + } List routerNics = _nicDao.listByVmId(vmId); for (final Nic routerNic : routerNics) { final NetworkVO nw = _networksDao.findById(routerNic.getNetworkId()); diff --git a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java index 2853fa96330d..7456e6462d53 100644 --- a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java @@ -3604,6 +3604,8 @@ public void doInTransactionWithoutResult(TransactionStatus status) { UsageEventUtils.publishUsageEvent(EventTypes.EVENT_NETWORK_OFFERING_ASSIGN, vm.getAccountId(), vm.getDataCenterId(), vm.getId(), nicIdString, networkOfferingId, null, isDefault, VirtualMachine.class.getName(), vm.getUuid(), vm.isDisplay()); } + Integer rate = _configMgr.getNetworkOfferingNetworkRate(networkOfferingId, network.getDataCenterId()); + _networkDetailsDao.addDetail(networkId, ApiConstants.NETWORKRATE, String.valueOf(rate), true); } }); } else { @@ -4002,6 +4004,7 @@ public Vpc migrateVpcNetwork(long vpcId, long vpcOfferingId, Map migrateNetworkToPhysicalNetwork(tier, oldNtwkOff, newNtwkOff, vpcId, vpcCopyId, newPhysicalNetworkId, account, callerUser); } } + _networkMigrationManager.copyVpcDetails(vpcId, vpcCopyId); _networkMigrationManager.deleteCopyOfVpc(vpcId, vpcCopyId); return _vpcDao.findById(vpcCopyId); } 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 bcf2c6176efe..fddbb47a06bd 100644 --- a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java +++ b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java @@ -74,6 +74,7 @@ import org.apache.cloudstack.network.RoutedIpv4Manager; import org.apache.cloudstack.query.QueryService; import org.apache.cloudstack.reservation.dao.ReservationDao; +import org.apache.cloudstack.resourcedetail.dao.VpcDetailsDao; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.EnumUtils; import org.apache.commons.lang3.ObjectUtils; @@ -279,6 +280,10 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis @Inject DataCenterDao _dcDao; @Inject + ConfigurationManager _configMgr; + @Inject + VpcDetailsDao vpcDetailsDao; + @Inject NetworkACLDao _networkAclDao; @Inject NetworkACLManager _networkAclMgr; @@ -395,7 +400,7 @@ public void doInTransactionWithoutResult(final TransactionStatus status) { } createVpcOffering(VpcOffering.defaultVPCOfferingName, VpcOffering.defaultVPCOfferingName, svcProviderMap, true, State.Enabled, null, false, - false, false, null, null, false, false); + false, false, null, null, false, false, null); } // configure default vpc offering with Netscaler as LB Provider @@ -415,7 +420,7 @@ public void doInTransactionWithoutResult(final TransactionStatus status) { } } createVpcOffering(VpcOffering.defaultVPCNSOfferingName, VpcOffering.defaultVPCNSOfferingName, - svcProviderMap, false, State.Enabled, null, false, false, false, null, null, false, false); + svcProviderMap, false, State.Enabled, null, false, false, false, null, null, false, false, null); } @@ -436,7 +441,7 @@ public void doInTransactionWithoutResult(final TransactionStatus status) { } } createVpcOffering(VpcOffering.redundantVPCOfferingName, VpcOffering.redundantVPCOfferingName, svcProviderMap, true, State.Enabled, - null, false, false, true, null, null, false, false); + null, false, false, true, null, null, false, false, null); } // configure default vpc offering with NSX as network service provider in NAT mode @@ -453,7 +458,7 @@ public void doInTransactionWithoutResult(final TransactionStatus status) { } } createVpcOffering(VpcOffering.DEFAULT_VPC_NAT_NSX_OFFERING_NAME, VpcOffering.DEFAULT_VPC_NAT_NSX_OFFERING_NAME, svcProviderMap, false, - State.Enabled, null, false, false, false, NetworkOffering.NetworkMode.NATTED, null, false, false); + State.Enabled, null, false, false, false, NetworkOffering.NetworkMode.NATTED, null, false, false, null); } @@ -471,7 +476,7 @@ public void doInTransactionWithoutResult(final TransactionStatus status) { } } createVpcOffering(VpcOffering.DEFAULT_VPC_ROUTE_NSX_OFFERING_NAME, VpcOffering.DEFAULT_VPC_ROUTE_NSX_OFFERING_NAME, svcProviderMap, false, - State.Enabled, null, false, false, false, NetworkOffering.NetworkMode.ROUTED, null, false, false); + State.Enabled, null, false, false, false, NetworkOffering.NetworkMode.ROUTED, null, false, false, null); } @@ -489,7 +494,7 @@ public void doInTransactionWithoutResult(final TransactionStatus status) { } } createVpcOffering(VpcOffering.DEFAULT_VPC_ROUTE_NETRIS_OFFERING_NAME, VpcOffering.DEFAULT_VPC_ROUTE_NETRIS_OFFERING_NAME, svcProviderMap, false, - State.Enabled, null, false, false, false, NetworkOffering.NetworkMode.ROUTED, null, false, false); + State.Enabled, null, false, false, false, NetworkOffering.NetworkMode.ROUTED, null, false, false, null); } @@ -507,7 +512,7 @@ public void doInTransactionWithoutResult(final TransactionStatus status) { } } createVpcOffering(VpcOffering.DEFAULT_VPC_NAT_NETRIS_OFFERING_NAME, VpcOffering.DEFAULT_VPC_NAT_NETRIS_OFFERING_NAME, svcProviderMap, false, - State.Enabled, null, false, false, false, NetworkOffering.NetworkMode.NATTED, null, false, false); + State.Enabled, null, false, false, false, NetworkOffering.NetworkMode.NATTED, null, false, false, null); } } @@ -594,6 +599,16 @@ public VpcOffering createVpcOffering(CreateVPCOfferingCmd cmd) { boolean specifyAsNumber = cmd.getSpecifyAsNumber(); String routingModeString = cmd.getRoutingMode(); boolean conserveMode = cmd.isConserveMode(); + Integer publicNetworkRate = cmd.getPublicNetworkRate(); + if (publicNetworkRate != null) { + if (publicNetworkRate < -1) { + throw new InvalidParameterValueException("Failed to create VPC offering " + vpcOfferingName + ": specify the public network rate value as -1 (unlimited) or a positive value"); + } + // 0 and -1 both mean unlimited + if (publicNetworkRate == 0) { + publicNetworkRate = -1; + } + } // check if valid domain if (CollectionUtils.isNotEmpty(cmd.getDomainIds())) { @@ -632,7 +647,7 @@ public VpcOffering createVpcOffering(CreateVPCOfferingCmd cmd) { return createVpcOffering(vpcOfferingName, displayText, supportedServices, serviceProviderList, serviceCapabilityList, internetProtocol, serviceOfferingId, provider, networkMode, - domainIds, zoneIds, (enable ? State.Enabled : State.Disabled), routingMode, specifyAsNumber, conserveMode); + domainIds, zoneIds, (enable ? State.Enabled : State.Disabled), routingMode, specifyAsNumber, conserveMode, publicNetworkRate); } @Override @@ -640,7 +655,7 @@ public VpcOffering createVpcOffering(CreateVPCOfferingCmd cmd) { public VpcOffering createVpcOffering(final String name, final String displayText, final List supportedServices, final Map> serviceProviders, final Map serviceCapabilityList, final NetUtils.InternetProtocol internetProtocol, final Long serviceOfferingId, final String externalProvider, final NetworkOffering.NetworkMode networkMode, List domainIds, List zoneIds, State state, - NetworkOffering.RoutingMode routingMode, boolean specifyAsNumber, boolean conserveMode) { + NetworkOffering.RoutingMode routingMode, boolean specifyAsNumber, boolean conserveMode, Integer publicNetworkRate) { boolean isExternalProvider = externalProvider != null && Arrays.asList("NSX", "Netris").stream().anyMatch(s -> s.equalsIgnoreCase(externalProvider)); @@ -741,7 +756,7 @@ public VpcOffering createVpcOffering(final String name, final String displayText final boolean offersRegionLevelVPC = isVpcOfferingForRegionLevelVpc(serviceCapabilityList); final boolean redundantRouter = isVpcOfferingRedundantRouter(serviceCapabilityList, redundantRouterService); final VpcOfferingVO offering = createVpcOffering(name, displayText, svcProviderMap, false, state, serviceOfferingId, supportsDistributedRouter, offersRegionLevelVPC, - redundantRouter, networkMode, routingMode, specifyAsNumber, conserveMode); + redundantRouter, networkMode, routingMode, specifyAsNumber, conserveMode, publicNetworkRate); if (offering != null) { List detailsVO = new ArrayList<>(); @@ -769,7 +784,8 @@ public VpcOffering createVpcOffering(final String name, final String displayText @DB protected VpcOfferingVO createVpcOffering(final String name, final String displayText, final Map> svcProviderMap, final boolean isDefault, final State state, final Long serviceOfferingId, final boolean supportsDistributedRouter, final boolean offersRegionLevelVPC, - final boolean redundantRouter, NetworkOffering.NetworkMode networkMode, NetworkOffering.RoutingMode routingMode, boolean specifyAsNumber, boolean conserveMode) { + final boolean redundantRouter, NetworkOffering.NetworkMode networkMode, NetworkOffering.RoutingMode routingMode, boolean specifyAsNumber, boolean conserveMode, + Integer publicNetworkRate) { return Transaction.execute(new TransactionCallback() { @Override @@ -786,6 +802,7 @@ public VpcOfferingVO doInTransaction(final TransactionStatus status) { offering.setRoutingMode(routingMode); } offering.setConserveMode(conserveMode); + offering.setPublicNetworkRate(publicNetworkRate); logger.debug("Adding vpc offering " + offering); offering = _vpcOffDao.persist(offering); @@ -1094,6 +1111,10 @@ private void applyResolvedValuesToCommand(CloneVPCOfferingCmd cmd, VpcOfferingVO ConfigurationManagerImpl.setField(cmd, "routingMode", sourceOffering.getRoutingMode().toString()); } + if (cmd.getPublicNetworkRate() == null && sourceOffering.getPublicNetworkRate() != null) { + ConfigurationManagerImpl.setField(cmd, "publicNetworkRate", sourceOffering.getPublicNetworkRate()); + } + if (cmd.getDomainIds() == null || cmd.getDomainIds().isEmpty()) { if (sourceDomainIds != null && !sourceDomainIds.isEmpty()) { ConfigurationManagerImpl.setField(cmd, "domainIds", sourceDomainIds); @@ -1841,10 +1862,16 @@ public VpcVO doInTransaction(final TransactionStatus status) { }); if (vpcVO != null) { UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VPC_CREATE, vpcVO.getAccountId(), vpcVO.getZoneId(), vpcVO.getId(), vpcVO.getName(), Vpc.class.getName(), vpcVO.getUuid(), vpcVO.isDisplay()); + saveVpcNetworkRateInDetails(vpcVO); } return vpcVO; } + private void saveVpcNetworkRateInDetails(Vpc vpc) { + final Integer rate = _configMgr.getVpcOfferingNetworkRate(vpc.getVpcOfferingId(), vpc.getZoneId()); + vpcDetailsDao.addDetail(vpc.getId(), ApiConstants.PUBLIC_NETWORK_RATE, String.valueOf(rate), true); + } + private Map> finalizeServicesAndProvidersForVpc(final long zoneId, final long offeringId) { final Map> svcProviders = new HashMap<>(); final List servicesMap = _vpcOffSvcMapDao.listByVpcOffId(offeringId); @@ -2824,6 +2851,8 @@ public boolean restartVpc(Long vpcId, boolean cleanUp, boolean makeRedundant, bo return false; } reconfigStaticNatForVpcVr(vpcId); + // only after a cleanup: the VR is recreated and its public NIC gets the new rate + saveVpcNetworkRateInDetails(vpc); return true; } diff --git a/server/src/test/java/com/cloud/api/ApiResponseHelperTest.java b/server/src/test/java/com/cloud/api/ApiResponseHelperTest.java index c0c019f6dbd8..9c70417e014f 100644 --- a/server/src/test/java/com/cloud/api/ApiResponseHelperTest.java +++ b/server/src/test/java/com/cloud/api/ApiResponseHelperTest.java @@ -50,6 +50,7 @@ import org.apache.cloudstack.api.response.DirectDownloadCertificateResponse; import org.apache.cloudstack.api.response.GuestOSCategoryResponse; import org.apache.cloudstack.api.response.IpQuarantineResponse; +import org.apache.cloudstack.api.response.NicResponse; import org.apache.cloudstack.api.response.NicSecondaryIpResponse; import org.apache.cloudstack.api.response.ResourceIconResponse; import org.apache.cloudstack.api.response.TemplateResponse; @@ -60,6 +61,7 @@ import org.apache.cloudstack.usage.UsageService; import org.apache.cloudstack.vm.UnmanagedInstanceTO; +import com.cloud.api.query.dao.UserVmJoinDao; import com.cloud.capacity.Capacity; import com.cloud.configuration.Resource; import com.cloud.domain.DomainVO; @@ -93,10 +95,14 @@ import com.cloud.user.UserDataVO; import com.cloud.user.UserVO; import com.cloud.user.dao.UserDataDao; +import com.cloud.utils.db.EntityManager; import com.cloud.utils.net.Ip; import com.cloud.vm.ConsoleSessionVO; import com.cloud.vm.NicSecondaryIp; +import com.cloud.vm.NicVO; import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.dao.NicExtraDhcpOptionDao; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -150,6 +156,15 @@ public class ApiResponseHelperTest { @Mock private VMInstanceVO vmInstanceVOMock; + @Mock + EntityManager entityManager; + + @Mock + NicExtraDhcpOptionDao nicExtraDhcpOptionDao; + + @Mock + UserVmJoinDao userVmJoinDao; + @Spy @InjectMocks ApiResponseHelper apiResponseHelper = new ApiResponseHelper(); @@ -168,6 +183,8 @@ public class ApiResponseHelperTest { static long autoScaleUserId = 7L; + private static final long NETWORK_ID = 10L; + @Before public void injectMocks() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException { @@ -800,4 +817,28 @@ public void createConsoleSessionResponseTestShouldReturnFullResponse() { Assert.assertEquals(expected.getVmName(), response.getVmName()); } } + + private NicResponse nicResponse(Integer rate) { + final NicVO nic = new NicVO("test", 1L, NETWORK_ID, VirtualMachine.Type.User); + nic.setNetworkRate(rate); + final NetworkVO network = Mockito.mock(NetworkVO.class); + Mockito.when(network.getUuid()).thenReturn("network-uuid"); + final VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); + Mockito.when(vm.getUuid()).thenReturn("vm-uuid"); + Mockito.when(entityManager.findById(NetworkVO.class, NETWORK_ID)).thenReturn(network); + Mockito.when(entityManager.findById(VMInstanceVO.class, 1L)).thenReturn(vm); + return apiResponseHelper.createNicResponse(nic); + } + + @Test + public void nicResponseReportsStoredRate() { + Assert.assertEquals(Integer.valueOf(120), nicResponse(120).getNetworkRate()); + } + + @Test + public void nicResponseReportsMissingZeroAndNegativeRateAsUnlimited() { + Assert.assertEquals(Integer.valueOf(-1), nicResponse(null).getNetworkRate()); + Assert.assertEquals(Integer.valueOf(-1), nicResponse(0).getNetworkRate()); + Assert.assertEquals(Integer.valueOf(-1), nicResponse(-1).getNetworkRate()); + } } diff --git a/server/src/test/java/com/cloud/api/query/dao/DomainRouterJoinDaoImplTest.java b/server/src/test/java/com/cloud/api/query/dao/DomainRouterJoinDaoImplTest.java new file mode 100644 index 000000000000..b23d0328ef2c --- /dev/null +++ b/server/src/test/java/com/cloud/api/query/dao/DomainRouterJoinDaoImplTest.java @@ -0,0 +1,129 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.api.query.dao; + +import java.util.Set; + +import org.apache.cloudstack.annotation.dao.AnnotationDao; +import org.apache.cloudstack.api.response.DomainRouterResponse; +import org.apache.cloudstack.api.response.NicResponse; +import org.apache.cloudstack.context.CallContext; +import org.junit.After; +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.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.test.util.ReflectionTestUtils; + +import com.cloud.api.ApiDBUtils; +import com.cloud.api.query.vo.DomainRouterJoinVO; +import com.cloud.cpu.CPU; +import com.cloud.network.Networks.TrafficType; +import com.cloud.network.router.VirtualRouter; +import com.cloud.user.Account; +import com.cloud.user.AccountManager; +import com.cloud.user.AccountVO; +import com.cloud.user.User; +import com.cloud.user.UserVO; + +@RunWith(MockitoJUnitRunner.Silent.class) +public class DomainRouterJoinDaoImplTest { + + @Mock + private AccountManager accountManager; + @Mock + private AnnotationDao annotationDao; + + @InjectMocks + private DomainRouterJoinDaoImpl dao = new DomainRouterJoinDaoImpl(); + + private MockedStatic apiDBUtils; + + @Before + public void setUp() { + apiDBUtils = Mockito.mockStatic(ApiDBUtils.class); + final AccountVO account = new AccountVO("testaccount", 1L, "networkdomain", Account.Type.NORMAL, "uuid"); + account.setId(1L); + final UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", + "user-uuid", User.Source.UNKNOWN); + CallContext.register(user, account); + } + + @After + public void tearDown() { + CallContext.unregister(); + apiDBUtils.close(); + } + + private DomainRouterJoinVO router(Integer nicNetworkRate) { + final DomainRouterJoinVO router = Mockito.mock(DomainRouterJoinVO.class); + Mockito.when(router.getUuid()).thenReturn("router-uuid"); + Mockito.when(router.getArch()).thenReturn(CPU.CPUArch.amd64); + Mockito.when(router.getRole()).thenReturn(VirtualRouter.Role.VIRTUAL_ROUTER); + Mockito.when(router.getNicId()).thenReturn(5L); + Mockito.when(router.getTrafficType()).thenReturn(TrafficType.Public); + Mockito.when(router.getNicNetworkRate()).thenReturn(nicNetworkRate); + return router; + } + + @SuppressWarnings("unchecked") + private static NicResponse onlyNicOf(DomainRouterResponse response) { + final Set nics = (Set) ReflectionTestUtils.getField(response, "nics"); + Assert.assertEquals(1, nics.size()); + return nics.iterator().next(); + } + + private DomainRouterResponse newResponse(Integer nicNetworkRate) { + final Account caller = Mockito.mock(Account.class); + Mockito.when(caller.getId()).thenReturn(1L); + Mockito.when(accountManager.isRootAdmin(1L)).thenReturn(true); + return dao.newDomainRouterResponse(router(nicNetworkRate), caller); + } + + @Test + public void newDomainRouterResponseReportsNicNetworkRate() { + Assert.assertEquals(Integer.valueOf(150), onlyNicOf(newResponse(150)).getNetworkRate()); + } + + @Test + public void newDomainRouterResponseReportsMissingZeroAndNegativeNicRateAsUnlimited() { + Assert.assertEquals(Integer.valueOf(-1), onlyNicOf(newResponse(null)).getNetworkRate()); + Assert.assertEquals(Integer.valueOf(-1), onlyNicOf(newResponse(0)).getNetworkRate()); + Assert.assertEquals(Integer.valueOf(-1), onlyNicOf(newResponse(-1)).getNetworkRate()); + } + + private DomainRouterResponse setResponse(Integer nicNetworkRate) { + return dao.setDomainRouterResponse(new DomainRouterResponse(), router(nicNetworkRate)); + } + + @Test + public void setDomainRouterResponseReportsNicNetworkRate() { + Assert.assertEquals(Integer.valueOf(150), onlyNicOf(setResponse(150)).getNetworkRate()); + } + + @Test + public void setDomainRouterResponseReportsMissingZeroAndNegativeNicRateAsUnlimited() { + Assert.assertEquals(Integer.valueOf(-1), onlyNicOf(setResponse(null)).getNetworkRate()); + Assert.assertEquals(Integer.valueOf(-1), onlyNicOf(setResponse(0)).getNetworkRate()); + Assert.assertEquals(Integer.valueOf(-1), onlyNicOf(setResponse(-1)).getNetworkRate()); + } +} diff --git a/server/src/test/java/com/cloud/api/query/dao/UserVmJoinDaoImplTest.java b/server/src/test/java/com/cloud/api/query/dao/UserVmJoinDaoImplTest.java index f657a8bbf045..de92852cd727 100755 --- a/server/src/test/java/com/cloud/api/query/dao/UserVmJoinDaoImplTest.java +++ b/server/src/test/java/com/cloud/api/query/dao/UserVmJoinDaoImplTest.java @@ -28,7 +28,9 @@ import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ResponseObject; +import org.apache.cloudstack.api.response.NicResponse; import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.extension.ExtensionHelper; import org.junit.After; import org.junit.Assert; @@ -37,6 +39,7 @@ import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; @@ -54,6 +57,7 @@ import com.cloud.host.dao.HostDetailsDao; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; +import com.cloud.vm.dao.NicExtraDhcpOptionDao; import com.cloud.vm.dao.VMInstanceDetailsDao; import com.cloud.vm.dao.VmIsoMapDao; @@ -96,6 +100,9 @@ public class UserVmJoinDaoImplTest extends GenericDaoBaseWithTagInformationBaseT @Mock ExtensionHelper extensionHelper; + @Mock + NicExtraDhcpOptionDao nicExtraDhcpOptionDao; + private UserVmJoinVO userVm = new UserVmJoinVO(); private UserVmResponse userVmResponse = new UserVmResponse(); @@ -212,4 +219,58 @@ public void effectiveCdromMaxCountClampsToHypervisorCap() { // Configured cap defaults to 1 (no cluster override mocked); host advertises 2; clamps to 1. Assert.assertEquals(1, _userVmJoinDaoImpl.effectiveCdromMaxCount(userVm)); } + + private UserVmJoinVO userVmWithNic(Integer nicNetworkRate) { + UserVmJoinVO uvo = Mockito.mock(UserVmJoinVO.class); + Mockito.when(uvo.getId()).thenReturn(vmId); + Mockito.when(uvo.getNicId()).thenReturn(5L); + Mockito.when(uvo.getNicNetworkRate()).thenReturn(nicNetworkRate); + return uvo; + } + + private NicResponse onlyNicOf(UserVmResponse response) { + Assert.assertEquals(1, response.getNics().size()); + return response.getNics().iterator().next(); + } + + private NicResponse setUserVmResponseNic(Integer nicNetworkRate) { + CallContext callContext = Mockito.mock(CallContext.class); + Mockito.when(callContext.getCallingAccount()).thenReturn(caller); + try (MockedStatic callContextStatic = Mockito.mockStatic(CallContext.class)) { + callContextStatic.when(CallContext::current).thenReturn(callContext); + UserVmResponse response = _userVmJoinDaoImpl.setUserVmResponse(ResponseObject.ResponseView.Full, new UserVmResponse(), userVmWithNic(nicNetworkRate)); + return onlyNicOf(response); + } + } + + @Test + public void testSetUserVmResponseReportsNicNetworkRate() { + Assert.assertEquals(Integer.valueOf(150), setUserVmResponseNic(150).getNetworkRate()); + } + + @Test + public void testSetUserVmResponseReportsMissingZeroAndNegativeNicRateAsUnlimited() { + Assert.assertEquals(Integer.valueOf(-1), setUserVmResponseNic(null).getNetworkRate()); + Assert.assertEquals(Integer.valueOf(-1), setUserVmResponseNic(0).getNetworkRate()); + Assert.assertEquals(Integer.valueOf(-1), setUserVmResponseNic(-1).getNetworkRate()); + } + + private NicResponse newUserVmResponseNic(Integer nicNetworkRate) { + prepareNewUserVmResponseForVnfAppliance(); + Mockito.when(userVmMock.getNicId()).thenReturn(5L); + Mockito.when(userVmMock.getNicNetworkRate()).thenReturn(nicNetworkRate); + UserVmResponse response = _userVmJoinDaoImpl.newUserVmResponse(ResponseObject.ResponseView.Full, "virtualmachine", userVmMock, + EnumSet.of(ApiConstants.VMDetails.all), null, null, caller); + return onlyNicOf(response); + } + + @Test + public void testNewUserVmResponseReportsNicNetworkRate() { + Assert.assertEquals(Integer.valueOf(150), newUserVmResponseNic(150).getNetworkRate()); + } + + @Test + public void testNewUserVmResponseReportsMissingNicRateAsUnlimited() { + Assert.assertEquals(Integer.valueOf(-1), newUserVmResponseNic(null).getNetworkRate()); + } } diff --git a/server/src/test/java/com/cloud/api/query/dao/VpcOfferingJoinDaoImplTest.java b/server/src/test/java/com/cloud/api/query/dao/VpcOfferingJoinDaoImplTest.java new file mode 100644 index 000000000000..9ffd5ff950c2 --- /dev/null +++ b/server/src/test/java/com/cloud/api/query/dao/VpcOfferingJoinDaoImplTest.java @@ -0,0 +1,63 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.api.query.dao; + +import org.apache.cloudstack.api.response.VpcOfferingResponse; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.test.util.ReflectionTestUtils; + +import com.cloud.api.query.vo.VpcOfferingJoinVO; +import com.cloud.network.vpc.VpcOffering; +import com.cloud.network.vpc.dao.VpcOfferingServiceMapDao; + +@RunWith(MockitoJUnitRunner.Silent.class) +public class VpcOfferingJoinDaoImplTest { + + @Mock + private VpcOfferingServiceMapDao vpcOfferingServiceMapDao; + + @InjectMocks + private VpcOfferingJoinDaoImpl dao = new VpcOfferingJoinDaoImpl(); + + private VpcOfferingResponse responseFor(Integer publicNetworkRate) { + final VpcOfferingJoinVO offering = Mockito.mock(VpcOfferingJoinVO.class); + Mockito.when(offering.getState()).thenReturn(VpcOffering.State.Enabled); + Mockito.when(offering.getPublicNetworkRate()).thenReturn(publicNetworkRate); + return dao.newVpcOfferingResponse(offering); + } + + @Test + public void responseCarriesPublicNetworkRate() { + Assert.assertEquals(100, ReflectionTestUtils.getField(responseFor(100), "publicNetworkRate")); + } + + @Test + public void responseCarriesUnlimitedPublicNetworkRate() { + Assert.assertEquals(-1, ReflectionTestUtils.getField(responseFor(-1), "publicNetworkRate")); + } + + @Test + public void responseLeavesPublicNetworkRateUnsetWhenOfferingHasNone() { + Assert.assertNull(ReflectionTestUtils.getField(responseFor(null), "publicNetworkRate")); + } +} diff --git a/server/src/test/java/com/cloud/api/query/vo/NetworkRateJoinVOTest.java b/server/src/test/java/com/cloud/api/query/vo/NetworkRateJoinVOTest.java new file mode 100644 index 000000000000..64f9d76c62c0 --- /dev/null +++ b/server/src/test/java/com/cloud/api/query/vo/NetworkRateJoinVOTest.java @@ -0,0 +1,51 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.api.query.vo; + +import org.junit.Assert; +import org.junit.Test; +import org.springframework.test.util.ReflectionTestUtils; + +public class NetworkRateJoinVOTest { + + @Test + public void userVmJoinVOExposesNicNetworkRate() { + final UserVmJoinVO vo = new UserVmJoinVO(); + Assert.assertNull(vo.getNicNetworkRate()); + + ReflectionTestUtils.setField(vo, "nicNetworkRate", 150); + Assert.assertEquals(Integer.valueOf(150), vo.getNicNetworkRate()); + } + + @Test + public void domainRouterJoinVOExposesNicNetworkRate() { + final DomainRouterJoinVO vo = new DomainRouterJoinVO(); + Assert.assertNull(vo.getNicNetworkRate()); + + ReflectionTestUtils.setField(vo, "nicNetworkRate", 150); + Assert.assertEquals(Integer.valueOf(150), vo.getNicNetworkRate()); + } + + @Test + public void vpcOfferingJoinVOExposesPublicNetworkRate() { + final VpcOfferingJoinVO vo = new VpcOfferingJoinVO(); + Assert.assertNull(vo.getPublicNetworkRate()); + + ReflectionTestUtils.setField(vo, "publicNetworkRate", 200); + Assert.assertEquals(Integer.valueOf(200), vo.getPublicNetworkRate()); + } +} diff --git a/server/src/test/java/com/cloud/configuration/ConfigurationManagerImplTest.java b/server/src/test/java/com/cloud/configuration/ConfigurationManagerImplTest.java index 9a0b150780e4..3fe78cc8d135 100644 --- a/server/src/test/java/com/cloud/configuration/ConfigurationManagerImplTest.java +++ b/server/src/test/java/com/cloud/configuration/ConfigurationManagerImplTest.java @@ -38,6 +38,7 @@ import com.cloud.network.dao.NsxProviderDao; import com.cloud.network.dao.PhysicalNetworkDao; import com.cloud.network.element.NsxProviderVO; +import com.cloud.network.vpc.VpcOffering; import com.cloud.offering.DiskOffering; import com.cloud.offering.NetworkOffering; import com.cloud.offerings.NetworkOfferingVO; @@ -66,6 +67,7 @@ import org.apache.cloudstack.api.command.admin.zone.DeleteZoneCmd; import org.apache.cloudstack.config.Configuration; import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope; import org.apache.cloudstack.framework.config.ConfigDepot; import org.apache.cloudstack.framework.config.ConfigKey; @@ -851,6 +853,63 @@ public void validateValueRangeTestReturnsNullWhenConfigKeyHasNoRange() { Assert.assertNull(configurationManagerImplSpy.validateValueRange("configkey.without.range", "0", Integer.class, null)); } + @Test + public void validateValueRangeTestVpcPublicNetworkThrottlingRateAcceptsUnlimitedAndPositive() { + String key = NetworkOrchestrationService.VpcPublicNetworkThrottlingRate.key(); + Assert.assertNull(configurationManagerImplSpy.validateValueRange(key, "-1", Integer.class, null)); + Assert.assertNull(configurationManagerImplSpy.validateValueRange(key, "0", Integer.class, null)); + Assert.assertNull(configurationManagerImplSpy.validateValueRange(key, "1", Integer.class, null)); + Assert.assertNull(configurationManagerImplSpy.validateValueRange(key, "100", Integer.class, null)); + Assert.assertNull(configurationManagerImplSpy.validateValueRange(key.toUpperCase(), "100", Integer.class, null)); + } + + @Test + public void validateValueRangeTestVpcPublicNetworkThrottlingRateRejectsBelowMinusOne() { + String key = NetworkOrchestrationService.VpcPublicNetworkThrottlingRate.key(); + Assert.assertNotNull(configurationManagerImplSpy.validateValueRange(key, "-2", Integer.class, null)); + Assert.assertNotNull(configurationManagerImplSpy.validateValueRange(key.toUpperCase(), "-2", Integer.class, null)); + Assert.assertNotNull(configurationManagerImplSpy.validateValueRange(key, "-5", Integer.class, null)); + } + + private VpcOffering mockVpcOfferingWithRate(Integer rate) { + VpcOffering vpcOffering = Mockito.mock(VpcOffering.class); + Mockito.when(vpcOffering.getPublicNetworkRate()).thenReturn(rate); + Mockito.doReturn(vpcOffering).when(entityManagerMock).findById(VpcOffering.class, 1L); + return vpcOffering; + } + + @Test + public void getVpcOfferingNetworkRateTestReturnsOfferingRateAsIs() { + mockVpcOfferingWithRate(200); + Assert.assertEquals(Integer.valueOf(200), configurationManagerImplSpy.getVpcOfferingNetworkRate(1L, 1L)); + } + + @Test + public void getVpcOfferingNetworkRateTestKeepsUnlimitedOfferingRate() { + mockVpcOfferingWithRate(-1); + Assert.assertEquals(Integer.valueOf(-1), configurationManagerImplSpy.getVpcOfferingNetworkRate(1L, 1L)); + } + + @Test + public void getVpcOfferingNetworkRateTestConvertsZeroOfferingRateToUnlimited() { + mockVpcOfferingWithRate(0); + Assert.assertEquals(Integer.valueOf(-1), configurationManagerImplSpy.getVpcOfferingNetworkRate(1L, 1L)); + } + + @Test + public void getVpcOfferingNetworkRateTestFallsBackToZoneSettingWhenOfferingHasNoRate() { + mockVpcOfferingWithRate(null); + // no config depot in unit tests, so the zone setting resolves to its default value (-1, unlimited) + Assert.assertEquals(NetworkOrchestrationService.VpcPublicNetworkThrottlingRate.valueIn(1L), + configurationManagerImplSpy.getVpcOfferingNetworkRate(1L, 1L)); + } + + @Test(expected = InvalidParameterValueException.class) + public void getVpcOfferingNetworkRateTestThrowsWhenOfferingDoesNotExist() { + Mockito.doReturn(null).when(entityManagerMock).findById(VpcOffering.class, 1L); + configurationManagerImplSpy.getVpcOfferingNetworkRate(1L, 1L); + } + @Test public void validateValueRangeTestReturnsNullWhenConfigKeyHasRangeAndValueIsValid() { Assert.assertNull(configurationManagerImplSpy.validateValueRange(NetworkModel.MACIdentifier.key(), "100", Integer.class, null)); diff --git a/server/src/test/java/com/cloud/network/NetworkMigrationManagerImplTest.java b/server/src/test/java/com/cloud/network/NetworkMigrationManagerImplTest.java new file mode 100644 index 000000000000..2a5bcbd9857e --- /dev/null +++ b/server/src/test/java/com/cloud/network/NetworkMigrationManagerImplTest.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.cloud.network; + +import java.util.Arrays; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.resourcedetail.VpcDetailVO; +import org.apache.cloudstack.resourcedetail.dao.VpcDetailsDao; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.network.dao.NetworkDetailVO; +import com.cloud.network.dao.NetworkDetailsDao; + +@RunWith(MockitoJUnitRunner.class) +public class NetworkMigrationManagerImplTest { + + @Mock + private VpcDetailsDao vpcDetailsDao; + + @Mock + private NetworkDetailsDao networkDetailsDao; + + @InjectMocks + private NetworkMigrationManagerImpl networkMigrationManager; + + @Test + public void testCopyVpcDetailsSkipsPublicNetworkRate() { + final long srcVpcId = 10L; + final long dstVpcId = 11L; + Mockito.when(vpcDetailsDao.listDetails(srcVpcId)).thenReturn(Arrays.asList( + new VpcDetailVO(srcVpcId, ApiConstants.PUBLIC_NETWORK_RATE, "200", true), + new VpcDetailVO(srcVpcId, "someKey", "someValue", false))); + + networkMigrationManager.copyVpcDetails(srcVpcId, dstVpcId); + + Mockito.verify(vpcDetailsDao, Mockito.never()).addDetail(Mockito.eq(dstVpcId), Mockito.eq(ApiConstants.PUBLIC_NETWORK_RATE), + Mockito.anyString(), Mockito.anyBoolean()); + Mockito.verify(vpcDetailsDao).addDetail(dstVpcId, "someKey", "someValue", false); + } + + @Test + public void testCopyNetworkDetailsSkipsNetworkRate() { + final long srcNetworkId = 20L; + final long dstNetworkId = 21L; + Mockito.when(networkDetailsDao.listDetails(srcNetworkId)).thenReturn(Arrays.asList( + new NetworkDetailVO(srcNetworkId, ApiConstants.NETWORKRATE, "100", true), + new NetworkDetailVO(srcNetworkId, "someKey", "someValue", false))); + + networkMigrationManager.copyNetworkDetails(srcNetworkId, dstNetworkId); + + ArgumentCaptor captor = ArgumentCaptor.forClass(NetworkDetailVO.class); + Mockito.verify(networkDetailsDao, Mockito.times(1)).persist(captor.capture()); + Assert.assertEquals("someKey", captor.getValue().getName()); + Assert.assertEquals(dstNetworkId, captor.getValue().getResourceId()); + } +} diff --git a/server/src/test/java/com/cloud/network/NetworkModelImplTest.java b/server/src/test/java/com/cloud/network/NetworkModelImplTest.java index 7810662c617b..79a0c1f12638 100644 --- a/server/src/test/java/com/cloud/network/NetworkModelImplTest.java +++ b/server/src/test/java/com/cloud/network/NetworkModelImplTest.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.when; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -47,10 +48,12 @@ import org.mockito.junit.MockitoJUnitRunner; import org.springframework.test.util.ReflectionTestUtils; +import com.cloud.configuration.ConfigurationManager; import com.cloud.dc.DataCenter; import com.cloud.dc.DataCenterVO; import com.cloud.dc.VlanVO; import com.cloud.exception.InvalidParameterValueException; +import com.cloud.network.Networks.TrafficType; import com.cloud.network.addr.PublicIp; import com.cloud.network.dao.IPAddressVO; import com.cloud.network.dao.NetworkDao; @@ -64,14 +67,25 @@ import com.cloud.network.element.VpcVirtualRouterElement; import com.cloud.network.vpc.VpcVO; import com.cloud.network.vpc.dao.VpcDao; +import com.cloud.offering.NetworkOffering; import com.cloud.offerings.NetworkOfferingVO; import com.cloud.offerings.dao.NetworkOfferingDao; import com.cloud.offerings.dao.NetworkOfferingServiceMapDao; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.utils.Pair; +import com.cloud.utils.db.EntityManager; import com.cloud.utils.net.Ip; +import com.cloud.vm.DomainRouterVO; import com.cloud.vm.Nic; import com.cloud.vm.NicProfile; +import com.cloud.vm.NicVO; +import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; +import com.cloud.vm.dao.DomainRouterDao; +import com.cloud.vm.dao.NicDao; +import com.cloud.vm.dao.VMInstanceDao; +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.extension.Extension; import org.apache.cloudstack.extension.ExtensionHelper; import org.apache.cloudstack.framework.extensions.network.NetworkExtensionElement; @@ -101,6 +115,19 @@ public class NetworkModelImplTest { @Mock private PhysicalNetworkServiceProviderDao physicalNetworkServiceProviderDao; + @Mock + private VMInstanceDao vmInstanceDao; + @Mock + private NicDao nicDao; + @Mock + private DomainRouterDao routerDao; + @Mock + private ServiceOfferingDao serviceOfferingDao; + @Mock + private ConfigurationManager configMgr; + @Mock + private EntityManager entityMgr; + @Spy @InjectMocks private NetworkModelImpl networkModel = new NetworkModelImpl(); @@ -120,6 +147,11 @@ public void setUp() { networkModel.networkExtensionElement = networkExtensionElement; networkModel._physicalNetworkDao = physicalNetworkDao; networkModel._pNSPDao = physicalNetworkServiceProviderDao; + networkModel._vmDao = vmInstanceDao; + networkModel._nicDao = nicDao; + networkModel._serviceOfferingDao = serviceOfferingDao; + networkModel._configMgr = configMgr; + networkModel._entityMgr = entityMgr; Mockito.lenient().when(extensionHelper.isNetworkExtensionProvider(Mockito.anyString())).thenReturn(false); } @@ -452,4 +484,266 @@ public void listSupportedNetworkServiceProvidersExcludesExtensionBackedProviders Mockito.verify(physicalNetworkServiceProviderDao, Mockito.times(1)).listAll(); Mockito.verify(physicalNetworkServiceProviderDao, Mockito.never()).listBy(Mockito.anyLong()); } + + // ----------------------------------------------------------------------- + // Tests for getNetworkRate + // ----------------------------------------------------------------------- + + private NetworkVO mockNetwork(long networkId, long networkOfferingId, long dataCenterId, TrafficType trafficType) { + NetworkVO network = mock(NetworkVO.class); + when(network.getNetworkOfferingId()).thenReturn(networkOfferingId); + when(network.getDataCenterId()).thenReturn(dataCenterId); + when(network.getTrafficType()).thenReturn(trafficType); + when(_networksDao.findById(networkId)).thenReturn(network); + return network; + } + + private void mockNetworkOffering(long networkOfferingId) { + NetworkOffering ntwkOff = mock(NetworkOffering.class); + when(ntwkOff.getId()).thenReturn(networkOfferingId); + when(entityMgr.findById(NetworkOffering.class, networkOfferingId)).thenReturn(ntwkOff); + } + + @Test + public void getNetworkRate_userVmWithNic_returnsServiceOfferingRate() { + long networkId = 1L, vmId = 10L, serviceOfferingId = 20L, dataCenterId = 2L; + mockNetwork(networkId, 30L, dataCenterId, TrafficType.Guest); + VMInstanceVO vm = mock(VMInstanceVO.class); + when(vm.getType()).thenReturn(VirtualMachine.Type.User); + when(vm.getServiceOfferingId()).thenReturn(serviceOfferingId); + when(vmInstanceDao.findById(vmId)).thenReturn(vm); + when(nicDao.findByNtwkIdAndInstanceId(networkId, vmId)).thenReturn(mock(NicVO.class)); + when(configMgr.getServiceOfferingNetworkRate(serviceOfferingId, dataCenterId)).thenReturn(100); + + assertEquals(Integer.valueOf(100), networkModel.getNetworkRate(networkId, vmId)); + } + + @Test + public void getNetworkRate_userVmWithoutNic_fallsBackToNetworkOffering() { + long networkId = 1L, vmId = 10L, networkOfferingId = 30L, dataCenterId = 2L; + mockNetwork(networkId, networkOfferingId, dataCenterId, TrafficType.Guest); + VMInstanceVO vm = mock(VMInstanceVO.class); + when(vm.getType()).thenReturn(VirtualMachine.Type.User); + when(vmInstanceDao.findById(vmId)).thenReturn(vm); + when(nicDao.findByNtwkIdAndInstanceId(networkId, vmId)).thenReturn(null); + mockNetworkOffering(networkOfferingId); + when(configMgr.getNetworkOfferingNetworkRate(networkOfferingId, dataCenterId)).thenReturn(50); + + assertEquals(Integer.valueOf(50), networkModel.getNetworkRate(networkId, vmId)); + } + + @Test + public void getNetworkRate_routerGuestWithSystemOfferingRate_returnsSystemOfferingRate() { + long networkId = 1L, vmId = 11L, serviceOfferingId = 21L, dataCenterId = 2L; + mockNetwork(networkId, 31L, dataCenterId, TrafficType.Guest); + VMInstanceVO vm = mock(VMInstanceVO.class); + when(vm.getType()).thenReturn(VirtualMachine.Type.DomainRouter); + when(vm.getServiceOfferingId()).thenReturn(serviceOfferingId); + when(vmInstanceDao.findById(vmId)).thenReturn(vm); + when(nicDao.findByNtwkIdAndInstanceId(networkId, vmId)).thenReturn(mock(NicVO.class)); + ServiceOfferingVO routerOffering = mock(ServiceOfferingVO.class); + when(routerOffering.getRateMbps()).thenReturn(75); + when(serviceOfferingDao.findById(serviceOfferingId)).thenReturn(routerOffering); + + assertEquals(Integer.valueOf(75), networkModel.getNetworkRate(networkId, vmId)); + } + + @Test + public void getNetworkRate_routerGuestWithZeroSystemOfferingRate_returnsUnlimited() { + long networkId = 1L, vmId = 11L, serviceOfferingId = 21L, dataCenterId = 2L; + mockNetwork(networkId, 31L, dataCenterId, TrafficType.Guest); + VMInstanceVO vm = mock(VMInstanceVO.class); + when(vm.getType()).thenReturn(VirtualMachine.Type.DomainRouter); + when(vm.getServiceOfferingId()).thenReturn(serviceOfferingId); + when(vmInstanceDao.findById(vmId)).thenReturn(vm); + when(nicDao.findByNtwkIdAndInstanceId(networkId, vmId)).thenReturn(mock(NicVO.class)); + ServiceOfferingVO routerOffering = mock(ServiceOfferingVO.class); + when(routerOffering.getRateMbps()).thenReturn(0); + when(serviceOfferingDao.findById(serviceOfferingId)).thenReturn(routerOffering); + + assertEquals(Integer.valueOf(-1), networkModel.getNetworkRate(networkId, vmId)); + } + + @Test + public void getNetworkRate_routerGuestWithoutSystemOfferingRate_fallsBackToNetworkOfferingRate() { + long networkId = 1L, vmId = 11L, serviceOfferingId = 21L, networkOfferingId = 31L, dataCenterId = 2L; + mockNetwork(networkId, networkOfferingId, dataCenterId, TrafficType.Guest); + VMInstanceVO vm = mock(VMInstanceVO.class); + when(vm.getType()).thenReturn(VirtualMachine.Type.DomainRouter); + when(vm.getServiceOfferingId()).thenReturn(serviceOfferingId); + when(vmInstanceDao.findById(vmId)).thenReturn(vm); + when(nicDao.findByNtwkIdAndInstanceId(networkId, vmId)).thenReturn(mock(NicVO.class)); + when(serviceOfferingDao.findById(serviceOfferingId)).thenReturn(null); + when(configMgr.getNetworkOfferingNetworkRate(networkOfferingId, dataCenterId)).thenReturn(60); + + assertEquals(Integer.valueOf(60), networkModel.getNetworkRate(networkId, vmId)); + } + + @Test + public void getNetworkRate_routerGuestWithoutNic_fallsBackToNetworkOfferingViaEntityManager() { + long networkId = 1L, vmId = 11L, networkOfferingId = 31L, dataCenterId = 2L; + mockNetwork(networkId, networkOfferingId, dataCenterId, TrafficType.Guest); + VMInstanceVO vm = mock(VMInstanceVO.class); + when(vm.getType()).thenReturn(VirtualMachine.Type.DomainRouter); + when(vmInstanceDao.findById(vmId)).thenReturn(vm); + when(nicDao.findByNtwkIdAndInstanceId(networkId, vmId)).thenReturn(null); + mockNetworkOffering(networkOfferingId); + when(configMgr.getNetworkOfferingNetworkRate(networkOfferingId, dataCenterId)).thenReturn(40); + + assertEquals(Integer.valueOf(40), networkModel.getNetworkRate(networkId, vmId)); + } + + @Test + public void getNetworkRate_routerPublicWithGuestSibling_returnsGuestNetworkOfferingRate() { + long networkId = 1L, vmId = 12L, dataCenterId = 2L, guestNetworkId = 5L, guestOfferingId = 55L; + mockNetwork(networkId, 99L, dataCenterId, TrafficType.Public); + VMInstanceVO vm = mock(VMInstanceVO.class); + when(vm.getType()).thenReturn(VirtualMachine.Type.DomainRouter); + when(vmInstanceDao.findById(vmId)).thenReturn(vm); + when(routerDao.findById(vmId)).thenReturn(null); + NicVO guestNic = mock(NicVO.class); + when(guestNic.getNetworkId()).thenReturn(guestNetworkId); + when(nicDao.listByVmId(vmId)).thenReturn(List.of(guestNic)); + NetworkVO guestNetwork = mock(NetworkVO.class); + when(guestNetwork.getTrafficType()).thenReturn(TrafficType.Guest); + when(guestNetwork.getNetworkOfferingId()).thenReturn(guestOfferingId); + when(_networksDao.findById(guestNetworkId)).thenReturn(guestNetwork); + when(configMgr.getNetworkOfferingNetworkRate(guestOfferingId, dataCenterId)).thenReturn(80); + + assertEquals(Integer.valueOf(80), networkModel.getNetworkRate(networkId, vmId)); + } + + @Test + public void getNetworkRate_routerPublicWithoutGuestSibling_fallsBackToNetworkOfferingViaEntityManager() { + long networkId = 1L, vmId = 12L, networkOfferingId = 99L, dataCenterId = 2L; + mockNetwork(networkId, networkOfferingId, dataCenterId, TrafficType.Public); + VMInstanceVO vm = mock(VMInstanceVO.class); + when(vm.getType()).thenReturn(VirtualMachine.Type.DomainRouter); + when(vmInstanceDao.findById(vmId)).thenReturn(vm); + when(routerDao.findById(vmId)).thenReturn(null); + when(nicDao.listByVmId(vmId)).thenReturn(Collections.emptyList()); + mockNetworkOffering(networkOfferingId); + when(configMgr.getNetworkOfferingNetworkRate(networkOfferingId, dataCenterId)).thenReturn(33); + + assertEquals(Integer.valueOf(33), networkModel.getNetworkRate(networkId, vmId)); + } + + @Test + public void getNetworkRate_routerPublicWithVpcRouter_returnsVpcOfferingRateWithoutNicLookup() { + long networkId = 1L, vmId = 12L, dataCenterId = 2L, vpcId = 7L, vpcOfferingId = 70L; + mockNetwork(networkId, 99L, dataCenterId, TrafficType.Public); + VMInstanceVO vm = mock(VMInstanceVO.class); + when(vm.getType()).thenReturn(VirtualMachine.Type.DomainRouter); + when(vmInstanceDao.findById(vmId)).thenReturn(vm); + DomainRouterVO router = mock(DomainRouterVO.class); + when(router.getVpcId()).thenReturn(vpcId); + when(routerDao.findById(vmId)).thenReturn(router); + VpcVO vpc = mock(VpcVO.class); + when(vpc.getVpcOfferingId()).thenReturn(vpcOfferingId); + when(vpcDao.findById(vpcId)).thenReturn(vpc); + when(configMgr.getVpcOfferingNetworkRate(vpcOfferingId, dataCenterId)).thenReturn(10); + + // Resolved purely from the router's own vpc_id - the guest NIC does not need to exist + // in the nics table yet, matching the state during initial VR deployment. + assertEquals(Integer.valueOf(10), networkModel.getNetworkRate(networkId, vmId)); + Mockito.verify(nicDao, Mockito.never()).listByVmId(Mockito.anyLong()); + } + + @Test + public void getNetworkRate_routerPublicWithVpcGuestSibling_returnsVpcOfferingRate() { + long networkId = 1L, vmId = 12L, dataCenterId = 2L, vpcId = 7L, vpcOfferingId = 70L; + mockNetwork(networkId, 99L, dataCenterId, TrafficType.Public); + VMInstanceVO vm = mock(VMInstanceVO.class); + when(vm.getType()).thenReturn(VirtualMachine.Type.DomainRouter); + when(vmInstanceDao.findById(vmId)).thenReturn(vm); + DomainRouterVO router = mock(DomainRouterVO.class); + when(router.getVpcId()).thenReturn(vpcId); + when(routerDao.findById(vmId)).thenReturn(router); + VpcVO vpc = mock(VpcVO.class); + when(vpc.getVpcOfferingId()).thenReturn(vpcOfferingId); + when(vpcDao.findById(vpcId)).thenReturn(vpc); + when(configMgr.getVpcOfferingNetworkRate(vpcOfferingId, dataCenterId)).thenReturn(120); + + assertEquals(Integer.valueOf(120), networkModel.getNetworkRate(networkId, vmId)); + Mockito.verify(configMgr, Mockito.never()).getNetworkOfferingNetworkRate(Mockito.anyLong(), Mockito.anyLong()); + } + + @Test + public void getNetworkRate_routerPublicWithVpcGuestSiblingButMissingVpc_fallsBackToNetworkOfferingRate() { + long networkId = 1L, vmId = 12L, dataCenterId = 2L, guestNetworkId = 5L, vpcId = 7L, guestOfferingId = 55L; + mockNetwork(networkId, 99L, dataCenterId, TrafficType.Public); + VMInstanceVO vm = mock(VMInstanceVO.class); + when(vm.getType()).thenReturn(VirtualMachine.Type.DomainRouter); + when(vmInstanceDao.findById(vmId)).thenReturn(vm); + DomainRouterVO router = mock(DomainRouterVO.class); + when(router.getVpcId()).thenReturn(vpcId); + when(routerDao.findById(vmId)).thenReturn(router); + when(vpcDao.findById(vpcId)).thenReturn(null); + NicVO guestNic = mock(NicVO.class); + when(guestNic.getNetworkId()).thenReturn(guestNetworkId); + when(nicDao.listByVmId(vmId)).thenReturn(List.of(guestNic)); + NetworkVO guestNetwork = mock(NetworkVO.class); + when(guestNetwork.getTrafficType()).thenReturn(TrafficType.Guest); + when(guestNetwork.getNetworkOfferingId()).thenReturn(guestOfferingId); + when(_networksDao.findById(guestNetworkId)).thenReturn(guestNetwork); + when(configMgr.getNetworkOfferingNetworkRate(guestOfferingId, dataCenterId)).thenReturn(90); + + assertEquals(Integer.valueOf(90), networkModel.getNetworkRate(networkId, vmId)); + Mockito.verify(configMgr, Mockito.never()).getVpcOfferingNetworkRate(Mockito.anyLong(), Mockito.anyLong()); + } + + @Test + public void getNetworkRate_consoleProxy_returnsUnlimitedWithoutOfferingLookup() { + long networkId = 1L, vmId = 13L, dataCenterId = 2L; + mockNetwork(networkId, 40L, dataCenterId, TrafficType.Guest); + VMInstanceVO vm = mock(VMInstanceVO.class); + when(vm.getType()).thenReturn(VirtualMachine.Type.ConsoleProxy); + when(vmInstanceDao.findById(vmId)).thenReturn(vm); + + assertEquals(Integer.valueOf(-1), networkModel.getNetworkRate(networkId, vmId)); + Mockito.verifyNoInteractions(configMgr); + } + + @Test + public void getNetworkRate_secondaryStorageVm_returnsUnlimitedWithoutOfferingLookup() { + long networkId = 1L, vmId = 14L, dataCenterId = 2L; + mockNetwork(networkId, 40L, dataCenterId, TrafficType.Guest); + VMInstanceVO vm = mock(VMInstanceVO.class); + when(vm.getType()).thenReturn(VirtualMachine.Type.SecondaryStorageVm); + when(vmInstanceDao.findById(vmId)).thenReturn(vm); + + assertEquals(Integer.valueOf(-1), networkModel.getNetworkRate(networkId, vmId)); + Mockito.verifyNoInteractions(configMgr); + } + + @Test + public void getNetworkRate_noVm_usesNetworkOfferingRate() { + long networkId = 1L, networkOfferingId = 30L, dataCenterId = 2L; + mockNetwork(networkId, networkOfferingId, dataCenterId, TrafficType.Guest); + mockNetworkOffering(networkOfferingId); + when(configMgr.getNetworkOfferingNetworkRate(networkOfferingId, dataCenterId)).thenReturn(90); + + assertEquals(Integer.valueOf(90), networkModel.getNetworkRate(networkId, null)); + } + + @Test + public void getNetworkRate_vmNotFound_fallsBackToNetworkOfferingRate() { + long networkId = 1L, vmId = 15L, networkOfferingId = 30L, dataCenterId = 2L; + mockNetwork(networkId, networkOfferingId, dataCenterId, TrafficType.Guest); + when(vmInstanceDao.findById(vmId)).thenReturn(null); + mockNetworkOffering(networkOfferingId); + when(configMgr.getNetworkOfferingNetworkRate(networkOfferingId, dataCenterId)).thenReturn(77); + + assertEquals(Integer.valueOf(77), networkModel.getNetworkRate(networkId, vmId)); + } + + @Test + public void getNetworkRate_noNetworkOffering_usesGlobalThrottlingRate() { + long networkId = 1L, networkOfferingId = 30L, dataCenterId = 2L; + mockNetwork(networkId, networkOfferingId, dataCenterId, TrafficType.Guest); + when(entityMgr.findById(NetworkOffering.class, networkOfferingId)).thenReturn(null); + + Integer expected = NetworkOrchestrationService.NetworkThrottlingRate.valueIn(dataCenterId); + assertEquals(expected, networkModel.getNetworkRate(networkId, null)); + } } diff --git a/server/src/test/java/com/cloud/network/NetworkServiceImplTest.java b/server/src/test/java/com/cloud/network/NetworkServiceImplTest.java index cd7d40d68951..9aa213e7bc5a 100644 --- a/server/src/test/java/com/cloud/network/NetworkServiceImplTest.java +++ b/server/src/test/java/com/cloud/network/NetworkServiceImplTest.java @@ -34,6 +34,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -56,6 +57,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentMatchers; +import org.mockito.InOrder; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockedStatic; @@ -92,17 +94,22 @@ import com.cloud.network.router.CommandSetupHelper; import com.cloud.network.router.NetworkHelper; import com.cloud.network.vo.PublicIpQuarantineVO; +import com.cloud.network.vpc.Vpc; import com.cloud.network.vpc.VpcManager; import com.cloud.network.vpc.VpcVO; import com.cloud.network.vpc.dao.VpcDao; +import com.cloud.network.vpc.dao.VpcOfferingDao; import com.cloud.offering.NetworkOffering; import com.cloud.offering.ServiceOffering; import com.cloud.offerings.NetworkOfferingVO; import com.cloud.offerings.dao.NetworkOfferingDao; import com.cloud.offerings.dao.NetworkOfferingServiceMapDao; import com.cloud.org.Grouping; +import com.cloud.server.ResourceTag.ResourceObjectType; +import com.cloud.server.ResourceTag; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.tags.dao.ResourceTagDao; import com.cloud.user.Account; import com.cloud.user.AccountManager; import com.cloud.user.AccountService; @@ -203,6 +210,15 @@ public class NetworkServiceImplTest { private AccountVO accountVOMock; @Mock private DomainVO domainVOMock; + @Mock + ResourceTagDao resourceTagDao; + + @Mock + VpcOfferingDao vpcOfferingDao; + + @Mock + NetworkMigrationManager networkMigrationManager; + @InjectMocks NetworkServiceImpl service; @@ -1378,4 +1394,33 @@ public void getAndValidateSupportForKeepMacAddressOnPublicNicParameterTestReturn Assert.assertFalse(service.getAndValidateSupportForKeepMacAddressOnPublicNicParameter(false, networkOfferingVO)); } + + @Test + public void testMigrateVpcCopiesVpcDetailsFromOriginalToCopyBeforeDeletingTheOriginal() { + final long originalVpcId = 10L; + final long copyVpcId = 20L; + final long newVpcOfferingId = 7L; + + // resume of a previous run: the id received is the one of the copy, the migration tag holds the id of the original + ResourceTag migrationTag = Mockito.mock(ResourceTag.class); + Mockito.when(migrationTag.getValue()).thenReturn(String.valueOf(originalVpcId)); + Mockito.when(resourceTagDao.findByKey(copyVpcId, ResourceObjectType.Vpc, NetworkMigrationManager.MIGRATION)).thenReturn(migrationTag); + + VpcVO originalVpc = Mockito.mock(VpcVO.class); + Mockito.when(originalVpc.getVpcOfferingId()).thenReturn(5L); + Mockito.when(originalVpc.getZoneId()).thenReturn(1L); + VpcVO copyVpc = Mockito.mock(VpcVO.class); + Mockito.when(copyVpc.getVpcOfferingId()).thenReturn(newVpcOfferingId); + Mockito.when(vpcDao.findById(originalVpcId)).thenReturn(originalVpc); + Mockito.when(vpcDao.findById(copyVpcId)).thenReturn(copyVpc); + Mockito.when(networkDao.listByVpc(Mockito.anyLong())).thenReturn(Collections.emptyList()); + + Vpc result = service.migrateVpcNetwork(copyVpcId, newVpcOfferingId, new HashMap<>(), Mockito.mock(Account.class), Mockito.mock(User.class), true); + + Mockito.verify(networkMigrationManager, Mockito.never()).makeCopyOfVpc(Mockito.anyLong(), Mockito.anyLong()); + InOrder inOrder = Mockito.inOrder(networkMigrationManager); + inOrder.verify(networkMigrationManager).copyVpcDetails(originalVpcId, copyVpcId); + inOrder.verify(networkMigrationManager).deleteCopyOfVpc(originalVpcId, copyVpcId); + Assert.assertSame(copyVpc, result); + } } 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 2acad0c2b45d..3e9d318afa31 100644 --- a/server/src/test/java/com/cloud/network/vpc/VpcManagerImplTest.java +++ b/server/src/test/java/com/cloud/network/vpc/VpcManagerImplTest.java @@ -23,6 +23,7 @@ import com.cloud.agent.api.to.IpAddressTO; import com.cloud.agent.manager.Commands; import com.cloud.alert.AlertManager; +import com.cloud.configuration.ConfigurationManager; import com.cloud.dc.DataCenterVO; import com.cloud.dc.VlanVO; import com.cloud.dc.dao.DataCenterDao; @@ -50,6 +51,7 @@ import com.cloud.network.vpc.dao.NetworkACLDao; import com.cloud.network.vpc.dao.VpcDao; import com.cloud.network.vpc.dao.VpcOfferingDao; +import com.cloud.network.vpc.dao.VpcOfferingDetailsDao; import com.cloud.network.vpc.dao.VpcOfferingServiceMapDao; import com.cloud.offering.NetworkOffering; import com.cloud.offerings.NetworkOfferingServiceMapVO; @@ -69,6 +71,8 @@ import com.cloud.vm.dao.DomainRouterDao; import com.cloud.vm.dao.NicDao; import org.apache.cloudstack.acl.SecurityChecker; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.command.admin.vpc.CloneVPCOfferingCmd; import org.apache.cloudstack.api.command.admin.vpc.CreateVPCOfferingCmd; import org.apache.cloudstack.api.command.user.vpc.UpdateVPCCmd; import org.apache.cloudstack.context.CallContext; @@ -78,11 +82,13 @@ import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.network.Ipv4GuestSubnetNetworkMap; import org.apache.cloudstack.network.RoutedIpv4Manager; +import org.apache.cloudstack.resourcedetail.dao.VpcDetailsDao; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockedConstruction; import org.mockito.Mockito; @@ -171,6 +177,12 @@ public class VpcManagerImplTest { NetworkACLVO networkACLVOMock; @Mock RoutedIpv4Manager routedIpv4Manager; + @Mock + ConfigurationManager configMgr; + @Mock + VpcDetailsDao vpcDetailsDao; + @Mock + VpcOfferingDetailsDao vpcOfferingDetailsDao; public static final long ACCOUNT_ID = 1; private AccountVO account; @@ -230,6 +242,9 @@ public void setup() throws NoSuchFieldException, IllegalAccessException { manager._ntwkSvc = networkServiceMock; manager._firewallDao = firewallDao; manager._networkAclDao = networkACLDaoMock; + manager._configMgr = configMgr; + manager.vpcDetailsDao = vpcDetailsDao; + manager.vpcOfferingDetailsDao = vpcOfferingDetailsDao; manager.routedIpv4Manager = routedIpv4Manager; CallContext.register(Mockito.mock(User.class), Mockito.mock(Account.class)); registerCallContext(); @@ -486,6 +501,66 @@ public void testDisabledConfigCreateIpv6VpcOffering() { manager.createVpcOffering(cmd); } + @Test(expected = InvalidParameterValueException.class) + public void testCreateVpcOfferingRejectsNetworkRateBelowMinusOne() { + CreateVPCOfferingCmd cmd = Mockito.mock(CreateVPCOfferingCmd.class); + Mockito.when(cmd.getPublicNetworkRate()).thenReturn(-5); + manager.createVpcOffering(cmd); + } + + @Test + public void testRestartVpcWithoutCleanupDoesNotRefreshPublicNetworkRate() throws Exception { + final long vpcId = 5L; + Vpc vpc = Mockito.mock(Vpc.class); + Mockito.when(vpcDao.getActiveVpcById(vpcId)).thenReturn(vpc); + Mockito.when(vpc.isRedundant()).thenReturn(false); + User user = Mockito.mock(User.class); + Mockito.when(user.getAccountId()).thenReturn(1L); + Mockito.when(accountManager.getActiveAccountById(1L)).thenReturn(Mockito.mock(Account.class)); + Mockito.when(networkModel.listNetworksByVpc(vpcId)).thenReturn(Collections.emptyList()); + VpcVO vpcVO = Mockito.mock(VpcVO.class); + Mockito.when(vpcDao.findById(vpcId)).thenReturn(vpcVO); + VpcManagerImpl spyManager = Mockito.spy(manager); + doReturn(true).when(spyManager).startVpc(vpcId, false); + + Assert.assertTrue(spyManager.restartVpc(vpcId, false, false, false, user)); + + // the VR is not recreated, so its public NIC keeps the old rate: the stored rate must not change either + Mockito.verify(vpcDetailsDao, Mockito.never()).addDetail(Mockito.anyLong(), Mockito.anyString(), Mockito.anyString(), Mockito.anyBoolean()); + Mockito.verify(configMgr, Mockito.never()).getVpcOfferingNetworkRate(Mockito.anyLong(), Mockito.any()); + } + + private Integer createVpcOfferingAndCaptureStoredRate(Integer requestedRate) { + CreateVPCOfferingCmd cmd = Mockito.mock(CreateVPCOfferingCmd.class); + Mockito.when(cmd.getPublicNetworkRate()).thenReturn(requestedRate); + VpcManagerImpl spyManager = Mockito.spy(manager); + ArgumentCaptor rateCaptor = ArgumentCaptor.forClass(Integer.class); + doReturn(Mockito.mock(VpcOffering.class)).when(spyManager).createVpcOffering(any(), any(), any(), any(), any(), any(), any(), any(), any(), + any(), any(), any(), any(), anyBoolean(), anyBoolean(), rateCaptor.capture()); + spyManager.createVpcOffering(cmd); + return rateCaptor.getValue(); + } + + @Test + public void testCreateVpcOfferingStoresZeroNetworkRateAsUnlimited() { + Assert.assertEquals(Integer.valueOf(-1), createVpcOfferingAndCaptureStoredRate(0)); + } + + @Test + public void testCreateVpcOfferingStoresUnlimitedNetworkRate() { + Assert.assertEquals(Integer.valueOf(-1), createVpcOfferingAndCaptureStoredRate(-1)); + } + + @Test + public void testCreateVpcOfferingStoresPositiveNetworkRate() { + Assert.assertEquals(Integer.valueOf(200), createVpcOfferingAndCaptureStoredRate(200)); + } + + @Test + public void testCreateVpcOfferingKeepsNetworkRateUnsetWhenNotSpecified() { + Assert.assertNull(createVpcOfferingAndCaptureStoredRate(null)); + } + private void mockVpcDnsResources(boolean supportDnsService, boolean isIpv6) { Mockito.when(accountManager.getAccount(vpcOwnerId)).thenReturn(account); vpcOfferingVO = Mockito.mock(VpcOfferingVO.class); @@ -658,4 +733,93 @@ public void getVpcElementsReturnsEmptyListWhenNoStaticNorExtensionProviders() { Assert.assertTrue(result.isEmpty()); } + private VpcManagerImpl spyForRestartWithCleanup(long vpcId, long offeringId, long zoneId) { + Vpc vpc = Mockito.mock(Vpc.class); + Mockito.when(vpc.getId()).thenReturn(vpcId); + Mockito.when(vpc.getVpcOfferingId()).thenReturn(offeringId); + Mockito.when(vpc.getZoneId()).thenReturn(zoneId); + Mockito.when(vpc.isRedundant()).thenReturn(false); + Mockito.when(vpcDao.getActiveVpcById(vpcId)).thenReturn(vpc); + Mockito.when(accountManager.getActiveAccountById(1L)).thenReturn(Mockito.mock(Account.class)); + Mockito.when(vpcDao.findById(vpcId)).thenReturn(Mockito.mock(VpcVO.class)); + return Mockito.spy(manager); + } + + private User userOfAccount() { + User user = Mockito.mock(User.class); + Mockito.when(user.getAccountId()).thenReturn(1L); + return user; + } + + @Test + public void testRestartVpcWithCleanupRefreshesPublicNetworkRate() throws Exception { + final long vpcId = 5L; + VpcManagerImpl spyManager = spyForRestartWithCleanup(vpcId, 7L, 8L); + doReturn(true).when(spyManager).startVpc(any(Vpc.class), any(), any()); + Mockito.when(networkMgr.areRoutersRunning(any())).thenReturn(true); + Mockito.doNothing().when(spyManager).reconfigStaticNatForVpcVr(vpcId); + Mockito.when(configMgr.getVpcOfferingNetworkRate(7L, 8L)).thenReturn(150); + + Assert.assertTrue(spyManager.restartVpc(vpcId, true, false, false, userOfAccount())); + + Mockito.verify(vpcDetailsDao).addDetail(vpcId, ApiConstants.PUBLIC_NETWORK_RATE, "150", true); + } + + @Test + public void testRestartVpcWithCleanupStoresUnlimitedPublicNetworkRate() throws Exception { + final long vpcId = 5L; + VpcManagerImpl spyManager = spyForRestartWithCleanup(vpcId, 7L, 8L); + doReturn(true).when(spyManager).startVpc(any(Vpc.class), any(), any()); + Mockito.when(networkMgr.areRoutersRunning(any())).thenReturn(true); + Mockito.doNothing().when(spyManager).reconfigStaticNatForVpcVr(vpcId); + Mockito.when(configMgr.getVpcOfferingNetworkRate(7L, 8L)).thenReturn(-1); + + Assert.assertTrue(spyManager.restartVpc(vpcId, true, false, false, userOfAccount())); + + Mockito.verify(vpcDetailsDao).addDetail(vpcId, ApiConstants.PUBLIC_NETWORK_RATE, "-1", true); + } + + @Test + public void testRestartVpcWithFailedCleanupKeepsStoredPublicNetworkRate() throws Exception { + final long vpcId = 5L; + VpcManagerImpl spyManager = spyForRestartWithCleanup(vpcId, 7L, 8L); + doReturn(true).when(spyManager).startVpc(any(Vpc.class), any(), any()); + Mockito.when(networkMgr.areRoutersRunning(any())).thenReturn(false); + + Assert.assertFalse(spyManager.restartVpc(vpcId, true, false, false, userOfAccount())); + + Mockito.verify(vpcDetailsDao, Mockito.never()).addDetail(Mockito.anyLong(), Mockito.anyString(), Mockito.anyString(), Mockito.anyBoolean()); + } + + private CloneVPCOfferingCmd applyResolvedValuesWithSourceRate(CloneVPCOfferingCmd cmd, Integer sourceRate) { + VpcOfferingVO source = Mockito.mock(VpcOfferingVO.class); + Mockito.when(source.getPublicNetworkRate()).thenReturn(sourceRate); + ReflectionTestUtils.invokeMethod(manager, "applyResolvedValuesToCommand", cmd, source, + new ArrayList(), new HashMap>(), null, null, new HashMap()); + return cmd; + } + + @Test + public void testCloneVpcOfferingInheritsPublicNetworkRateFromSource() { + Assert.assertEquals(Integer.valueOf(80), applyResolvedValuesWithSourceRate(new CloneVPCOfferingCmd(), 80).getPublicNetworkRate()); + } + + @Test + public void testCloneVpcOfferingInheritsUnlimitedPublicNetworkRateFromSource() { + Assert.assertEquals(Integer.valueOf(-1), applyResolvedValuesWithSourceRate(new CloneVPCOfferingCmd(), -1).getPublicNetworkRate()); + } + + @Test + public void testCloneVpcOfferingKeepsRequestedPublicNetworkRate() { + CloneVPCOfferingCmd cmd = new CloneVPCOfferingCmd(); + ReflectionTestUtils.setField(cmd, "publicNetworkRate", 50); + + Assert.assertEquals(Integer.valueOf(50), applyResolvedValuesWithSourceRate(cmd, 80).getPublicNetworkRate()); + } + + @Test + public void testCloneVpcOfferingKeepsPublicNetworkRateUnsetWhenSourceHasNone() { + Assert.assertNull(applyResolvedValuesWithSourceRate(new CloneVPCOfferingCmd(), null).getPublicNetworkRate()); + } + } diff --git a/server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java b/server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java index a8d3927f910e..79cb9d6da76e 100644 --- a/server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java +++ b/server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java @@ -445,6 +445,12 @@ public Integer getServiceOfferingNetworkRate(long serviceOfferingId, Long dataCe return null; } + @Override + public Integer getVpcOfferingNetworkRate(long vpcOfferingId, Long dataCenterId) { + // TODO Auto-generated method stub + return null; + } + /* (non-Javadoc) * @see com.cloud.configuration.ConfigurationService#isOfferingForVpc(com.cloud.offering.NetworkOffering) */ diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 99bf2cf7aef9..fd444b1e7d21 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -2104,6 +2104,7 @@ "label.publicipid": "IP address ID", "label.publickey": "Public key", "label.publicnetwork": "Public Network", +"label.publicnetworkrate": "Public network rate (Mb/s)", "label.publicport": "Public port", "label.purgeresources": "Purge Resources", "label.purge.usage.records.success": "Successfully purged usage records", diff --git a/ui/src/components/view/DetailsTab.vue b/ui/src/components/view/DetailsTab.vue index d2aabacb10c9..49df1235d79c 100644 --- a/ui/src/components/view/DetailsTab.vue +++ b/ui/src/components/view/DetailsTab.vue @@ -147,6 +147,9 @@
{{ dataResource[item].join(', ') }}
+
+ {{ String(dataResource[item]) === '-1' ? $t('label.unlimited') : dataResource[item] }} +
{{ dataResource[item] }}
diff --git a/ui/src/components/view/ListView.vue b/ui/src/components/view/ListView.vue index 9a7d874fef3e..cf8f25aa93fb 100644 --- a/ui/src/components/view/ListView.vue +++ b/ui/src/components/view/ListView.vue @@ -424,6 +424,9 @@ {{ isNaN(text) ? text : (parseFloat(text) / 1024.0).toFixed(2) + ' GB' }} + diff --git a/ui/src/config/section/network.js b/ui/src/config/section/network.js index 1235dca8ceab..55f55f8c99c9 100644 --- a/ui/src/config/section/network.js +++ b/ui/src/config/section/network.js @@ -53,7 +53,7 @@ export default { 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', 'dnszone', 'dnssubdomain'] + 'dns1', 'dns2', 'ip6dns1', 'ip6dns2', 'publicmtu', 'privatemtu', 'networkrate', 'dnszone', 'dnssubdomain'] if (isAdmin()) { const vlanIndex = fields.findIndex(detail => detail === 'vlan') fields.splice(vlanIndex + 1, 0, 'broadcasturi') @@ -294,7 +294,7 @@ export default { return fields }, details: () => { - const fields = ['name', 'id', 'displaytext', 'cidr', 'networkdomain', 'ip4routing', 'ip4routes', 'ip6routes', 'ispersistent', 'redundantvpcrouter', 'restartrequired', 'zonename', 'account', 'domain', 'dns1', 'dns2', 'ip6dns1', 'ip6dns2', 'publicmtu'] + const fields = ['name', 'id', 'displaytext', 'cidr', 'networkdomain', 'ip4routing', 'ip4routes', 'ip6routes', 'ispersistent', 'redundantvpcrouter', 'restartrequired', 'zonename', 'account', 'domain', 'dns1', 'dns2', 'ip6dns1', 'ip6dns2', 'publicmtu', 'publicnetworkrate'] if (isAdmin()) { fields.push({ field: 'keepmacaddressonpublicnic', diff --git a/ui/src/config/section/offering.js b/ui/src/config/section/offering.js index d8d06eb2ccb2..5f803c6dc558 100644 --- a/ui/src/config/section/offering.js +++ b/ui/src/config/section/offering.js @@ -43,9 +43,9 @@ export default { } return [] }, - columns: ['name', 'displaytext', 'state', 'cpunumber', 'cpuspeed', 'memory', 'gpu', 'domain', 'zone', 'order'], + columns: ['name', 'displaytext', 'state', 'cpunumber', 'cpuspeed', 'memory', 'gpu', 'domain', 'zone', 'order', 'networkrate'], details: () => { - var fields = ['name', 'id', 'displaytext', 'offerha', 'provisioningtype', 'storagetype', 'iscustomized', 'iscustomizediops', 'limitcpuuse', 'cpunumber', 'cpuspeed', 'memory', 'hosttags', 'tags', 'storageaccessgroups', 'storagetags', 'domain', 'zone', 'created', 'dynamicscalingenabled', 'diskofferingstrictness', 'encryptroot', 'purgeresources', 'leaseduration', 'gpucardid', 'gpucardname', 'vgpuprofileid', 'vgpuprofilename', 'gpucount', 'gpudisplay', 'leaseexpiryaction', 'externaldetails'] + var fields = ['name', 'id', 'displaytext', 'offerha', 'provisioningtype', 'storagetype', 'iscustomized', 'iscustomizediops', 'limitcpuuse', 'cpunumber', 'cpuspeed', 'memory', 'hosttags', 'tags', 'storageaccessgroups', 'storagetags', 'domain', 'zone', 'created', 'dynamicscalingenabled', 'diskofferingstrictness', 'encryptroot', 'purgeresources', 'leaseduration', 'gpucardid', 'gpucardname', 'vgpuprofileid', 'vgpuprofilename', 'gpucount', 'gpudisplay', 'leaseexpiryaction', 'externaldetails', 'networkrate'] if (store.getters.apis.createServiceOffering && store.getters.apis.createServiceOffering.params.filter(x => x.name === 'storagepolicy').length > 0) { fields.splice(6, 0, 'vspherestoragepolicy') @@ -566,7 +566,7 @@ export default { searchFilters: ['name', 'zoneid', 'domainid'], resourceType: 'VpcOffering', columns: ['name', 'state', 'displaytext', 'domain', 'zone', 'order'], - details: ['name', 'id', 'displaytext', 'internetprotocol', 'distributedvpcrouter', 'tags', 'routingmode', 'specifyasnumber', 'service', 'fornsx', 'networkmode', 'conservemode', 'domain', 'zone', 'created'], + details: ['name', 'id', 'displaytext', 'internetprotocol', 'distributedvpcrouter', 'tags', 'routingmode', 'specifyasnumber', 'service', 'fornsx', 'networkmode', 'conservemode', 'publicnetworkrate', 'domain', 'zone', 'created'], related: [{ name: 'vpc', title: 'label.vpc', diff --git a/ui/src/views/network/NicsTable.vue b/ui/src/views/network/NicsTable.vue index 3a70eb3429c3..ef3cf8e63994 100644 --- a/ui/src/views/network/NicsTable.vue +++ b/ui/src/views/network/NicsTable.vue @@ -57,6 +57,9 @@ {{ record.isolationuri }} + + {{ String(record.networkrate) === '-1' ? $t('label.unlimited') : record.networkrate }} + {{ record.nicdnsname }} diff --git a/ui/src/views/offering/AddVpcOffering.vue b/ui/src/views/offering/AddVpcOffering.vue index 780da6638f8e..53f3b5452e76 100644 --- a/ui/src/views/offering/AddVpcOffering.vue +++ b/ui/src/views/offering/AddVpcOffering.vue @@ -202,6 +202,14 @@ + + + + @@ -366,6 +374,7 @@ export default { }) this.rules = reactive({ name: [{ required: true, message: this.$t('message.error.name') }], + publicnetworkrate: [{ type: 'number', validator: this.validateNetworkRate }], domainid: [{ type: 'array', required: true, message: this.$t('message.error.select') }], zoneid: [{ type: 'array', @@ -721,6 +730,9 @@ export default { } params.networkmode = values.networkmode params.conservemode = values.conservemode + if (values.publicnetworkrate) { + params.publicnetworkrate = values.publicnetworkrate + } if (!values.forVpc) { params.specifyasnumber = values.specifyasnumber } @@ -755,6 +767,12 @@ export default { }, closeAction () { this.$emit('close-action') + }, + async validateNetworkRate (rule, value) { + if (value && (isNaN(value) || value < -1)) { + return Promise.reject(this.$t('message.error.number')) + } + return Promise.resolve() } } }