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..87c99203e1be 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 = "4.24.0", + description = "Data transfer rate in megabits per second allowed for a VPC's public gateway (internet-facing network), created with this offering. Default is unlimited") + 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/command/admin/vpc/UpdateVPCOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/UpdateVPCOfferingCmd.java index 97f30f6fa2ef..201fc91da910 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/UpdateVPCOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/UpdateVPCOfferingCmd.java @@ -68,6 +68,11 @@ public class UpdateVPCOfferingCmd extends BaseAsyncCmd implements DomainAndZoneI @Parameter(name = ApiConstants.SORT_KEY, type = CommandType.INTEGER, description = "Sort key of the VPC offering, integer") private Integer sortKey; + @Parameter(name = ApiConstants.PUBLIC_NETWORK_RATE, type = CommandType.INTEGER, + since = "4.24.0", + description = "Data transfer rate in megabits per second allowed for a VPC's public gateway (internet-facing network), created with this offering. Use 0 for unlimited") + private Integer publicNetworkRate; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -100,6 +105,10 @@ public Integer getSortKey() { return sortKey; } + public Integer getPublicNetworkRate() { + return publicNetworkRate; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// 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..f1f9f6d18dea 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 = "4.24.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..843c21f5fd34 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 = "4.24.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..4ef9dde12fd5 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; null if not set (falls back to the zone/global default)", since = "4.24.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..4e3eb5c656f0 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 = "4.24.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/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..088f6b93d19b 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 @@ -138,6 +138,9 @@ public interface NetworkOrchestrationService { 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); + ConfigKey VpcPublicNetworkThrottlingRate = new ConfigKey<>("Network", Integer.class, "vpc.public.network.throttling.rate", "0", + "Default data transfer rate in megabits per second allowed for a VPC's public/internet-facing network. 0 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..7059c531b07d 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); @@ -5066,7 +5075,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/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..2f9c99136d1b --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/upgrade/NetworkRateBackfill.java @@ -0,0 +1,251 @@ +// 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.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.utils.db.TransactionLegacy; +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 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 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 = TransactionLegacy.currentTxn().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 = TransactionLegacy.currentTxn().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())) { + 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 -1; + } + } + return getNetworkOfferingNetworkRate(network.getNetworkOfferingId(), network.getDataCenterId()); + } + + // 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 = TransactionLegacy.currentTxn().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 = TransactionLegacy.currentTxn().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 = TransactionLegacy.currentTxn().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 = TransactionLegacy.currentTxn().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..472642a50a81 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().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..800386275f21 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` smallint unsigned 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/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..c60c5dba394c 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; @@ -2715,6 +2720,11 @@ public NetworkResponse createNetworkResponse(ResponseView view, Network network) response.setNetworkDomain(network.getNetworkDomain()); response.setPublicMtu(network.getPublicMtu()); response.setPrivateMtu(network.getPrivateMtu()); + NetworkDetailVO networkRateDetail = networkDetailsDao.findDetail(network.getId(), ApiConstants.NETWORKRATE); + if (networkRateDetail != null) { + int networkRate = NumberUtils.toInt(networkRateDetail.getValue(), -1); + response.setNetworkRate(networkRate > 0 ? networkRate : -1); + } response.setDns1(profile.getDns1()); response.setDns2(profile.getDns2()); response.setIpv6Dns1(profile.getIp6Dns1()); @@ -3591,6 +3601,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 +4918,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..858ef8b58d11 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 == null || pubNetworkRate <= 0 ) ? -1 : 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..fa27ce30fb6c 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; @@ -9064,6 +9065,32 @@ 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); + } + + // networkRate is unsigned int in vpc_offerings table, and can't be + // set to -1 + // so 0 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/NetworkMigrationManagerImpl.java b/server/src/main/java/com/cloud/network/NetworkMigrationManagerImpl.java index a09867b8ffc3..bc01b42b9fd7 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; @@ -484,6 +485,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..dd43cf16e0b3 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 { 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..9c7d5171ff6a 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,10 @@ public VpcOffering createVpcOffering(CreateVPCOfferingCmd cmd) { boolean specifyAsNumber = cmd.getSpecifyAsNumber(); String routingModeString = cmd.getRoutingMode(); boolean conserveMode = cmd.isConserveMode(); + Integer publicNetworkRate = cmd.getPublicNetworkRate(); + if (publicNetworkRate != null && publicNetworkRate < 0) { + throw new InvalidParameterValueException("Failed to create VPC offering " + vpcOfferingName + ": specify the public network rate value as 0 or more"); + } // check if valid domain if (CollectionUtils.isNotEmpty(cmd.getDomainIds())) { @@ -632,7 +641,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 +649,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 +750,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 +778,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 +796,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 +1105,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); @@ -1433,7 +1448,7 @@ public boolean deleteVpcOffering(final long offId) { @Override @ActionEvent(eventType = EventTypes.EVENT_VPC_OFFERING_UPDATE, eventDescription = "updating vpc offering") public VpcOffering updateVpcOffering(long vpcOffId, String vpcOfferingName, String displayText, String state) { - return updateVpcOfferingInternal(vpcOffId, vpcOfferingName, displayText, state, null, null, null); + return updateVpcOfferingInternal(vpcOffId, vpcOfferingName, displayText, state, null, null, null, null); } @Override @@ -1446,6 +1461,10 @@ public VpcOffering updateVpcOffering(final UpdateVPCOfferingCmd cmd) { final List domainIds = cmd.getDomainIds(); final List zoneIds = cmd.getZoneIds(); final Integer sortKey = cmd.getSortKey(); + final Integer publicNetworkRate = cmd.getPublicNetworkRate(); + if (publicNetworkRate != null && publicNetworkRate < 0) { + throw new InvalidParameterValueException("Failed to update VPC offering " + offeringId + ": specify the public network rate value as 0 or more"); + } // check if valid domain if (CollectionUtils.isNotEmpty(domainIds)) { @@ -1464,10 +1483,11 @@ public VpcOffering updateVpcOffering(final UpdateVPCOfferingCmd cmd) { } } - return updateVpcOfferingInternal(offeringId, vpcOfferingName, displayText, state, sortKey, domainIds, zoneIds); + return updateVpcOfferingInternal(offeringId, vpcOfferingName, displayText, state, sortKey, domainIds, zoneIds, publicNetworkRate); } - private VpcOffering updateVpcOfferingInternal(long vpcOffId, String vpcOfferingName, String displayText, String state, Integer sortKey, final List domainIds, final List zoneIds) { + private VpcOffering updateVpcOfferingInternal(long vpcOffId, String vpcOfferingName, String displayText, String state, Integer sortKey, final List domainIds, final List zoneIds, + Integer publicNetworkRate) { // Verify input parameters final VpcOfferingVO offeringToUpdate = _vpcOffDao.findById(vpcOffId); if (offeringToUpdate == null) { @@ -1492,7 +1512,7 @@ private VpcOffering updateVpcOfferingInternal(long vpcOffId, String vpcOfferingN } Collections.sort(filteredZoneIds); - final boolean updateNeeded = vpcOfferingName != null || displayText != null || state != null || sortKey != null; + final boolean updateNeeded = vpcOfferingName != null || displayText != null || state != null || sortKey != null || publicNetworkRate != null; final VpcOfferingVO offering = _vpcOffDao.createForUpdate(vpcOffId); @@ -1518,6 +1538,9 @@ private VpcOffering updateVpcOfferingInternal(long vpcOffId, String vpcOfferingN if (sortKey != null) { offering.setSortKey(sortKey); } + if (publicNetworkRate != null) { + offering.setPublicNetworkRate(publicNetworkRate); + } if (!_vpcOffDao.update(vpcOffId, offering)) { return null; @@ -1841,10 +1864,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); @@ -2810,6 +2839,7 @@ public boolean restartVpc(Long vpcId, boolean cleanUp, boolean makeRedundant, bo // the restart procedure. if (vpcDao.update(vpc.getId(), entity)) { vpc = entity; + saveVpcNetworkRateInDetails(vpc); } // If the offering and redundant column are changing, force the 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/vpc/VpcManagerImplTest.java b/server/src/test/java/com/cloud/network/vpc/VpcManagerImplTest.java index 2acad0c2b45d..507833bcfee2 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; @@ -70,6 +71,7 @@ import com.cloud.vm.dao.NicDao; import org.apache.cloudstack.acl.SecurityChecker; import org.apache.cloudstack.api.command.admin.vpc.CreateVPCOfferingCmd; +import org.apache.cloudstack.api.command.admin.vpc.UpdateVPCOfferingCmd; import org.apache.cloudstack.api.command.user.vpc.UpdateVPCCmd; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; @@ -78,6 +80,7 @@ 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; @@ -171,6 +174,10 @@ public class VpcManagerImplTest { NetworkACLVO networkACLVOMock; @Mock RoutedIpv4Manager routedIpv4Manager; + @Mock + ConfigurationManager configMgr; + @Mock + VpcDetailsDao vpcDetailsDao; public static final long ACCOUNT_ID = 1; private AccountVO account; @@ -230,6 +237,8 @@ public void setup() throws NoSuchFieldException, IllegalAccessException { manager._ntwkSvc = networkServiceMock; manager._firewallDao = firewallDao; manager._networkAclDao = networkACLDaoMock; + manager._configMgr = configMgr; + manager.vpcDetailsDao = vpcDetailsDao; manager.routedIpv4Manager = routedIpv4Manager; CallContext.register(Mockito.mock(User.class), Mockito.mock(Account.class)); registerCallContext(); @@ -486,6 +495,21 @@ public void testDisabledConfigCreateIpv6VpcOffering() { manager.createVpcOffering(cmd); } + @Test(expected = InvalidParameterValueException.class) + public void testCreateVpcOfferingRejectsNegativeNetworkRate() { + CreateVPCOfferingCmd cmd = Mockito.mock(CreateVPCOfferingCmd.class); + Mockito.when(cmd.getPublicNetworkRate()).thenReturn(-5); + manager.createVpcOffering(cmd); + } + + @Test(expected = InvalidParameterValueException.class) + public void testUpdateVpcOfferingRejectsNegativeNetworkRate() { + UpdateVPCOfferingCmd cmd = Mockito.mock(UpdateVPCOfferingCmd.class); + Mockito.when(cmd.getId()).thenReturn(1L); + Mockito.when(cmd.getPublicNetworkRate()).thenReturn(-5); + manager.updateVpcOffering(cmd); + } + private void mockVpcDnsResources(boolean supportDnsService, boolean isIpv6) { Mockito.when(accountManager.getAccount(vpcOwnerId)).thenReturn(account); vpcOfferingVO = Mockito.mock(VpcOfferingVO.class); 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..a150f92c3bdb 100644 --- a/ui/src/components/view/DetailsTab.vue +++ b/ui/src/components/view/DetailsTab.vue @@ -147,6 +147,12 @@
{{ dataResource[item].join(', ') }}
+
+ {{ String(dataResource[item]) === '-1' ? $t('label.unlimited') : dataResource[item] }} +
+
+ {{ ['-1', '0'].includes(String(dataResource[item])) ? $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..a5cdf1f55af3 100644 --- a/ui/src/components/view/ListView.vue +++ b/ui/src/components/view/ListView.vue @@ -424,6 +424,12 @@ {{ 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..513765ce40a6 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', @@ -597,7 +597,7 @@ export default { icon: 'edit-outlined', label: 'label.edit', dataView: true, - args: ['name', 'displaytext'] + args: ['name', 'displaytext', 'publicnetworkrate'] }, { api: 'updateVPCOffering', icon: 'play-circle-outlined', 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..11062bf93a58 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 < 0)) { + return Promise.reject(this.$t('message.error.number')) + } + return Promise.resolve() } } }