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 99e19ab4288..653b38e6ea3 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -213,6 +213,10 @@ public class ApiConstants { public static final String ID = "id"; public static final String IDS = "ids"; public static final String INDEX = "index"; + public static final String INSTANCES_DISKS_STATS_RETENTION_ENABLED = "instancesdisksstatsretentionenabled"; + public static final String INSTANCES_DISKS_STATS_RETENTION_TIME = "instancesdisksstatsretentiontime"; + public static final String INSTANCES_STATS_RETENTION_TIME = "instancesstatsretentiontime"; + public static final String INSTANCES_STATS_USER_ONLY = "instancesstatsuseronly"; public static final String PREFIX = "prefix"; public static final String PREVIOUS_ACL_RULE_ID = "previousaclruleid"; public static final String NEXT_ACL_RULE_ID = "nextaclruleid"; @@ -392,6 +396,7 @@ public class ApiConstants { public static final String START_IPV6 = "startipv6"; public static final String START_PORT = "startport"; public static final String STATE = "state"; + public static final String STATS = "stats"; public static final String STATUS = "status"; public static final String STORAGE_TYPE = "storagetype"; public static final String STORAGE_POLICY = "storagepolicy"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java index fa65101778c..4a2711e2092 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java @@ -19,6 +19,7 @@ package org.apache.cloudstack.api.command.user.config; import java.util.Map; import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.response.CapabilitiesResponse; import org.apache.cloudstack.config.ApiServiceConfiguration; @@ -65,6 +66,10 @@ public class ListCapabilitiesCmd extends BaseCmd { response.setApiLimitMax((Integer)capabilities.get("apiLimitMax")); } response.setDefaultUiPageSize((Long)capabilities.get(ApiServiceConfiguration.DefaultUIPageSize.key())); + response.setInstancesStatsRetentionTime((Integer) capabilities.get(ApiConstants.INSTANCES_STATS_RETENTION_TIME)); + response.setInstancesStatsUserOnly((Boolean) capabilities.get(ApiConstants.INSTANCES_STATS_USER_ONLY)); + response.setInstancesDisksStatsRetentionEnabled((Boolean) capabilities.get(ApiConstants.INSTANCES_DISKS_STATS_RETENTION_ENABLED)); + response.setInstancesDisksStatsRetentionTime((Integer) capabilities.get(ApiConstants.INSTANCES_DISKS_STATS_RETENTION_TIME)); response.setObjectName("capability"); response.setResponseName(getCommandName()); this.setResponseObject(response); diff --git a/api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java index e860baebe6d..d2f7c6b00d0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java @@ -104,6 +104,22 @@ public class CapabilitiesResponse extends BaseResponse { @Param(description = "default page size in the UI for various views, value set in the configurations", since = "4.15.2") private Long defaultUiPageSize; + @SerializedName(ApiConstants.INSTANCES_STATS_RETENTION_TIME) + @Param(description = "the retention time for Instances stats", since = "4.18.0") + private Integer instancesStatsRetentionTime; + + @SerializedName(ApiConstants.INSTANCES_STATS_USER_ONLY) + @Param(description = "true if stats are collected only for user instances, false if system instance stats are also collected", since = "4.18.0") + private Boolean instancesStatsUserOnly; + + @SerializedName(ApiConstants.INSTANCES_DISKS_STATS_RETENTION_ENABLED) + @Param(description = "true if stats are retained for instance disks otherwise false", since = "4.18.0") + private Boolean instancesDisksStatsRetentionEnabled; + + @SerializedName(ApiConstants.INSTANCES_DISKS_STATS_RETENTION_TIME) + @Param(description = "the retention time for Instances disks stats", since = "4.18.0") + private Integer instancesDisksStatsRetentionTime; + public void setSecurityGroupsEnabled(boolean securityGroupsEnabled) { this.securityGroupsEnabled = securityGroupsEnabled; } @@ -183,4 +199,20 @@ public class CapabilitiesResponse extends BaseResponse { public void setDefaultUiPageSize(Long defaultUiPageSize) { this.defaultUiPageSize = defaultUiPageSize; } + + public void setInstancesStatsRetentionTime(Integer instancesStatsRetentionTime) { + this.instancesStatsRetentionTime = instancesStatsRetentionTime; + } + + public void setInstancesStatsUserOnly(Boolean instancesStatsUserOnly) { + this.instancesStatsUserOnly = instancesStatsUserOnly; + } + + public void setInstancesDisksStatsRetentionEnabled(Boolean instancesDisksStatsRetentionEnabled) { + this.instancesDisksStatsRetentionEnabled = instancesDisksStatsRetentionEnabled; + } + + public void setInstancesDisksStatsRetentionTime(Integer instancesDisksStatsRetentionTime) { + this.instancesDisksStatsRetentionTime = instancesDisksStatsRetentionTime; + } } diff --git a/core/src/main/java/com/cloud/agent/api/VmDiskStatsEntry.java b/core/src/main/java/com/cloud/agent/api/VmDiskStatsEntry.java index ead1e80b414..9fbb46f37fe 100644 --- a/core/src/main/java/com/cloud/agent/api/VmDiskStatsEntry.java +++ b/core/src/main/java/com/cloud/agent/api/VmDiskStatsEntry.java @@ -30,6 +30,11 @@ public class VmDiskStatsEntry implements VmDiskStats { long bytesWrite = 0; long bytesRead = 0; + long deltaIoRead = 0; + long deltaIoWrite = 0; + long deltaBytesWrite = 0; + long deltaBytesRead = 0; + public VmDiskStatsEntry() { } @@ -94,4 +99,36 @@ public class VmDiskStatsEntry implements VmDiskStats { return ioWrite; } + public long getDeltaIoRead() { + return deltaIoRead; + } + + public void setDeltaIoRead(long deltaIoRead) { + this.deltaIoRead = deltaIoRead; + } + + public long getDeltaIoWrite() { + return deltaIoWrite; + } + + public void setDeltaIoWrite(long deltaIoWrite) { + this.deltaIoWrite = deltaIoWrite; + } + + public long getDeltaBytesWrite() { + return deltaBytesWrite; + } + + public void setDeltaBytesWrite(long deltaBytesWrite) { + this.deltaBytesWrite = deltaBytesWrite; + } + + public long getDeltaBytesRead() { + return deltaBytesRead; + } + + public void setDeltaBytesRead(long deltaBytesRead) { + this.deltaBytesRead = deltaBytesRead; + } + } diff --git a/core/src/main/java/com/cloud/agent/api/VmStatsEntry.java b/core/src/main/java/com/cloud/agent/api/VmStatsEntry.java index 9c792b88254..54721a8a10a 100644 --- a/core/src/main/java/com/cloud/agent/api/VmStatsEntry.java +++ b/core/src/main/java/com/cloud/agent/api/VmStatsEntry.java @@ -19,11 +19,9 @@ package com.cloud.agent.api; -import com.cloud.vm.UserVmVO; - public class VmStatsEntry extends VmStatsEntryBase { - private UserVmVO userVmVO; + private String vmUuid; public VmStatsEntry() { @@ -52,12 +50,12 @@ public class VmStatsEntry extends VmStatsEntryBase { entityType); } - public UserVmVO getUserVmVO() { - return userVmVO; + public String getVmUuid() { + return vmUuid; } - public void setUserVmVO(UserVmVO userVmVO) { - this.userVmVO = userVmVO; + public void setVmUuid(String vmUuid) { + this.vmUuid = vmUuid; } } diff --git a/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java b/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java index 35c36d35cd9..4ffb30b493a 100644 --- a/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java +++ b/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java @@ -17,6 +17,7 @@ package com.cloud.vm; import java.net.URI; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -263,4 +264,25 @@ public interface VirtualMachineManager extends Manager { Pair findClusterAndHostIdForVm(long vmId); + /** + * Obtains statistics for a list of VMs; CPU and network utilization + * @param hostId ID of the host + * @param hostName name of the host + * @param vmIds list of VM IDs + * @return map of VM ID and stats entry for the VM + */ + HashMap getVirtualMachineStatistics(long hostId, String hostName, List vmIds); + /** + * Obtains statistics for a list of VMs; CPU and network utilization + * @param hostId ID of the host + * @param hostName name of the host + * @param vmMap map of VM IDs and the corresponding VirtualMachine object + * @return map of VM ID and stats entry for the VM + */ + HashMap getVirtualMachineStatistics(long hostId, String hostName, Map vmMap); + + HashMap> getVmDiskStatistics(long hostId, String hostName, Map vmMap); + + HashMap> getVmNetworkStatistics(long hostId, String hostName, Map vmMap); + } diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java index 7d441c70b75..d2cdc3da579 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -17,6 +17,8 @@ package com.cloud.vm; +import static com.cloud.configuration.ConfigurationManagerImpl.MIGRATE_VM_ACROSS_CLUSTERS; + import java.net.URI; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -45,7 +47,6 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; import javax.persistence.EntityExistsException; -import com.cloud.storage.VolumeApiServiceImpl; import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.dao.AnnotationDao; @@ -98,6 +99,12 @@ import com.cloud.agent.api.CheckVirtualMachineCommand; import com.cloud.agent.api.ClusterVMMetaDataSyncAnswer; import com.cloud.agent.api.ClusterVMMetaDataSyncCommand; import com.cloud.agent.api.Command; +import com.cloud.agent.api.GetVmDiskStatsAnswer; +import com.cloud.agent.api.GetVmDiskStatsCommand; +import com.cloud.agent.api.GetVmNetworkStatsAnswer; +import com.cloud.agent.api.GetVmNetworkStatsCommand; +import com.cloud.agent.api.GetVmStatsAnswer; +import com.cloud.agent.api.GetVmStatsCommand; import com.cloud.agent.api.MigrateCommand; import com.cloud.agent.api.MigrateVmToPoolAnswer; import com.cloud.agent.api.ModifyTargetsCommand; @@ -122,6 +129,9 @@ import com.cloud.agent.api.StopCommand; import com.cloud.agent.api.UnPlugNicAnswer; import com.cloud.agent.api.UnPlugNicCommand; import com.cloud.agent.api.UnregisterVMCommand; +import com.cloud.agent.api.VmDiskStatsEntry; +import com.cloud.agent.api.VmNetworkStatsEntry; +import com.cloud.agent.api.VmStatsEntry; import com.cloud.agent.api.routing.NetworkElementCommand; import com.cloud.agent.api.to.DiskTO; import com.cloud.agent.api.to.DpdkTO; @@ -208,6 +218,7 @@ import com.cloud.storage.VMTemplateVO; import com.cloud.storage.Volume; import com.cloud.storage.Volume.Type; import com.cloud.storage.VolumeApiService; +import com.cloud.storage.VolumeApiServiceImpl; import com.cloud.storage.VolumeVO; import com.cloud.storage.dao.DiskOfferingDao; import com.cloud.storage.dao.GuestOSCategoryDao; @@ -254,8 +265,6 @@ import com.cloud.vm.snapshot.VMSnapshotManager; import com.cloud.vm.snapshot.VMSnapshotVO; import com.cloud.vm.snapshot.dao.VMSnapshotDao; -import static com.cloud.configuration.ConfigurationManagerImpl.MIGRATE_VM_ACROSS_CLUSTERS; - public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMachineManager, VmWorkJobHandler, Listener, Configurable { private static final Logger s_logger = Logger.getLogger(VirtualMachineManagerImpl.class); @@ -5825,4 +5834,98 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac assert vm != null; return vm; } + + @Override + public HashMap getVirtualMachineStatistics(long hostId, String hostName, List vmIds) { + HashMap vmStatsById = new HashMap<>(); + if (CollectionUtils.isEmpty(vmIds)) { + return vmStatsById; + } + Map vmMap = new HashMap<>(); + for (Long vmId : vmIds) { + vmMap.put(vmId, _vmDao.findById(vmId)); + } + return getVirtualMachineStatistics(hostId, hostName, vmMap); + } + + @Override + public HashMap getVirtualMachineStatistics(long hostId, String hostName, Map vmMap) { + HashMap vmStatsById = new HashMap<>(); + if (MapUtils.isEmpty(vmMap)) { + return vmStatsById; + } + Map vmNames = new HashMap<>(); + for (Map.Entry vmEntry : vmMap.entrySet()) { + vmNames.put(vmEntry.getValue().getInstanceName(), vmEntry.getKey()); + } + Answer answer = _agentMgr.easySend(hostId, new GetVmStatsCommand(new ArrayList<>(vmNames.keySet()), _hostDao.findById(hostId).getGuid(), hostName)); + if (answer == null || !answer.getResult()) { + s_logger.warn("Unable to obtain VM statistics."); + return vmStatsById; + } else { + HashMap vmStatsByName = ((GetVmStatsAnswer)answer).getVmStatsMap(); + if (vmStatsByName == null) { + s_logger.warn("Unable to obtain VM statistics."); + return vmStatsById; + } + for (Map.Entry entry : vmStatsByName.entrySet()) { + vmStatsById.put(vmNames.get(entry.getKey()), entry.getValue()); + } + } + return vmStatsById; + } + + @Override + public HashMap> getVmDiskStatistics(long hostId, String hostName, Map vmMap) { + HashMap> vmDiskStatsById = new HashMap<>(); + if (MapUtils.isEmpty(vmMap)) { + return vmDiskStatsById; + } + Map vmNames = new HashMap<>(); + for (Map.Entry vmEntry : vmMap.entrySet()) { + vmNames.put(vmEntry.getValue().getInstanceName(), vmEntry.getKey()); + } + Answer answer = _agentMgr.easySend(hostId, new GetVmDiskStatsCommand(new ArrayList<>(vmNames.keySet()), _hostDao.findById(hostId).getGuid(), hostName)); + if (answer == null || !answer.getResult()) { + s_logger.warn("Unable to obtain VM disk statistics."); + return vmDiskStatsById; + } else { + HashMap> vmDiskStatsByName = ((GetVmDiskStatsAnswer)answer).getVmDiskStatsMap(); + if (vmDiskStatsByName == null) { + s_logger.warn("Unable to obtain VM disk statistics."); + return vmDiskStatsById; + } + for (Map.Entry> entry: vmDiskStatsByName.entrySet()) { + vmDiskStatsById.put(vmNames.get(entry.getKey()), entry.getValue()); + } + } + return vmDiskStatsById; + } + + @Override + public HashMap> getVmNetworkStatistics(long hostId, String hostName, Map vmMap) { + HashMap> vmNetworkStatsById = new HashMap<>(); + if (MapUtils.isEmpty(vmMap)) { + return vmNetworkStatsById; + } + Map vmNames = new HashMap<>(); + for (Map.Entry vmEntry : vmMap.entrySet()) { + vmNames.put(vmEntry.getValue().getInstanceName(), vmEntry.getKey()); + } + Answer answer = _agentMgr.easySend(hostId, new GetVmNetworkStatsCommand(new ArrayList<>(vmNames.keySet()), _hostDao.findById(hostId).getGuid(), hostName)); + if (answer == null || !answer.getResult()) { + s_logger.warn("Unable to obtain VM network statistics."); + return vmNetworkStatsById; + } else { + HashMap> vmNetworkStatsByName = ((GetVmNetworkStatsAnswer)answer).getVmNetworkStatsMap(); + if (vmNetworkStatsByName == null) { + s_logger.warn("Unable to obtain VM network statistics."); + return vmNetworkStatsById; + } + for (Map.Entry> entry: vmNetworkStatsByName.entrySet()) { + vmNetworkStatsById.put(vmNames.get(entry.getKey()), entry.getValue()); + } + } + return vmNetworkStatsById; + } } diff --git a/engine/schema/src/main/java/com/cloud/storage/VolumeStatsVO.java b/engine/schema/src/main/java/com/cloud/storage/VolumeStatsVO.java new file mode 100644 index 00000000000..2d181742123 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/storage/VolumeStatsVO.java @@ -0,0 +1,86 @@ +// 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.storage; + +import java.util.Date; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; + +@Entity +@Table(name = "volume_stats") +public class VolumeStatsVO { + + @Id + @Column(name = "id", updatable = false, nullable = false) + protected long id; + + @Column(name = "volume_id", updatable = false, nullable = false) + protected Long volumeId; + + @Column(name = "mgmt_server_id", updatable = false, nullable = false) + protected Long mgmtServerId; + + @Column(name= "timestamp", updatable = false) + @Temporal(value = TemporalType.TIMESTAMP) + protected Date timestamp; + + @Column(name = "volume_stats_data", updatable = false, nullable = false, length = 65535) + protected String volumeStatsData; + + public VolumeStatsVO(Long volumeId, Long mgmtServerId, Date timestamp, String volumeStatsData) { + this.volumeId = volumeId; + this.mgmtServerId = mgmtServerId; + this.timestamp = timestamp; + this.volumeStatsData = volumeStatsData; + } + + public VolumeStatsVO() { + + } + + public long getId() { + return id; + } + + public Long getVolumeId() { + return volumeId; + } + + public Long getMgmtServerId() { + return mgmtServerId; + } + + public Date getTimestamp() { + return timestamp; + } + + public String getVolumeStatsData() { + return volumeStatsData; + } + + @Override + public String toString() { + return ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "vmId", "mgmtServerId", "timestamp", "volumeStatsData"); + } +} diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeStatsDao.java b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeStatsDao.java new file mode 100644 index 00000000000..7eb6c025446 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeStatsDao.java @@ -0,0 +1,82 @@ +// 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.storage.dao; + +import java.util.Date; +import java.util.List; + +import com.cloud.utils.db.GenericDao; +import com.cloud.storage.VolumeStatsVO; + +/** + * Data Access Object for volume_stats table. + */ +public interface VolumeStatsDao extends GenericDao { + + /** + * Finds Volume stats by Volume ID. + * @param volumeId the Volume ID. + * @return list of stats for the specified Volume. + */ + List findByVolumeId(long volumeId); + + /** + * Finds Volume stats by Volume ID. The result is sorted by timestamp in descending order. + * @param volumeId the Volume ID. + * @return ordered list of stats for the specified Volume. + */ + List findByVolumeIdOrderByTimestampDesc(long volumeId); + + /** + * Finds stats by Volume ID and timestamp >= a given time. + * @param volumeId the specific Volume. + * @param time the specific time. + * @return list of stats for the specified Volume, with timestamp >= the specified time. + */ + List findByVolumeIdAndTimestampGreaterThanEqual(long volumeId, Date time); + + /** + * Finds stats by Volume ID and timestamp <= a given time. + * @param volumeId the specific Volume. + * @param time the specific time. + * @return list of stats for the specified Volume, with timestamp <= the specified time. + */ + List findByVolumeIdAndTimestampLessThanEqual(long volumeId, Date time); + + /** + * Finds stats by Volume ID and timestamp between a given time range. + * @param volumeId the specific Volume. + * @param startTime the start time. + * @param endTime the start time. + * @return list of stats for the specified Volume, between the specified start and end times. + */ + List findByVolumeIdAndTimestampBetween(long volumeId, Date startTime, Date endTime); + + /** + * Removes (expunges) all stats of the specified Volume. + * @param volumeId the Volume ID to remove stats. + */ + void removeAllByVolumeId(long volumeId); + + /** + * Removes (expunges) all Volume stats with {@code timestamp} less than + * a given Date. + * @param limit the maximum date to keep stored. Records that exceed this limit will be removed. + */ + void removeAllByTimestampLessThan(Date limit); + +} \ No newline at end of file diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeStatsDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeStatsDaoImpl.java new file mode 100644 index 00000000000..5d0d3c8921c --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeStatsDaoImpl.java @@ -0,0 +1,124 @@ +// 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.storage.dao; + +import java.util.Date; +import java.util.List; + +import javax.annotation.PostConstruct; + +import org.springframework.stereotype.Component; + +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.SearchCriteria.Op; +import com.cloud.storage.VolumeStatsVO; + +@Component +public class VolumeStatsDaoImpl extends GenericDaoBase implements VolumeStatsDao { + + protected SearchBuilder volumeIdSearch; + protected SearchBuilder volumeIdTimestampGreaterThanEqualSearch; + protected SearchBuilder volumeIdTimestampLessThanEqualSearch; + protected SearchBuilder volumeIdTimestampBetweenSearch; + protected SearchBuilder timestampSearch; + + private final static String VOLUME_ID = "volumeId"; + private final static String TIMESTAMP = "timestamp"; + + @PostConstruct + protected void init() { + volumeIdSearch = createSearchBuilder(); + volumeIdSearch.and(VOLUME_ID, volumeIdSearch.entity().getVolumeId(), Op.EQ); + volumeIdSearch.done(); + + volumeIdTimestampGreaterThanEqualSearch = createSearchBuilder(); + volumeIdTimestampGreaterThanEqualSearch.and(VOLUME_ID, volumeIdTimestampGreaterThanEqualSearch.entity().getVolumeId(), Op.EQ); + volumeIdTimestampGreaterThanEqualSearch.and(TIMESTAMP, volumeIdTimestampGreaterThanEqualSearch.entity().getTimestamp(), Op.GTEQ); + volumeIdTimestampGreaterThanEqualSearch.done(); + + volumeIdTimestampLessThanEqualSearch = createSearchBuilder(); + volumeIdTimestampLessThanEqualSearch.and(VOLUME_ID, volumeIdTimestampLessThanEqualSearch.entity().getVolumeId(), Op.EQ); + volumeIdTimestampLessThanEqualSearch.and(TIMESTAMP, volumeIdTimestampLessThanEqualSearch.entity().getTimestamp(), Op.LTEQ); + volumeIdTimestampLessThanEqualSearch.done(); + + volumeIdTimestampBetweenSearch = createSearchBuilder(); + volumeIdTimestampBetweenSearch.and(VOLUME_ID, volumeIdTimestampBetweenSearch.entity().getVolumeId(), Op.EQ); + volumeIdTimestampBetweenSearch.and(TIMESTAMP, volumeIdTimestampBetweenSearch.entity().getTimestamp(), Op.BETWEEN); + volumeIdTimestampBetweenSearch.done(); + + timestampSearch = createSearchBuilder(); + timestampSearch.and(TIMESTAMP, timestampSearch.entity().getTimestamp(), Op.LT); + timestampSearch.done(); + + } + + @Override + public List findByVolumeId(long volumeId) { + SearchCriteria sc = volumeIdSearch.create(); + sc.setParameters(VOLUME_ID, volumeId); + return listBy(sc); + } + + @Override + public List findByVolumeIdOrderByTimestampDesc(long volumeId) { + SearchCriteria sc = volumeIdSearch.create(); + sc.setParameters(VOLUME_ID, volumeId); + Filter orderByFilter = new Filter(VolumeStatsVO.class, TIMESTAMP, false, null, null); + return search(sc, orderByFilter, null, false); + } + + @Override + public List findByVolumeIdAndTimestampGreaterThanEqual(long volumeId, Date time) { + SearchCriteria sc = volumeIdTimestampGreaterThanEqualSearch.create(); + sc.setParameters(VOLUME_ID, volumeId); + sc.setParameters(TIMESTAMP, time); + return listBy(sc); + } + + @Override + public List findByVolumeIdAndTimestampLessThanEqual(long volumeId, Date time) { + SearchCriteria sc = volumeIdTimestampLessThanEqualSearch.create(); + sc.setParameters(VOLUME_ID, volumeId); + sc.setParameters(TIMESTAMP, time); + return listBy(sc); + } + + @Override + public List findByVolumeIdAndTimestampBetween(long volumeId, Date startTime, Date endTime) { + SearchCriteria sc = volumeIdTimestampBetweenSearch.create(); + sc.setParameters(VOLUME_ID, volumeId); + sc.setParameters(TIMESTAMP, startTime, endTime); + return listBy(sc); + } + + @Override + public void removeAllByVolumeId(long volumeId) { + SearchCriteria sc = volumeIdSearch.create(); + sc.setParameters(VOLUME_ID, volumeId); + expunge(sc); + } + + @Override + public void removeAllByTimestampLessThan(Date limit) { + SearchCriteria sc = timestampSearch.create(); + sc.setParameters(TIMESTAMP, limit); + expunge(sc); + } +} diff --git a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml index cec86d2d7be..ebda8235fa4 100644 --- a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml +++ b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml @@ -226,6 +226,7 @@ + diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41720to41800.sql b/engine/schema/src/main/resources/META-INF/db/schema-41720to41800.sql index 34f138c7540..cc400143fb2 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41720to41800.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41720to41800.sql @@ -912,6 +912,17 @@ SET description = "Use SSL method used to encrypt copy traffic between zones generating links for external access." WHERE name = 'secstorage.encrypt.copy'; +-- Create table to persist volume stats. +DROP TABLE IF EXISTS `cloud`.`volume_stats`; +CREATE TABLE `cloud`.`volume_stats` ( + `id` bigint unsigned NOT NULL auto_increment COMMENT 'id', + `volume_id` bigint unsigned NOT NULL, + `mgmt_server_id` bigint unsigned NOT NULL, + `timestamp` datetime NOT NULL, + `volume_stats_data` text NOT NULL, + PRIMARY KEY(`id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + -- allow isolated networks without services to be used as is. UPDATE `cloud`.`networks` ntwk SET ntwk.state = 'Implemented' diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index 11f19142833..178f67d3da2 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -16,6 +16,8 @@ // under the License. package com.cloud.hypervisor.kvm.resource; +import static com.cloud.host.Host.HOST_VOLUME_ENCRYPTION; + import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; @@ -31,6 +33,7 @@ import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.Comparator; +import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -53,7 +56,6 @@ import org.apache.cloudstack.storage.to.TemplateObjectTO; import org.apache.cloudstack.storage.to.VolumeObjectTO; import org.apache.cloudstack.utils.bytescale.ByteScaleUtils; import org.apache.cloudstack.utils.cryptsetup.CryptSetup; - import org.apache.cloudstack.utils.hypervisor.HypervisorUtils; import org.apache.cloudstack.utils.linux.CPUStat; import org.apache.cloudstack.utils.linux.KVMHostInfo; @@ -86,8 +88,8 @@ import org.libvirt.MemoryStatistic; import org.libvirt.Network; import org.libvirt.SchedParameter; import org.libvirt.SchedUlongParameter; -import org.libvirt.VcpuInfo; import org.libvirt.Secret; +import org.libvirt.VcpuInfo; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -188,18 +190,17 @@ import com.cloud.utils.NumbersUtil; import com.cloud.utils.Pair; import com.cloud.utils.PropertiesUtil; import com.cloud.utils.Ternary; +import com.cloud.utils.UuidUtils; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.NetUtils; import com.cloud.utils.script.OutputInterpreter; import com.cloud.utils.script.OutputInterpreter.AllLinesParser; import com.cloud.utils.script.Script; import com.cloud.utils.ssh.SshHelper; -import com.cloud.utils.UuidUtils; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine.PowerState; import com.cloud.vm.VmDetailConstants; - -import static com.cloud.host.Host.HOST_VOLUME_ENCRYPTION; +import com.google.gson.Gson; /** * LibvirtComputingResource execute requests on the computing/routing host using @@ -417,6 +418,8 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv private final Map _pifs = new HashMap(); private final Map _vmStats = new ConcurrentHashMap(); + private final Map vmDiskStats = new ConcurrentHashMap<>(); + protected static final HashMap s_powerStatesTable; static { s_powerStatesTable = new HashMap(); @@ -448,6 +451,8 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv protected LibvirtDomainXMLParser parser = new LibvirtDomainXMLParser(); + private static Gson gson = new Gson(); + /** * Virsh command to set the memory balloon stats period.

* 1st parameter: the VM ID or name;
@@ -4035,7 +4040,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv try { dm = getDomain(conn, vmName); - final List stats = new ArrayList(); + final List stats = new ArrayList<>(); final List disks = getDisks(conn, vmName); @@ -4047,7 +4052,27 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv String diskPath = getDiskPathFromDiskDef(disk); if (diskPath != null) { final VmDiskStatsEntry stat = new VmDiskStatsEntry(vmName, diskPath, blockStats.wr_req, blockStats.rd_req, blockStats.wr_bytes, blockStats.rd_bytes); + final DomainBlockStats oldStats = vmDiskStats.get(String.format("%s-%s", vmName, diskPath)); + if (oldStats != null) { + final long deltaiord = blockStats.rd_req - oldStats.rd_req; + if (deltaiord > 0) { + stat.setDeltaIoRead(deltaiord); + } + final long deltaiowr = blockStats.wr_req - oldStats.wr_req; + if (deltaiowr > 0) { + stat.setDeltaIoWrite(deltaiowr); + } + final long deltabytesrd = blockStats.rd_bytes - oldStats.rd_bytes; + if (deltabytesrd > 0) { + stat.setDeltaBytesRead(deltabytesrd); + } + final long deltabyteswr = blockStats.wr_bytes - oldStats.wr_bytes; + if (deltabyteswr > 0) { + stat.setDeltaBytesWrite(deltabyteswr); + } + } stats.add(stat); + vmDiskStats.put(String.format("%s-%s", vmName, diskPath), blockStats); } } @@ -4155,6 +4180,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv continue; } final DomainBlockStats blockStats = dm.blockStats(disk.getDiskLabel()); + s_logger.info(String.format("STATS_LOG getVm****Stat @ %s: Disk: %s---------------%s", new Date(), disk.getDiskLabel(), gson.toJson(blockStats))); io_rd += blockStats.rd_req; io_wr += blockStats.wr_req; bytes_rd += blockStats.rd_bytes; diff --git a/plugins/metrics/src/main/java/org/apache/cloudstack/api/BaseResourceUsageHistoryCmd.java b/plugins/metrics/src/main/java/org/apache/cloudstack/api/BaseResourceUsageHistoryCmd.java new file mode 100644 index 00000000000..815852d11c7 --- /dev/null +++ b/plugins/metrics/src/main/java/org/apache/cloudstack/api/BaseResourceUsageHistoryCmd.java @@ -0,0 +1,54 @@ +// 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; + +import java.util.Date; + +import javax.inject.Inject; + +import org.apache.cloudstack.metrics.MetricsService; + +public abstract class BaseResourceUsageHistoryCmd extends BaseListCmd { + + @Inject + protected MetricsService metricsService; + + // /////////////////////////////////////////////////// + // /// BaseResourceUsageHistoryCmd API parameters //// + // /////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.START_DATE, type = CommandType.DATE, description = "start date to filter stats." + + "Use format \"yyyy-MM-dd hh:mm:ss\")") + private Date startDate; + + @Parameter(name = ApiConstants.END_DATE, type = CommandType.DATE, description = "end date to filter stats." + + "Use format \"yyyy-MM-dd hh:mm:ss\")") + private Date endDate; + + // /////////////////////////////////////////////////// + // ///////////////// Accessors /////////////////////// + // /////////////////////////////////////////////////// + + public Date getStartDate() { + return startDate; + } + + public Date getEndDate() { + return endDate; + } +} diff --git a/plugins/metrics/src/main/java/org/apache/cloudstack/api/ListSystemVMsUsageHistoryCmd.java b/plugins/metrics/src/main/java/org/apache/cloudstack/api/ListSystemVMsUsageHistoryCmd.java new file mode 100644 index 00000000000..e2d3af24aef --- /dev/null +++ b/plugins/metrics/src/main/java/org/apache/cloudstack/api/ListSystemVMsUsageHistoryCmd.java @@ -0,0 +1,71 @@ +// 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; + +import java.util.List; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.response.VmMetricsStatsResponse; + +@APICommand(name = "listSystemVmsUsageHistory", description = "Lists System VM stats", responseObject = VmMetricsStatsResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.18.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin}) +public class ListSystemVMsUsageHistoryCmd extends BaseResourceUsageHistoryCmd { + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = UserVmResponse.class, description = "the ID of the system VM.") + private Long id; + + @Parameter(name=ApiConstants.IDS, type=CommandType.LIST, collectionType=CommandType.UUID, entityType=UserVmResponse.class, description="the IDs of the system VMs, mutually exclusive with id.") + private List ids; + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "name of the system VMs (a substring match is made against the parameter value returning the data for all matching VMs).") + private String name; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + public List getIds() { + return ids; + } + + public String getName() { + return name; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + ListResponse response = metricsService.searchForSystemVmMetricsStats(this); + response.setResponseName(getCommandName()); + setResponseObject(response); + } +} diff --git a/plugins/metrics/src/main/java/org/apache/cloudstack/api/ListVMsUsageHistoryCmd.java b/plugins/metrics/src/main/java/org/apache/cloudstack/api/ListVMsUsageHistoryCmd.java index 2b2fa310d2e..860c130fe92 100644 --- a/plugins/metrics/src/main/java/org/apache/cloudstack/api/ListVMsUsageHistoryCmd.java +++ b/plugins/metrics/src/main/java/org/apache/cloudstack/api/ListVMsUsageHistoryCmd.java @@ -17,24 +17,17 @@ package org.apache.cloudstack.api; -import java.util.Date; import java.util.List; -import javax.inject.Inject; - import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.UserVmResponse; -import org.apache.cloudstack.metrics.MetricsService; import org.apache.cloudstack.response.VmMetricsStatsResponse; @APICommand(name = "listVirtualMachinesUsageHistory", description = "Lists VM stats", responseObject = VmMetricsStatsResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.17", authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) -public class ListVMsUsageHistoryCmd extends BaseListCmd { - - @Inject - private MetricsService metricsService; +public class ListVMsUsageHistoryCmd extends BaseResourceUsageHistoryCmd { ///////////////////////////////////////////////////// //////////////// API parameters ///////////////////// @@ -49,14 +42,6 @@ public class ListVMsUsageHistoryCmd extends BaseListCmd { @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "name of the virtual machine (a substring match is made against the parameter value returning the data for all matching VMs).") private String name; - @Parameter(name = ApiConstants.START_DATE, type = CommandType.DATE, description = "start date to filter VM stats." - + "Use format \"yyyy-MM-dd hh:mm:ss\")") - private Date startDate; - - @Parameter(name = ApiConstants.END_DATE, type = CommandType.DATE, description = "end date to filter VM stats." - + "Use format \"yyyy-MM-dd hh:mm:ss\")") - private Date endDate; - ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -73,14 +58,6 @@ public class ListVMsUsageHistoryCmd extends BaseListCmd { return name; } - public Date getStartDate() { - return startDate; - } - - public Date getEndDate() { - return endDate; - } - ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/plugins/metrics/src/main/java/org/apache/cloudstack/api/ListVolumesUsageHistoryCmd.java b/plugins/metrics/src/main/java/org/apache/cloudstack/api/ListVolumesUsageHistoryCmd.java new file mode 100644 index 00000000000..4e9191a16f8 --- /dev/null +++ b/plugins/metrics/src/main/java/org/apache/cloudstack/api/ListVolumesUsageHistoryCmd.java @@ -0,0 +1,72 @@ +// 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; + +import java.util.List; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.SystemVmResponse; +import org.apache.cloudstack.api.response.VolumeResponse; +import org.apache.cloudstack.response.VolumeMetricsStatsResponse; + +@APICommand(name = "listVolumesUsageHistory", description = "Lists volume stats", responseObject = VolumeMetricsStatsResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.18.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class ListVolumesUsageHistoryCmd extends BaseResourceUsageHistoryCmd { + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = VolumeResponse.class, description = "the ID of the volume.") + private Long id; + + @Parameter(name=ApiConstants.IDS, type=CommandType.LIST, collectionType=CommandType.UUID, entityType= SystemVmResponse.class, description="the IDs of the volumes, mutually exclusive with id.") + private List ids; + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "name of the volume (a substring match is made against the parameter value returning the data for all matching Volumes).") + private String name; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + public List getIds() { + return ids; + } + + public String getName() { + return name; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + ListResponse response = metricsService.searchForVolumeMetricsStats(this); + response.setResponseName(getCommandName()); + setResponseObject(response); + } +} diff --git a/plugins/metrics/src/main/java/org/apache/cloudstack/metrics/MetricsService.java b/plugins/metrics/src/main/java/org/apache/cloudstack/metrics/MetricsService.java index ee67fe86ba7..48033dd7538 100644 --- a/plugins/metrics/src/main/java/org/apache/cloudstack/metrics/MetricsService.java +++ b/plugins/metrics/src/main/java/org/apache/cloudstack/metrics/MetricsService.java @@ -17,10 +17,11 @@ package org.apache.cloudstack.metrics; -import com.cloud.utils.Pair; -import com.cloud.utils.component.PluggableService; +import java.util.List; +import org.apache.cloudstack.api.ListSystemVMsUsageHistoryCmd; import org.apache.cloudstack.api.ListVMsUsageHistoryCmd; +import org.apache.cloudstack.api.ListVolumesUsageHistoryCmd; import org.apache.cloudstack.api.response.ClusterResponse; import org.apache.cloudstack.api.response.HostResponse; import org.apache.cloudstack.api.response.ListResponse; @@ -39,14 +40,18 @@ import org.apache.cloudstack.response.UsageServerMetricsResponse; import org.apache.cloudstack.response.VmMetricsResponse; import org.apache.cloudstack.response.VmMetricsStatsResponse; import org.apache.cloudstack.response.VolumeMetricsResponse; +import org.apache.cloudstack.response.VolumeMetricsStatsResponse; import org.apache.cloudstack.response.ZoneMetricsResponse; -import java.util.List; +import com.cloud.utils.Pair; +import com.cloud.utils.component.PluggableService; public interface MetricsService extends PluggableService { InfrastructureResponse listInfrastructure(); ListResponse searchForVmMetricsStats(ListVMsUsageHistoryCmd cmd); + ListResponse searchForSystemVmMetricsStats(ListSystemVMsUsageHistoryCmd cmd); + ListResponse searchForVolumeMetricsStats(ListVolumesUsageHistoryCmd cmd); List listVolumeMetrics(List volumeResponses); List listVmMetrics(List vmResponses); List listStoragePoolMetrics(List poolResponses); diff --git a/plugins/metrics/src/main/java/org/apache/cloudstack/metrics/MetricsServiceImpl.java b/plugins/metrics/src/main/java/org/apache/cloudstack/metrics/MetricsServiceImpl.java index 079a2d4fc85..7d1f74ed924 100644 --- a/plugins/metrics/src/main/java/org/apache/cloudstack/metrics/MetricsServiceImpl.java +++ b/plugins/metrics/src/main/java/org/apache/cloudstack/metrics/MetricsServiceImpl.java @@ -37,11 +37,13 @@ import org.apache.cloudstack.api.ListHostsMetricsCmd; import org.apache.cloudstack.api.ListInfrastructureCmd; import org.apache.cloudstack.api.ListMgmtsMetricsCmd; import org.apache.cloudstack.api.ListStoragePoolsMetricsCmd; +import org.apache.cloudstack.api.ListSystemVMsUsageHistoryCmd; import org.apache.cloudstack.api.ListUsageServerMetricsCmd; import org.apache.cloudstack.api.ListVMsMetricsCmd; import org.apache.cloudstack.api.ListVMsMetricsCmdByAdmin; import org.apache.cloudstack.api.ListVMsUsageHistoryCmd; import org.apache.cloudstack.api.ListVolumesMetricsCmd; +import org.apache.cloudstack.api.ListVolumesUsageHistoryCmd; import org.apache.cloudstack.api.ListZonesMetricsCmd; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.response.ClusterResponse; @@ -66,9 +68,11 @@ import org.apache.cloudstack.response.UsageServerMetricsResponse; import org.apache.cloudstack.response.VmMetricsResponse; import org.apache.cloudstack.response.VmMetricsStatsResponse; import org.apache.cloudstack.response.VolumeMetricsResponse; +import org.apache.cloudstack.response.VolumeMetricsStatsResponse; import org.apache.cloudstack.response.ZoneMetricsResponse; import org.apache.cloudstack.storage.datastore.db.ImageStoreDao; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.utils.bytescale.ByteScaleUtils; import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; @@ -76,6 +80,7 @@ import org.apache.commons.lang3.builder.ReflectionToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.log4j.Logger; +import com.cloud.agent.api.VmDiskStatsEntry; import com.cloud.agent.api.VmStatsEntryBase; import com.cloud.alert.AlertManager; import com.cloud.alert.dao.AlertDao; @@ -105,6 +110,10 @@ import com.cloud.org.Managed; import com.cloud.server.DbStatsCollection; import com.cloud.server.ManagementServerHostStats; import com.cloud.server.StatsCollector; +import com.cloud.storage.VolumeStatsVO; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.storage.dao.VolumeStatsDao; import com.cloud.usage.UsageJobVO; import com.cloud.usage.dao.UsageJobDao; import com.cloud.user.Account; @@ -162,6 +171,10 @@ public class MetricsServiceImpl extends MutualExclusiveIdsManagerBase implements protected VmStatsDao vmStatsDao; @Inject private UsageJobDao usageJobDao; + @Inject + private VolumeDao volumeDao; + @Inject + private VolumeStatsDao volumeStatsDao; private static Gson gson = new Gson(); @@ -197,8 +210,34 @@ public class MetricsServiceImpl extends MutualExclusiveIdsManagerBase implements @Override public ListResponse searchForVmMetricsStats(ListVMsUsageHistoryCmd cmd) { Pair, Integer> userVmList = searchForUserVmsInternal(cmd); - Map> vmStatsList = searchForVmMetricsStatsInternal(cmd, userVmList.first()); - return createVmMetricsStatsResponse(userVmList, vmStatsList); + Map> vmStatsList = searchForVmMetricsStatsInternal(cmd.getStartDate(), cmd.getEndDate(), userVmList.first()); + return createVmMetricsStatsResponse(userVmList.first(), vmStatsList); + } + + /** + * Searches for VM stats based on the {@code ListVMsUsageHistoryCmd} parameters. + * + * @param cmd the {@link ListVMsUsageHistoryCmd} specifying what should be searched. + * @return the list of VM metrics stats found. + */ + @Override + public ListResponse searchForSystemVmMetricsStats(ListSystemVMsUsageHistoryCmd cmd) { + Pair, Integer> vmList = searchForSystemVmsInternal(cmd); + Map> vmStatsList = searchForVmMetricsStatsInternal(cmd.getStartDate(), cmd.getEndDate(), vmList.first()); + return createVmMetricsStatsResponse(vmList.first(), vmStatsList); + } + + /** + * Searches for Volume stats based on the {@code ListVolumesUsageHistoryCmd} parameters. + * + * @param cmd the {@link ListVolumesUsageHistoryCmd} specifying what should be searched. + * @return the list of VM metrics stats found. + */ + @Override + public ListResponse searchForVolumeMetricsStats(ListVolumesUsageHistoryCmd cmd) { + Pair, Integer> volumeList = searchForVolumesInternal(cmd); + Map> volumeStatsList = searchForVolumeMetricsStatsInternal(cmd, volumeList.first()); + return createVolumeMetricsStatsResponse(volumeList, volumeStatsList); } /** @@ -235,23 +274,109 @@ public class MetricsServiceImpl extends MutualExclusiveIdsManagerBase implements return userVmDao.searchAndCount(sc, searchFilter); } + /** + * Searches System VMs based on {@code ListSystemVMsUsageHistoryCmd} parameters. + * + * @param cmd the {@link ListSystemVMsUsageHistoryCmd} specifying the parameters. + * @return the list of VMs. + */ + protected Pair, Integer> searchForSystemVmsInternal(ListSystemVMsUsageHistoryCmd cmd) { + Filter searchFilter = new Filter(VMInstanceVO.class, "id", true, cmd.getStartIndex(), cmd.getPageSizeVal()); + List ids = getIdsListFromCmd(cmd.getId(), cmd.getIds()); + String keyword = cmd.getKeyword(); + + SearchBuilder sb = vmInstanceDao.createSearchBuilder(); + sb.and("idIN", sb.entity().getId(), SearchCriteria.Op.IN); + sb.and("name", sb.entity().getName(), SearchCriteria.Op.LIKE); + sb.and("state", sb.entity().getState(), SearchCriteria.Op.EQ); + sb.and("type", sb.entity().getType(), SearchCriteria.Op.NEQ); + + SearchCriteria sc = sb.create(); + sc.setParameters("type", VirtualMachine.Type.User.toString()); + if (CollectionUtils.isNotEmpty(ids)) { + sc.setParameters("idIN", ids.toArray()); + } + if (StringUtils.isNotBlank(keyword)) { + SearchCriteria ssc = vmInstanceDao.createSearchCriteria(); + ssc.addOr("name", SearchCriteria.Op.LIKE, "%" + keyword + "%"); + ssc.addOr("state", SearchCriteria.Op.EQ, keyword); + sc.addAnd("name", SearchCriteria.Op.SC, ssc); + } + + return vmInstanceDao.searchAndCount(sc, searchFilter); + } + /** * Searches stats for a list of VMs, based on date filtering parameters. * - * @param cmd the {@link ListVMsUsageHistoryCmd} specifying the filtering parameters. - * @param userVmList the list of VMs for which stats should be searched. + * @param startDate the start date for which stats should be searched. + * @param endDate the end date for which stats should be searched. + * @param vmList the list of VMs for which stats should be searched. * @return the key-value map in which keys are VM IDs and values are lists of VM stats. */ - protected Map> searchForVmMetricsStatsInternal(ListVMsUsageHistoryCmd cmd, List userVmList) { - Map> vmStatsVOList = new HashMap>(); + protected Map> searchForVmMetricsStatsInternal(Date startDate, Date endDate, List vmList) { + Map> vmStatsVOList = new HashMap<>(); + validateDateParams(startDate, endDate); + + for (VMInstanceVO vmInstanceVO : vmList) { + Long vmId = vmInstanceVO.getId(); + vmStatsVOList.put(vmId, findVmStatsAccordingToDateParams(vmId, startDate, endDate)); + } + + return vmStatsVOList; + } + + /** + * Searches Volumes based on {@code ListVolumesUsageHistoryCmd} parameters. + * + * @param cmd the {@link ListVolumesUsageHistoryCmd} specifying the parameters. + * @return the list of VMs. + */ + protected Pair, Integer> searchForVolumesInternal(ListVolumesUsageHistoryCmd cmd) { + Filter searchFilter = new Filter(VolumeVO.class, "id", true, cmd.getStartIndex(), cmd.getPageSizeVal()); + List ids = getIdsListFromCmd(cmd.getId(), cmd.getIds()); + String name = cmd.getName(); + String keyword = cmd.getKeyword(); + + SearchBuilder sb = volumeDao.createSearchBuilder(); + sb.and("idIN", sb.entity().getId(), SearchCriteria.Op.IN); + sb.and("name", sb.entity().getName(), SearchCriteria.Op.LIKE); + sb.and("state", sb.entity().getState(), SearchCriteria.Op.EQ); + + SearchCriteria sc = sb.create(); + if (CollectionUtils.isNotEmpty(ids)) { + sc.setParameters("idIN", ids.toArray()); + } + if (StringUtils.isNotBlank(name)) { + sc.setParameters("name", "%" + name + "%"); + } + if (StringUtils.isNotBlank(keyword)) { + SearchCriteria ssc = volumeDao.createSearchCriteria(); + ssc.addOr("name", SearchCriteria.Op.LIKE, "%" + keyword + "%"); + ssc.addOr("state", SearchCriteria.Op.EQ, keyword); + sc.addAnd("name", SearchCriteria.Op.SC, ssc); + } + + return volumeDao.searchAndCount(sc, searchFilter); + } + + /** + * Searches stats for a list of Volumes, based on date filtering parameters. + * + * @param cmd the {@link ListVolumesUsageHistoryCmd} specifying the filtering parameters. + * @param volumeList the list of Volumes for which stats should be searched. + * @return the key-value map in which keys are Volume IDs and values are lists of Volume stats. + */ + protected Map> searchForVolumeMetricsStatsInternal(ListVolumesUsageHistoryCmd cmd, List volumeList) { + Map> vmStatsVOList = new HashMap<>(); Date startDate = cmd.getStartDate(); Date endDate = cmd.getEndDate(); validateDateParams(startDate, endDate); - for (UserVmVO userVmVO : userVmList) { - Long vmId = userVmVO.getId(); - vmStatsVOList.put(vmId, findVmStatsAccordingToDateParams(vmId, startDate, endDate)); + for (VolumeVO volumeVO : volumeList) { + Long volumeId = volumeVO.getId(); + vmStatsVOList.put(volumeId, findVolumeStatsAccordingToDateParams(volumeId, startDate, endDate)); } return vmStatsVOList; @@ -295,21 +420,22 @@ public class MetricsServiceImpl extends MutualExclusiveIdsManagerBase implements * Creates a {@code ListResponse}. For each VM, this joins essential VM info * with its respective list of stats. * - * @param userVmList the list of VMs. + * @param vmList the list of VMs. * @param vmStatsList the respective list of stats. * @return the list of responses that was created. */ - protected ListResponse createVmMetricsStatsResponse(Pair, Integer> userVmList, + protected ListResponse createVmMetricsStatsResponse(List vmList, Map> vmStatsList) { - List responses = new ArrayList(); - for (UserVmVO userVmVO : userVmList.first()) { + List responses = new ArrayList<>(); + for (VMInstanceVO vmVO : vmList) { VmMetricsStatsResponse vmMetricsStatsResponse = new VmMetricsStatsResponse(); vmMetricsStatsResponse.setObjectName("virtualmachine"); - vmMetricsStatsResponse.setId(userVmVO.getUuid()); - vmMetricsStatsResponse.setName(userVmVO.getName()); - vmMetricsStatsResponse.setDisplayName(userVmVO.getDisplayName()); - - vmMetricsStatsResponse.setStats(createStatsResponse(vmStatsList.get(userVmVO.getId()))); + vmMetricsStatsResponse.setId(vmVO.getUuid()); + vmMetricsStatsResponse.setName(vmVO.getName()); + if (vmVO instanceof UserVmVO) { + vmMetricsStatsResponse.setDisplayName(((UserVmVO) vmVO).getDisplayName()); + } + vmMetricsStatsResponse.setStats(createStatsResponse(vmStatsList.get(vmVO.getId()))); responses.add(vmMetricsStatsResponse); } @@ -318,6 +444,7 @@ public class MetricsServiceImpl extends MutualExclusiveIdsManagerBase implements return response; } + /** * Creates a {@code Set} from a given {@code List}. * @@ -351,6 +478,75 @@ public class MetricsServiceImpl extends MutualExclusiveIdsManagerBase implements return statsResponseList; } + /** + * Finds stats for a specific Volume based on date parameters. + * + * @param volumeId the specific Volume. + * @param startDate the start date to filtering. + * @param endDate the end date to filtering. + * @return the list of stats for the specified Volume. + */ + protected List findVolumeStatsAccordingToDateParams(Long volumeId, Date startDate, Date endDate){ + if (startDate != null && endDate != null) { + return volumeStatsDao.findByVolumeIdAndTimestampBetween(volumeId, startDate, endDate); + } + if (startDate != null) { + return volumeStatsDao.findByVolumeIdAndTimestampGreaterThanEqual(volumeId, startDate); + } + if (endDate != null) { + return volumeStatsDao.findByVolumeIdAndTimestampLessThanEqual(volumeId, endDate); + } + return volumeStatsDao.findByVolumeId(volumeId); + } + + /** + * Creates a {@code ListResponse}. For each VM, this joins essential VM info + * with its respective list of stats. + * + * @param volumeList the list of VMs. + * @param volumeStatsList the respective list of stats. + * @return the list of responses that was created. + */ + protected ListResponse createVolumeMetricsStatsResponse(Pair, Integer> volumeList, + Map> volumeStatsList) { + List responses = new ArrayList<>(); + for (VolumeVO volumeVO : volumeList.first()) { + VolumeMetricsStatsResponse volumeMetricsStatsResponse = new VolumeMetricsStatsResponse(); + volumeMetricsStatsResponse.setObjectName("volume"); + volumeMetricsStatsResponse.setId(volumeVO.getUuid()); + volumeMetricsStatsResponse.setName(volumeVO.getName()); + + volumeMetricsStatsResponse.setStats(createVolumeStatsResponse(volumeStatsList.get(volumeVO.getId()))); + responses.add(volumeMetricsStatsResponse); + } + + ListResponse response = new ListResponse<>(); + response.setResponses(responses); + return response; + } + + + /** + * Creates a {@code Set} from a given {@code List}. + * + * @param volumeStatsList the list of VM stats. + * @return the set of responses that was created. + */ + protected List createVolumeStatsResponse(List volumeStatsList) { + List statsResponseList = new ArrayList<>(); + for (VolumeStatsVO volumeStats : volumeStatsList) { + StatsResponse response = new StatsResponse(); + response.setTimestamp(volumeStats.getTimestamp()); + VmDiskStatsEntry statsEntry = gson.fromJson(volumeStats.getVolumeStatsData(), VmDiskStatsEntry.class); + response.setDiskKbsRead(ByteScaleUtils.bytesToKibibytes(statsEntry.getBytesRead())); + response.setDiskKbsWrite(ByteScaleUtils.bytesToKibibytes(statsEntry.getBytesWrite())); + response.setDiskIORead(statsEntry.getIORead()); + response.setDiskIOWrite(statsEntry.getIOWrite()); + statsResponseList.add(response); + } + return statsResponseList; + } + @Override public InfrastructureResponse listInfrastructure() { @@ -879,7 +1075,8 @@ public class MetricsServiceImpl extends MutualExclusiveIdsManagerBase implements cmdList.add(ListVolumesMetricsCmd.class); cmdList.add(ListZonesMetricsCmd.class); cmdList.add(ListVMsUsageHistoryCmd.class); - + cmdList.add(ListSystemVMsUsageHistoryCmd.class); + cmdList.add(ListVolumesUsageHistoryCmd.class); // separate Admin commands cmdList.add(ListVMsMetricsCmdByAdmin.class); return cmdList; diff --git a/plugins/metrics/src/main/java/org/apache/cloudstack/response/VolumeMetricsStatsResponse.java b/plugins/metrics/src/main/java/org/apache/cloudstack/response/VolumeMetricsStatsResponse.java new file mode 100644 index 00000000000..598f78a53d2 --- /dev/null +++ b/plugins/metrics/src/main/java/org/apache/cloudstack/response/VolumeMetricsStatsResponse.java @@ -0,0 +1,54 @@ +// 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.response; + +import java.util.List; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.response.StatsResponse; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +public class VolumeMetricsStatsResponse extends BaseResponse { + @SerializedName(ApiConstants.ID) + @Param(description = "the ID of the volume") + private String id; + + @SerializedName(ApiConstants.NAME) + @Param(description = "the name of the volume") + private String name; + + @SerializedName("stats") + @Param(description = "the list of VM stats") + private List stats; + + public void setId(String id) { + this.id = id; + } + + public void setName(String name) { + this.name = name; + } + + public void setStats(List stats) { + this.stats = stats; + } + +} diff --git a/plugins/metrics/src/test/java/org/apache/cloudstack/metrics/MetricsServiceImplTest.java b/plugins/metrics/src/test/java/org/apache/cloudstack/metrics/MetricsServiceImplTest.java index ddc4d8b30bf..ec4add4141d 100644 --- a/plugins/metrics/src/test/java/org/apache/cloudstack/metrics/MetricsServiceImplTest.java +++ b/plugins/metrics/src/test/java/org/apache/cloudstack/metrics/MetricsServiceImplTest.java @@ -44,6 +44,7 @@ import com.cloud.utils.Pair; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.vm.UserVmVO; +import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VmStatsVO; import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.VmStatsDao; @@ -84,10 +85,10 @@ public class MetricsServiceImplTest { ArgumentCaptor opCaptor; long fakeVmId1 = 1L, fakeVmId2 = 2L; - Pair, Integer> expectedVmListAndCounter; + Pair, Integer> expectedVmListAndCounter; @Mock - Pair, Integer> expectedVmListAndCounterMock; + Pair, Integer> expectedVmListAndCounterMock; @Mock Map> vmStatsMapMock; @@ -100,7 +101,7 @@ public class MetricsServiceImplTest { } private void preparesearchForUserVmsInternalTest() { - expectedVmListAndCounter = new Pair, Integer>(Arrays.asList(userVmVOMock), 1); + expectedVmListAndCounter = new Pair<>(Arrays.asList(userVmVOMock), 1); Mockito.doReturn(1L).when(listVMsUsageHistoryCmdMock).getStartIndex(); Mockito.doReturn(2L).when(listVMsUsageHistoryCmdMock).getPageSizeVal(); @@ -196,9 +197,11 @@ public class MetricsServiceImplTest { Mockito.doReturn(fakeVmId1).when(userVmVOMock).getId(); Map> expected = new HashMap>(); expected.put(fakeVmId1, new ArrayList()); + Date startDate = Mockito.mock(Date.class); + Date endDate = Mockito.mock(Date.class); Map> result = spy.searchForVmMetricsStatsInternal( - listVMsUsageHistoryCmdMock, Arrays.asList(userVmVOMock)); + startDate, endDate, Arrays.asList(userVmVOMock)); Mockito.verify(userVmVOMock).getId(); Mockito.verify(spy).findVmStatsAccordingToDateParams( @@ -210,9 +213,10 @@ public class MetricsServiceImplTest { public void searchForVmMetricsStatsInternalTestWithAnEmptyListOfVms() { Mockito.doNothing().when(spy).validateDateParams(Mockito.any(), Mockito.any()); Map> expected = new HashMap>(); - + Date startDate = Mockito.mock(Date.class); + Date endDate = Mockito.mock(Date.class); Map> result = spy.searchForVmMetricsStatsInternal( - listVMsUsageHistoryCmdMock, new ArrayList()); + startDate, endDate, new ArrayList()); Mockito.verify(userVmVOMock, Mockito.never()).getId(); Mockito.verify(spy, Mockito.never()).findVmStatsAccordingToDateParams( @@ -287,7 +291,7 @@ public class MetricsServiceImplTest { Mockito.doReturn(null).when(spy).createStatsResponse(Mockito.any()); ListResponse result = spy.createVmMetricsStatsResponse( - expectedVmListAndCounterMock, vmStatsMapMock); + expectedVmListAndCounterMock.first(), vmStatsMapMock); Assert.assertEquals(Integer.valueOf(1), result.getCount()); } @@ -299,7 +303,7 @@ public class MetricsServiceImplTest { @Test(expected = NullPointerException.class) public void createVmMetricsStatsResponseTestWithNoVmStatsList() { - spy.createVmMetricsStatsResponse(expectedVmListAndCounterMock, null); + spy.createVmMetricsStatsResponse(expectedVmListAndCounterMock.first(), null); } @Test diff --git a/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java b/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java index bd522b23891..29cc4cead0b 100644 --- a/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java +++ b/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java @@ -37,15 +37,6 @@ import java.util.concurrent.TimeUnit; import javax.inject.Inject; -import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.collections.MapUtils; -import org.apache.commons.lang3.RandomStringUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.log4j.Logger; - -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; - import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.affinity.AffinityGroupVO; import org.apache.cloudstack.affinity.dao.AffinityGroupDao; @@ -79,6 +70,11 @@ import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.managed.context.ManagedContextRunnable; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Logger; import com.cloud.agent.AgentManager; import com.cloud.agent.api.PerformanceMonitorAnswer; @@ -86,9 +82,9 @@ import com.cloud.agent.api.PerformanceMonitorCommand; import com.cloud.agent.api.VmStatsEntry; import com.cloud.agent.api.routing.GetAutoScaleMetricsAnswer; import com.cloud.agent.api.routing.GetAutoScaleMetricsCommand; -import com.cloud.agent.api.to.LoadBalancerTO.AutoScaleVmProfileTO; -import com.cloud.agent.api.to.LoadBalancerTO.AutoScaleVmGroupTO; import com.cloud.agent.api.to.LoadBalancerTO.AutoScalePolicyTO; +import com.cloud.agent.api.to.LoadBalancerTO.AutoScaleVmGroupTO; +import com.cloud.agent.api.to.LoadBalancerTO.AutoScaleVmProfileTO; import com.cloud.agent.api.to.LoadBalancerTO.ConditionTO; import com.cloud.agent.api.to.LoadBalancerTO.CounterTO; import com.cloud.api.ApiDBUtils; @@ -173,9 +169,9 @@ import com.cloud.utils.db.GenericSearchBuilder; import com.cloud.utils.db.JoinBuilder; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; -import com.cloud.utils.db.TransactionCallback; import com.cloud.utils.db.SearchCriteria.Op; import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallback; import com.cloud.utils.db.TransactionStatus; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.NetUtils; @@ -185,10 +181,14 @@ import com.cloud.vm.UserVmService; import com.cloud.vm.UserVmVO; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachineManager; import com.cloud.vm.VmDetailConstants; +import com.cloud.vm.VmStats; import com.cloud.vm.dao.DomainRouterDao; import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.VMInstanceDao; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; public class AutoScaleManagerImpl extends ManagerBase implements AutoScaleManager, AutoScaleService, Configurable { private static final Logger s_logger = Logger.getLogger(AutoScaleManagerImpl.class); @@ -273,6 +273,8 @@ public class AutoScaleManagerImpl extends ManagerBase implements AutoScaleManage private AffinityGroupDao affinityGroupDao; @Inject private NetworkOfferingDao networkOfferingDao; + @Inject + private VirtualMachineManager virtualMachineManager; private static final String PARAM_ROOT_DISK_SIZE = "rootdisksize"; private static final String PARAM_DISK_OFFERING_ID = "diskofferingid"; @@ -2653,21 +2655,21 @@ public class AutoScaleManagerImpl extends ManagerBase implements AutoScaleManage List vmIds = hostAndVmIds.getValue(); if (!DEFAULT_HOST_ID.equals(hostId)) { - Map vmStatsById = getVmStatsByIdFromHost(hostId, vmIds); + Map vmStatsById = getVmStatsByIdFromHost(hostId, vmIds); processVmStatsByIdFromHost(groupTO, vmIds, vmStatsById, policyCountersMap); } } } - protected Map getVmStatsByIdFromHost(Long hostId, List vmIds) { - Map vmStatsById = new HashMap<>(); + protected Map getVmStatsByIdFromHost(Long hostId, List vmIds) { + Map vmStatsById = new HashMap<>(); HostVO host = hostDao.findById(hostId); if (host == null) { s_logger.debug("Failed to get VM stats from non-existing host : " + hostId); return vmStatsById; } try { - vmStatsById = userVmMgr.getVirtualMachineStatistics(host.getId(), host.getName(), vmIds); + vmStatsById = virtualMachineManager.getVirtualMachineStatistics(host.getId(), host.getName(), vmIds); if (MapUtils.isEmpty(vmStatsById)) { s_logger.warn("Got empty result for virtual machine statistics from host: " + host); } @@ -2677,10 +2679,10 @@ public class AutoScaleManagerImpl extends ManagerBase implements AutoScaleManage return vmStatsById; } - protected void processVmStatsByIdFromHost(AutoScaleVmGroupTO groupTO, List vmIds, Map vmStatsById, Map> policyCountersMap) { + protected void processVmStatsByIdFromHost(AutoScaleVmGroupTO groupTO, List vmIds, Map vmStatsById, Map> policyCountersMap) { Date timestamp = new Date(); for (Long vmId : vmIds) { - VmStatsEntry vmStats = vmStatsById == null ? null : vmStatsById.get(vmId); + VmStatsEntry vmStats = vmStatsById == null ? null : (VmStatsEntry)vmStatsById.get(vmId); for (Map.Entry> policyCounters : policyCountersMap.entrySet()) { Long policyId = policyCounters.getKey(); List counters = policyCounters.getValue(); diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java b/server/src/main/java/com/cloud/server/ManagementServerImpl.java index 9922c275ed7..d2375ef8d1c 100644 --- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java +++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java @@ -16,6 +16,9 @@ // under the License. package com.cloud.server; +import static com.cloud.configuration.ConfigurationManagerImpl.VM_USERDATA_MAX_LENGTH; +import static com.cloud.vm.UserVmManager.MAX_USER_DATA_LENGTH_BYTES; + import java.io.UnsupportedEncodingException; import java.lang.reflect.Field; import java.net.URLDecoder; @@ -45,202 +48,6 @@ import javax.crypto.spec.SecretKeySpec; import javax.inject.Inject; import javax.naming.ConfigurationException; -import com.cloud.agent.AgentManager; -import com.cloud.agent.api.Answer; -import com.cloud.agent.api.Command; -import com.cloud.agent.api.GetVncPortAnswer; -import com.cloud.agent.api.GetVncPortCommand; -import com.cloud.agent.api.PatchSystemVmAnswer; -import com.cloud.agent.api.PatchSystemVmCommand; -import com.cloud.agent.api.proxy.AllowConsoleAccessCommand; -import com.cloud.agent.api.routing.NetworkElementCommand; -import com.cloud.agent.manager.Commands; -import com.cloud.agent.manager.allocator.HostAllocator; -import com.cloud.alert.Alert; -import com.cloud.alert.AlertManager; -import com.cloud.alert.AlertVO; -import com.cloud.alert.dao.AlertDao; -import com.cloud.api.ApiDBUtils; -import com.cloud.api.query.dao.StoragePoolJoinDao; -import com.cloud.api.query.vo.StoragePoolJoinVO; -import com.cloud.capacity.Capacity; -import com.cloud.capacity.CapacityVO; -import com.cloud.capacity.dao.CapacityDao; -import com.cloud.capacity.dao.CapacityDaoImpl.SummedCapacity; -import com.cloud.cluster.ClusterManager; -import com.cloud.configuration.Config; -import com.cloud.configuration.ConfigurationManagerImpl; -import com.cloud.consoleproxy.ConsoleProxyManagementState; -import com.cloud.consoleproxy.ConsoleProxyManager; -import com.cloud.dc.AccountVlanMapVO; -import com.cloud.dc.ClusterVO; -import com.cloud.dc.DataCenterVO; -import com.cloud.dc.DomainVlanMapVO; -import com.cloud.dc.HostPodVO; -import com.cloud.dc.Pod; -import com.cloud.dc.PodVlanMapVO; -import com.cloud.dc.Vlan; -import com.cloud.dc.Vlan.VlanType; -import com.cloud.dc.VlanVO; -import com.cloud.dc.dao.AccountVlanMapDao; -import com.cloud.dc.dao.ClusterDao; -import com.cloud.dc.dao.DataCenterDao; -import com.cloud.dc.dao.DomainVlanMapDao; -import com.cloud.dc.dao.HostPodDao; -import com.cloud.dc.dao.PodVlanMapDao; -import com.cloud.dc.dao.VlanDao; -import com.cloud.deploy.DataCenterDeployment; -import com.cloud.deploy.DeploymentPlanner; -import com.cloud.deploy.DeploymentPlanner.ExcludeList; -import com.cloud.deploy.DeploymentPlanningManager; -import com.cloud.domain.DomainVO; -import com.cloud.domain.dao.DomainDao; -import com.cloud.event.ActionEvent; -import com.cloud.event.ActionEventUtils; -import com.cloud.event.EventTypes; -import com.cloud.event.EventVO; -import com.cloud.event.dao.EventDao; -import com.cloud.exception.AgentUnavailableException; -import com.cloud.exception.ConcurrentOperationException; -import com.cloud.exception.InsufficientAddressCapacityException; -import com.cloud.exception.InvalidParameterValueException; -import com.cloud.exception.ManagementServerException; -import com.cloud.exception.OperationTimedoutException; -import com.cloud.exception.PermissionDeniedException; -import com.cloud.exception.ResourceUnavailableException; -import com.cloud.exception.VirtualMachineMigrationException; -import com.cloud.gpu.GPU; -import com.cloud.ha.HighAvailabilityManager; -import com.cloud.host.DetailVO; -import com.cloud.host.Host; -import com.cloud.host.Host.Type; -import com.cloud.host.HostTagVO; -import com.cloud.host.HostVO; -import com.cloud.host.dao.HostDao; -import com.cloud.host.dao.HostDetailsDao; -import com.cloud.host.dao.HostTagsDao; -import com.cloud.hypervisor.Hypervisor; -import com.cloud.hypervisor.Hypervisor.HypervisorType; -import com.cloud.hypervisor.HypervisorCapabilities; -import com.cloud.hypervisor.HypervisorCapabilitiesVO; -import com.cloud.hypervisor.dao.HypervisorCapabilitiesDao; -import com.cloud.hypervisor.kvm.dpdk.DpdkHelper; -import com.cloud.info.ConsoleProxyInfo; -import com.cloud.network.IpAddress; -import com.cloud.network.IpAddressManager; -import com.cloud.network.IpAddressManagerImpl; -import com.cloud.network.Network; -import com.cloud.network.NetworkModel; -import com.cloud.network.Networks; -import com.cloud.network.dao.IPAddressDao; -import com.cloud.network.dao.IPAddressVO; -import com.cloud.network.dao.LoadBalancerDao; -import com.cloud.network.dao.LoadBalancerVO; -import com.cloud.network.dao.NetworkAccountDao; -import com.cloud.network.dao.NetworkAccountVO; -import com.cloud.network.dao.NetworkDao; -import com.cloud.network.dao.NetworkDomainDao; -import com.cloud.network.dao.NetworkDomainVO; -import com.cloud.network.dao.NetworkVO; -import com.cloud.network.vpc.dao.VpcDao; -import com.cloud.org.Cluster; -import com.cloud.org.Grouping.AllocationState; -import com.cloud.projects.Project; -import com.cloud.projects.Project.ListProjectResourcesCriteria; -import com.cloud.projects.ProjectManager; -import com.cloud.resource.ResourceManager; -import com.cloud.server.ResourceTag.ResourceObjectType; -import com.cloud.server.auth.UserAuthenticator; -import com.cloud.service.ServiceOfferingVO; -import com.cloud.service.dao.ServiceOfferingDao; -import com.cloud.service.dao.ServiceOfferingDetailsDao; -import com.cloud.storage.DiskOfferingVO; -import com.cloud.storage.GuestOS; -import com.cloud.storage.GuestOSCategoryVO; -import com.cloud.storage.GuestOSHypervisor; -import com.cloud.storage.GuestOSHypervisorVO; -import com.cloud.storage.GuestOSVO; -import com.cloud.storage.GuestOsCategory; -import com.cloud.storage.ScopeType; -import com.cloud.storage.Storage; -import com.cloud.storage.StorageManager; -import com.cloud.storage.StoragePool; -import com.cloud.storage.StoragePoolStatus; -import com.cloud.storage.VMTemplateVO; -import com.cloud.storage.Volume; -import com.cloud.storage.VolumeApiServiceImpl; -import com.cloud.storage.VolumeVO; -import com.cloud.storage.dao.DiskOfferingDao; -import com.cloud.storage.dao.GuestOSCategoryDao; -import com.cloud.storage.dao.GuestOSDao; -import com.cloud.storage.dao.GuestOSHypervisorDao; -import com.cloud.storage.dao.VMTemplateDao; -import com.cloud.storage.dao.VolumeDao; -import com.cloud.storage.secondary.SecondaryStorageVmManager; -import com.cloud.tags.ResourceTagVO; -import com.cloud.tags.dao.ResourceTagDao; -import com.cloud.template.TemplateManager; -import com.cloud.user.Account; -import com.cloud.user.AccountManager; -import com.cloud.user.AccountService; -import com.cloud.user.SSHKeyPair; -import com.cloud.user.SSHKeyPairVO; -import com.cloud.user.User; -import com.cloud.user.UserData; -import com.cloud.user.UserDataVO; -import com.cloud.user.UserVO; -import com.cloud.user.dao.AccountDao; -import com.cloud.user.dao.SSHKeyPairDao; -import com.cloud.user.dao.UserDao; -import com.cloud.user.dao.UserDataDao; -import com.cloud.utils.NumbersUtil; -import com.cloud.utils.Pair; -import com.cloud.utils.PasswordGenerator; -import com.cloud.utils.Ternary; -import com.cloud.utils.component.ComponentLifecycle; -import com.cloud.utils.component.ManagerBase; -import com.cloud.utils.concurrency.NamedThreadFactory; -import com.cloud.utils.crypt.DBEncryptionUtil; -import com.cloud.utils.db.DB; -import com.cloud.utils.db.Filter; -import com.cloud.utils.db.GlobalLock; -import com.cloud.utils.db.JoinBuilder; -import com.cloud.utils.db.JoinBuilder.JoinType; -import com.cloud.utils.db.SearchBuilder; -import com.cloud.utils.db.SearchCriteria; -import com.cloud.utils.db.Transaction; -import com.cloud.utils.db.TransactionCallbackNoReturn; -import com.cloud.utils.db.TransactionStatus; -import com.cloud.utils.db.UUIDManager; -import com.cloud.utils.exception.CloudRuntimeException; -import com.cloud.utils.fsm.StateMachine2; -import com.cloud.utils.net.MacAddress; -import com.cloud.utils.net.NetUtils; -import com.cloud.utils.ssh.SSHKeysHelper; -import com.cloud.vm.ConsoleProxyVO; -import com.cloud.vm.DiskProfile; -import com.cloud.vm.DomainRouterVO; -import com.cloud.vm.InstanceGroupVO; -import com.cloud.vm.NicVO; -import com.cloud.vm.SecondaryStorageVmVO; -import com.cloud.vm.UserVmDetailVO; -import com.cloud.vm.UserVmManager; -import com.cloud.vm.UserVmVO; -import com.cloud.vm.VMInstanceVO; -import com.cloud.vm.VirtualMachine; -import com.cloud.vm.VirtualMachine.State; -import com.cloud.vm.VirtualMachineManager; -import com.cloud.vm.VirtualMachineProfile; -import com.cloud.vm.VirtualMachineProfileImpl; -import com.cloud.vm.dao.ConsoleProxyDao; -import com.cloud.vm.dao.DomainRouterDao; -import com.cloud.vm.dao.InstanceGroupDao; -import com.cloud.vm.dao.NicDao; -import com.cloud.vm.dao.SecondaryStorageVmDao; -import com.cloud.vm.dao.UserVmDao; -import com.cloud.vm.dao.UserVmDetailsDao; -import com.cloud.vm.dao.VMInstanceDao; - import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.SecurityChecker; import org.apache.cloudstack.affinity.AffinityGroupProcessor; @@ -802,9 +609,201 @@ import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; - -import static com.cloud.configuration.ConfigurationManagerImpl.VM_USERDATA_MAX_LENGTH; -import static com.cloud.vm.UserVmManager.MAX_USER_DATA_LENGTH_BYTES; +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.GetVncPortAnswer; +import com.cloud.agent.api.GetVncPortCommand; +import com.cloud.agent.api.PatchSystemVmAnswer; +import com.cloud.agent.api.PatchSystemVmCommand; +import com.cloud.agent.api.proxy.AllowConsoleAccessCommand; +import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.manager.Commands; +import com.cloud.agent.manager.allocator.HostAllocator; +import com.cloud.alert.Alert; +import com.cloud.alert.AlertManager; +import com.cloud.alert.AlertVO; +import com.cloud.alert.dao.AlertDao; +import com.cloud.api.ApiDBUtils; +import com.cloud.api.query.dao.StoragePoolJoinDao; +import com.cloud.api.query.vo.StoragePoolJoinVO; +import com.cloud.capacity.Capacity; +import com.cloud.capacity.CapacityVO; +import com.cloud.capacity.dao.CapacityDao; +import com.cloud.capacity.dao.CapacityDaoImpl.SummedCapacity; +import com.cloud.cluster.ClusterManager; +import com.cloud.configuration.Config; +import com.cloud.configuration.ConfigurationManagerImpl; +import com.cloud.consoleproxy.ConsoleProxyManagementState; +import com.cloud.consoleproxy.ConsoleProxyManager; +import com.cloud.dc.AccountVlanMapVO; +import com.cloud.dc.ClusterVO; +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.DomainVlanMapVO; +import com.cloud.dc.HostPodVO; +import com.cloud.dc.Pod; +import com.cloud.dc.PodVlanMapVO; +import com.cloud.dc.Vlan; +import com.cloud.dc.Vlan.VlanType; +import com.cloud.dc.VlanVO; +import com.cloud.dc.dao.AccountVlanMapDao; +import com.cloud.dc.dao.ClusterDao; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.dc.dao.DomainVlanMapDao; +import com.cloud.dc.dao.HostPodDao; +import com.cloud.dc.dao.PodVlanMapDao; +import com.cloud.dc.dao.VlanDao; +import com.cloud.deploy.DataCenterDeployment; +import com.cloud.deploy.DeploymentPlanner; +import com.cloud.deploy.DeploymentPlanner.ExcludeList; +import com.cloud.deploy.DeploymentPlanningManager; +import com.cloud.domain.DomainVO; +import com.cloud.domain.dao.DomainDao; +import com.cloud.event.ActionEvent; +import com.cloud.event.ActionEventUtils; +import com.cloud.event.EventTypes; +import com.cloud.event.EventVO; +import com.cloud.event.dao.EventDao; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientAddressCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.ManagementServerException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.exception.PermissionDeniedException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.exception.VirtualMachineMigrationException; +import com.cloud.gpu.GPU; +import com.cloud.ha.HighAvailabilityManager; +import com.cloud.host.DetailVO; +import com.cloud.host.Host; +import com.cloud.host.Host.Type; +import com.cloud.host.HostTagVO; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.host.dao.HostDetailsDao; +import com.cloud.host.dao.HostTagsDao; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.hypervisor.HypervisorCapabilities; +import com.cloud.hypervisor.HypervisorCapabilitiesVO; +import com.cloud.hypervisor.dao.HypervisorCapabilitiesDao; +import com.cloud.hypervisor.kvm.dpdk.DpdkHelper; +import com.cloud.info.ConsoleProxyInfo; +import com.cloud.network.IpAddress; +import com.cloud.network.IpAddressManager; +import com.cloud.network.IpAddressManagerImpl; +import com.cloud.network.Network; +import com.cloud.network.NetworkModel; +import com.cloud.network.Networks; +import com.cloud.network.dao.IPAddressDao; +import com.cloud.network.dao.IPAddressVO; +import com.cloud.network.dao.LoadBalancerDao; +import com.cloud.network.dao.LoadBalancerVO; +import com.cloud.network.dao.NetworkAccountDao; +import com.cloud.network.dao.NetworkAccountVO; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkDomainDao; +import com.cloud.network.dao.NetworkDomainVO; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.vpc.dao.VpcDao; +import com.cloud.org.Cluster; +import com.cloud.org.Grouping.AllocationState; +import com.cloud.projects.Project; +import com.cloud.projects.Project.ListProjectResourcesCriteria; +import com.cloud.projects.ProjectManager; +import com.cloud.resource.ResourceManager; +import com.cloud.server.ResourceTag.ResourceObjectType; +import com.cloud.server.auth.UserAuthenticator; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.service.dao.ServiceOfferingDetailsDao; +import com.cloud.storage.DiskOfferingVO; +import com.cloud.storage.GuestOS; +import com.cloud.storage.GuestOSCategoryVO; +import com.cloud.storage.GuestOSHypervisor; +import com.cloud.storage.GuestOSHypervisorVO; +import com.cloud.storage.GuestOSVO; +import com.cloud.storage.GuestOsCategory; +import com.cloud.storage.ScopeType; +import com.cloud.storage.Storage; +import com.cloud.storage.StorageManager; +import com.cloud.storage.StoragePool; +import com.cloud.storage.StoragePoolStatus; +import com.cloud.storage.VMTemplateVO; +import com.cloud.storage.Volume; +import com.cloud.storage.VolumeApiServiceImpl; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.DiskOfferingDao; +import com.cloud.storage.dao.GuestOSCategoryDao; +import com.cloud.storage.dao.GuestOSDao; +import com.cloud.storage.dao.GuestOSHypervisorDao; +import com.cloud.storage.dao.VMTemplateDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.storage.secondary.SecondaryStorageVmManager; +import com.cloud.tags.ResourceTagVO; +import com.cloud.tags.dao.ResourceTagDao; +import com.cloud.template.TemplateManager; +import com.cloud.user.Account; +import com.cloud.user.AccountManager; +import com.cloud.user.AccountService; +import com.cloud.user.SSHKeyPair; +import com.cloud.user.SSHKeyPairVO; +import com.cloud.user.User; +import com.cloud.user.UserData; +import com.cloud.user.UserDataVO; +import com.cloud.user.UserVO; +import com.cloud.user.dao.AccountDao; +import com.cloud.user.dao.SSHKeyPairDao; +import com.cloud.user.dao.UserDao; +import com.cloud.user.dao.UserDataDao; +import com.cloud.utils.NumbersUtil; +import com.cloud.utils.Pair; +import com.cloud.utils.PasswordGenerator; +import com.cloud.utils.Ternary; +import com.cloud.utils.component.ComponentLifecycle; +import com.cloud.utils.component.ManagerBase; +import com.cloud.utils.concurrency.NamedThreadFactory; +import com.cloud.utils.crypt.DBEncryptionUtil; +import com.cloud.utils.db.DB; +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GlobalLock; +import com.cloud.utils.db.JoinBuilder; +import com.cloud.utils.db.JoinBuilder.JoinType; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallbackNoReturn; +import com.cloud.utils.db.TransactionStatus; +import com.cloud.utils.db.UUIDManager; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.fsm.StateMachine2; +import com.cloud.utils.net.MacAddress; +import com.cloud.utils.net.NetUtils; +import com.cloud.utils.ssh.SSHKeysHelper; +import com.cloud.vm.ConsoleProxyVO; +import com.cloud.vm.DiskProfile; +import com.cloud.vm.DomainRouterVO; +import com.cloud.vm.InstanceGroupVO; +import com.cloud.vm.NicVO; +import com.cloud.vm.SecondaryStorageVmVO; +import com.cloud.vm.UserVmDetailVO; +import com.cloud.vm.UserVmManager; +import com.cloud.vm.UserVmVO; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.VirtualMachineManager; +import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.VirtualMachineProfileImpl; +import com.cloud.vm.dao.ConsoleProxyDao; +import com.cloud.vm.dao.DomainRouterDao; +import com.cloud.vm.dao.InstanceGroupDao; +import com.cloud.vm.dao.NicDao; +import com.cloud.vm.dao.SecondaryStorageVmDao; +import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.dao.UserVmDetailsDao; +import com.cloud.vm.dao.VMInstanceDao; public class ManagementServerImpl extends ManagerBase implements ManagementServer, Configurable { public static final Logger s_logger = Logger.getLogger(ManagementServerImpl.class.getName()); @@ -4188,6 +4187,10 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe capabilities.put("kubernetesServiceEnabled", kubernetesServiceEnabled); capabilities.put("kubernetesClusterExperimentalFeaturesEnabled", kubernetesClusterExperimentalFeaturesEnabled); capabilities.put(ApiServiceConfiguration.DefaultUIPageSize.key(), ApiServiceConfiguration.DefaultUIPageSize.value()); + capabilities.put(ApiConstants.INSTANCES_STATS_RETENTION_TIME, StatsCollector.vmStatsMaxRetentionTime.value()); + capabilities.put(ApiConstants.INSTANCES_STATS_USER_ONLY, StatsCollector.vmStatsCollectUserVMOnly.value()); + capabilities.put(ApiConstants.INSTANCES_DISKS_STATS_RETENTION_ENABLED, StatsCollector.vmDiskStatsRetentionEnabled.value()); + capabilities.put(ApiConstants.INSTANCES_DISKS_STATS_RETENTION_TIME, StatsCollector.vmDiskStatsMaxRetentionTime.value()); if (apiLimitEnabled) { capabilities.put("apiLimitInterval", apiLimitInterval); capabilities.put("apiLimitMax", apiLimitMax); diff --git a/server/src/main/java/com/cloud/server/StatsCollector.java b/server/src/main/java/com/cloud/server/StatsCollector.java index cd3a0cc95be..5197ccc3a3c 100644 --- a/server/src/main/java/com/cloud/server/StatsCollector.java +++ b/server/src/main/java/com/cloud/server/StatsCollector.java @@ -16,16 +16,17 @@ // under the License. package com.cloud.server; -import javax.inject.Inject; +import static com.cloud.utils.NumbersUtil.toHumanReadableSize; + import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; - import java.lang.management.RuntimeMXBean; import java.net.URI; import java.net.URISyntaxException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.Date; @@ -41,7 +42,8 @@ import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import com.cloud.utils.db.DbUtil; +import javax.inject.Inject; + import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProvider; @@ -62,9 +64,9 @@ import org.apache.cloudstack.utils.usage.UsageUtils; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.MapUtils; import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.DateUtils; -import org.apache.commons.lang3.BooleanUtils; import org.apache.log4j.Logger; import org.influxdb.BatchOptions; import org.influxdb.InfluxDB; @@ -105,6 +107,7 @@ import com.cloud.host.HostStats; import com.cloud.host.HostVO; import com.cloud.host.Status; import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.network.as.AutoScaleManager; import com.cloud.org.Cluster; @@ -118,8 +121,10 @@ import com.cloud.storage.Storage.ImageFormat; import com.cloud.storage.StorageManager; import com.cloud.storage.StorageStats; import com.cloud.storage.VolumeStats; +import com.cloud.storage.VolumeStatsVO; import com.cloud.storage.VolumeVO; import com.cloud.storage.dao.VolumeDao; +import com.cloud.storage.dao.VolumeStatsDao; import com.cloud.user.UserStatisticsVO; import com.cloud.user.VmDiskStatisticsVO; import com.cloud.user.dao.UserStatisticsDao; @@ -130,6 +135,7 @@ import com.cloud.utils.component.ComponentMethodInterceptable; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.concurrency.NamedThreadFactory; import com.cloud.utils.db.DbProperties; +import com.cloud.utils.db.DbUtil; import com.cloud.utils.db.Filter; import com.cloud.utils.db.GlobalLock; import com.cloud.utils.db.SearchCriteria; @@ -144,13 +150,15 @@ import com.cloud.vm.UserVmManager; import com.cloud.vm.UserVmVO; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachineManager; +import com.cloud.vm.VmDiskStats; +import com.cloud.vm.VmNetworkStats; import com.cloud.vm.VmStats; import com.cloud.vm.VmStatsVO; import com.cloud.vm.dao.NicDao; import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.VMInstanceDao; import com.cloud.vm.dao.VmStatsDao; - import com.codahale.metrics.JvmAttributeGaugeSet; import com.codahale.metrics.Metric; import com.codahale.metrics.MetricRegistry; @@ -160,12 +168,12 @@ import com.codahale.metrics.jvm.GarbageCollectorMetricSet; import com.codahale.metrics.jvm.MemoryUsageGaugeSet; import com.codahale.metrics.jvm.ThreadStatesGaugeSet; import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.reflect.TypeToken; import com.sun.management.OperatingSystemMXBean; -import static com.cloud.utils.NumbersUtil.toHumanReadableSize; - /** * Provides real time stats for various agent resources up to x seconds * @@ -267,15 +275,24 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc private static final ConfigKey statsOutputUri = new ConfigKey<>("Advanced", String.class, "stats.output.uri", "", "URI to send StatsCollector statistics to. The collector is defined on the URI scheme. Example: graphite://graphite-hostaddress:port or influxdb://influxdb-hostaddress/dbname. Note that the port is optional, if not added the default port for the respective collector (graphite or influxdb) will be used. Additionally, the database name '/dbname' is also optional; default db name is 'cloudstack'. You must create and configure the database if using influxdb.", true); - protected static ConfigKey vmStatsIncrementMetrics = new ConfigKey("Advanced", Boolean.class, "vm.stats.increment.metrics", "true", + protected static ConfigKey vmStatsIncrementMetrics = new ConfigKey<>("Advanced", Boolean.class, "vm.stats.increment.metrics", "true", "When set to 'true', VM metrics(NetworkReadKBs, NetworkWriteKBs, DiskWriteKBs, DiskReadKBs, DiskReadIOs and DiskWriteIOs) that are collected from the hypervisor are summed before being returned." - + "On the other hand, when set to 'false', the VM metrics API will just display the latest metrics collected.", true); + + "On the other hand, when set to 'false', the VM metrics API will just display the latest metrics collected.", true); private static final ConfigKey VM_STATS_INCREMENT_METRICS_IN_MEMORY = new ConfigKey<>("Advanced", Boolean.class, "vm.stats.increment.metrics.in.memory", "true", "When set to 'true', VM metrics(NetworkReadKBs, NetworkWriteKBs, DiskWriteKBs, DiskReadKBs, DiskReadIOs and DiskWriteIOs) that are collected from the hypervisor are summed and stored in memory. " + "On the other hand, when set to 'false', the VM metrics API will just display the latest metrics collected.", true); - protected static ConfigKey vmStatsMaxRetentionTime = new ConfigKey("Advanced", Integer.class, "vm.stats.max.retention.time", "1", + protected static ConfigKey vmStatsMaxRetentionTime = new ConfigKey<>("Advanced", Integer.class, "vm.stats.max.retention.time", "720", "The maximum time (in minutes) for keeping VM stats records in the database. The VM stats cleanup process will be disabled if this is set to 0 or less than 0.", true); + protected static ConfigKey vmStatsCollectUserVMOnly = new ConfigKey<>("Advanced", Boolean.class, "vm.stats.user.vm.only", "false", + "When set to 'false' stats for system VMs will be collected otherwise stats collection will be done only for user VMs", true); + + protected static ConfigKey vmDiskStatsRetentionEnabled = new ConfigKey<>("Advanced", Boolean.class, "vm.disk.stats.retention.enabled", "false", + "When set to 'true' stats for VM disks will be stored in the database otherwise disk stats will not be stored", true); + + protected static ConfigKey vmDiskStatsMaxRetentionTime = new ConfigKey<>("Advanced", Integer.class, "vm.disk.stats.max.retention.time", "720", + "The maximum time (in minutes) for keeping VM disks stats records in the database. The VM disks stats cleanup process will be disabled if this is set to 0 or less than 0.", true); + private static StatsCollector s_instance = null; private static Gson gson = new Gson(); @@ -296,6 +313,8 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc @Inject private VolumeDao _volsDao; @Inject + protected VolumeStatsDao volumeStatsDao; + @Inject private PrimaryDataStoreDao _storagePoolDao; @Inject private StorageManager _storageManager; @@ -330,6 +349,8 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc private ClusterManager clusterManager; @Inject private ManagementServerStatusDao managementServerStatusDao; + @Inject + VirtualMachineManager virtualMachineManager; private final ConcurrentHashMap managementServerHostStats = new ConcurrentHashMap<>(); private final ConcurrentHashMap dbStats = new ConcurrentHashMap<>(); @@ -467,6 +488,8 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc _executor.scheduleWithFixedDelay(new VmStatsCleaner(), DEFAULT_INITIAL_DELAY, 60000L, TimeUnit.MILLISECONDS); + _executor.scheduleWithFixedDelay(new VolumeStatsCleaner(), DEFAULT_INITIAL_DELAY, 60000L, TimeUnit.MILLISECONDS); + scheduleCollection(MANAGEMENT_SERVER_STATUS_COLLECTION_INTERVAL, new ManagementServerCollector(), 1L); scheduleCollection(DATABASE_SERVER_STATUS_COLLECTION_INTERVAL, new DbCollector(), 0L); @@ -604,6 +627,19 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc externalStatsPrefix, externalStatsHost, externalStatsPort)); } + protected Map getVmMapForStatsForHost(Host host) { + List vms = _vmInstance.listByHostAndState(host.getId(), VirtualMachine.State.Running); + boolean collectUserVMStatsOnly = Boolean.TRUE.equals(vmStatsCollectUserVMOnly.value()); + Map vmMap = new HashMap<>(); + for (VMInstanceVO vm : vms) { + if (collectUserVMStatsOnly && !VirtualMachine.Type.User.equals(vm.getType())) { + continue; + } + vmMap.put(vm.getId(), vm); + } + return vmMap; + } + class HostCollector extends AbstractStatsCollector { @Override protected void runInContext() { @@ -1156,25 +1192,18 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc Map metrics = new HashMap<>(); for (HostVO host : hosts) { - List vms = _userVmDao.listRunningByHostId(host.getId()); Date timestamp = new Date(); - - List vmIds = new ArrayList(); - - for (UserVmVO vm : vms) { - vmIds.add(vm.getId()); - } - + Map vmMap = getVmMapForStatsForHost(host); try { - Map vmStatsById = _userVmMgr.getVirtualMachineStatistics(host.getId(), host.getName(), vmIds); + Map vmStatsById = virtualMachineManager.getVirtualMachineStatistics(host.getId(), host.getName(), vmMap); if (vmStatsById != null) { Set vmIdSet = vmStatsById.keySet(); for (Long vmId : vmIdSet) { - VmStatsEntry statsForCurrentIteration = vmStatsById.get(vmId); + VmStatsEntry statsForCurrentIteration = (VmStatsEntry)vmStatsById.get(vmId); statsForCurrentIteration.setVmId(vmId); - UserVmVO userVmVo = _userVmDao.findById(vmId); - statsForCurrentIteration.setUserVmVO(userVmVo); + VMInstanceVO vm = vmMap.get(vmId); + statsForCurrentIteration.setVmUuid(vm.getUuid()); persistVirtualMachineStats(statsForCurrentIteration, timestamp); @@ -1226,6 +1255,12 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc } } + class VolumeStatsCleaner extends ManagedContextRunnable{ + protected void runInContext() { + cleanUpVolumeStats(); + } + } + /** * Gets the latest or the accumulation of the stats collected from a given VM. * @@ -1345,12 +1380,20 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc } } + private void logLessLatestStatDiscrepancy(String prefix, String hostName, String vmName, long reported, long stored, boolean toHumanReadable) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(String.format("%s that's less than the last one. Assuming something went wrong and persisting it. Host: %s . VM: %s Reported: %s Stored: %s", + prefix, hostName, vmName, toHumanReadable ? toHumanReadableSize(reported) : reported, toHumanReadable ? toHumanReadableSize(stored) : stored)); + } + } + class VmDiskStatsTask extends ManagedContextRunnable { @Override protected void runInContext() { //Check for ownership //msHost in UP state with min id should run the job ManagementServerHostVO msHost = managementServerHostDao.findOneInUpState(new Filter(ManagementServerHostVO.class, "id", true, 0L, 1L)); + boolean persistVolumeStats = vmDiskStatsRetentionEnabled.value(); if (msHost == null || (msHost.getMsid() != mgmtSrvrId)) { LOGGER.debug("Skipping collect vm disk stats from hosts"); return; @@ -1363,94 +1406,77 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc List hosts = _hostDao.search(sc, null); for (HostVO host : hosts) { + Date timestamp = new Date(); try { Transaction.execute(new TransactionCallbackNoReturn() { @Override public void doInTransactionWithoutResult(TransactionStatus status) { - List vms = _userVmDao.listRunningByHostId(host.getId()); - List vmIds = new ArrayList(); - - for (UserVmVO vm : vms) { - if (vm.getType() == VirtualMachine.Type.User) // user vm - vmIds.add(vm.getId()); - } - - HashMap> vmDiskStatsById = _userVmMgr.getVmDiskStatistics(host.getId(), host.getName(), vmIds); + Map vmMap = getVmMapForStatsForHost(host); + HashMap> vmDiskStatsById = virtualMachineManager.getVmDiskStatistics(host.getId(), host.getName(), vmMap); if (vmDiskStatsById == null) return; Set vmIdSet = vmDiskStatsById.keySet(); for (Long vmId : vmIdSet) { - List vmDiskStats = vmDiskStatsById.get(vmId); + List vmDiskStats = vmDiskStatsById.get(vmId); if (vmDiskStats == null) continue; - UserVmVO userVm = _userVmDao.findById(vmId); - for (VmDiskStatsEntry vmDiskStat : vmDiskStats) { + VMInstanceVO vm = vmMap.get(vmId); + for (VmDiskStats vmDiskStat : vmDiskStats) { + VmDiskStatsEntry vmDiskStatEntry = (VmDiskStatsEntry)vmDiskStat; SearchCriteria sc_volume = _volsDao.createSearchCriteria(); - sc_volume.addAnd("path", SearchCriteria.Op.EQ, vmDiskStat.getPath()); + sc_volume.addAnd("path", SearchCriteria.Op.EQ, vmDiskStatEntry.getPath()); List volumes = _volsDao.search(sc_volume, null); if (CollectionUtils.isEmpty(volumes)) break; VolumeVO volume = volumes.get(0); - VmDiskStatisticsVO previousVmDiskStats = _vmDiskStatsDao.findBy(userVm.getAccountId(), userVm.getDataCenterId(), vmId, volume.getId()); - VmDiskStatisticsVO vmDiskStat_lock = _vmDiskStatsDao.lock(userVm.getAccountId(), userVm.getDataCenterId(), vmId, volume.getId()); + VmDiskStatisticsVO previousVmDiskStats = _vmDiskStatsDao.findBy(vm.getAccountId(), vm.getDataCenterId(), vmId, volume.getId()); + VmDiskStatisticsVO vmDiskStat_lock = _vmDiskStatsDao.lock(vm.getAccountId(), vm.getDataCenterId(), vmId, volume.getId()); - if (areAllDiskStatsZero(vmDiskStat)) { + if (persistVolumeStats) { + persistVolumeStats(volume.getId(), vmDiskStatEntry, vm.getHypervisorType(), timestamp); + } + + if (areAllDiskStatsZero(vmDiskStatEntry)) { LOGGER.debug("IO/bytes read and write are all 0. Not updating vm_disk_statistics"); continue; } if (vmDiskStat_lock == null) { - LOGGER.warn("unable to find vm disk stats from host for account: " + userVm.getAccountId() + " with vmId: " + userVm.getId() + LOGGER.warn("unable to find vm disk stats from host for account: " + vm.getAccountId() + " with vmId: " + vm.getId() + " and volumeId:" + volume.getId()); continue; } if (isCurrentVmDiskStatsDifferentFromPrevious(previousVmDiskStats, vmDiskStat_lock)) { LOGGER.debug("vm disk stats changed from the time GetVmDiskStatsCommand was sent. " + "Ignoring current answer. Host: " + host.getName() - + " . VM: " + vmDiskStat.getVmName() + " Read(Bytes): " + toHumanReadableSize(vmDiskStat.getBytesRead()) + " write(Bytes): " + toHumanReadableSize(vmDiskStat.getBytesWrite()) - + " Read(IO): " + toHumanReadableSize(vmDiskStat.getIORead()) + " write(IO): " + toHumanReadableSize(vmDiskStat.getIOWrite())); + + " . VM: " + vmDiskStatEntry.getVmName() + " Read(Bytes): " + toHumanReadableSize(vmDiskStatEntry.getBytesRead()) + " write(Bytes): " + toHumanReadableSize(vmDiskStatEntry.getBytesWrite()) + + " Read(IO): " + toHumanReadableSize(vmDiskStatEntry.getIORead()) + " write(IO): " + toHumanReadableSize(vmDiskStatEntry.getIOWrite())); continue; } - if (vmDiskStat_lock.getCurrentBytesRead() > vmDiskStat.getBytesRead()) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Read # of bytes that's less than the last one. " + "Assuming something went wrong and persisting it. Host: " - + host.getName() + " . VM: " + vmDiskStat.getVmName() + " Reported: " + toHumanReadableSize(vmDiskStat.getBytesRead()) + " Stored: " - + vmDiskStat_lock.getCurrentBytesRead()); - } + if (vmDiskStat_lock.getCurrentBytesRead() > vmDiskStatEntry.getBytesRead()) { + logLessLatestStatDiscrepancy("Read # of bytes", host.getName(), vmDiskStatEntry.getVmName(), vmDiskStatEntry.getBytesRead(), vmDiskStat_lock.getCurrentBytesRead(), true); vmDiskStat_lock.setNetBytesRead(vmDiskStat_lock.getNetBytesRead() + vmDiskStat_lock.getCurrentBytesRead()); } - vmDiskStat_lock.setCurrentBytesRead(vmDiskStat.getBytesRead()); - if (vmDiskStat_lock.getCurrentBytesWrite() > vmDiskStat.getBytesWrite()) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Write # of bytes that's less than the last one. " + "Assuming something went wrong and persisting it. Host: " - + host.getName() + " . VM: " + vmDiskStat.getVmName() + " Reported: " + toHumanReadableSize(vmDiskStat.getBytesWrite()) + " Stored: " - + toHumanReadableSize(vmDiskStat_lock.getCurrentBytesWrite())); - } + vmDiskStat_lock.setCurrentBytesRead(vmDiskStatEntry.getBytesRead()); + if (vmDiskStat_lock.getCurrentBytesWrite() > vmDiskStatEntry.getBytesWrite()) { + logLessLatestStatDiscrepancy("Write # of bytes", host.getName(), vmDiskStatEntry.getVmName(), vmDiskStatEntry.getBytesWrite(), vmDiskStat_lock.getCurrentBytesWrite(), true); vmDiskStat_lock.setNetBytesWrite(vmDiskStat_lock.getNetBytesWrite() + vmDiskStat_lock.getCurrentBytesWrite()); } - vmDiskStat_lock.setCurrentBytesWrite(vmDiskStat.getBytesWrite()); - if (vmDiskStat_lock.getCurrentIORead() > vmDiskStat.getIORead()) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Read # of IO that's less than the last one. " + "Assuming something went wrong and persisting it. Host: " - + host.getName() + " . VM: " + vmDiskStat.getVmName() + " Reported: " + vmDiskStat.getIORead() + " Stored: " - + vmDiskStat_lock.getCurrentIORead()); - } + vmDiskStat_lock.setCurrentBytesWrite(vmDiskStatEntry.getBytesWrite()); + if (vmDiskStat_lock.getCurrentIORead() > vmDiskStatEntry.getIORead()) { + logLessLatestStatDiscrepancy("Read # of IO", host.getName(), vmDiskStatEntry.getVmName(), vmDiskStatEntry.getIORead(), vmDiskStat_lock.getCurrentIORead(), false); vmDiskStat_lock.setNetIORead(vmDiskStat_lock.getNetIORead() + vmDiskStat_lock.getCurrentIORead()); } - vmDiskStat_lock.setCurrentIORead(vmDiskStat.getIORead()); - if (vmDiskStat_lock.getCurrentIOWrite() > vmDiskStat.getIOWrite()) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Write # of IO that's less than the last one. " + "Assuming something went wrong and persisting it. Host: " - + host.getName() + " . VM: " + vmDiskStat.getVmName() + " Reported: " + vmDiskStat.getIOWrite() + " Stored: " - + vmDiskStat_lock.getCurrentIOWrite()); - } + vmDiskStat_lock.setCurrentIORead(vmDiskStatEntry.getIORead()); + if (vmDiskStat_lock.getCurrentIOWrite() > vmDiskStatEntry.getIOWrite()) { + logLessLatestStatDiscrepancy("Write # of IO", host.getName(), vmDiskStatEntry.getVmName(), vmDiskStatEntry.getIOWrite(), vmDiskStat_lock.getCurrentIOWrite(), false); vmDiskStat_lock.setNetIOWrite(vmDiskStat_lock.getNetIOWrite() + vmDiskStat_lock.getCurrentIOWrite()); } - vmDiskStat_lock.setCurrentIOWrite(vmDiskStat.getIOWrite()); + vmDiskStat_lock.setCurrentIOWrite(vmDiskStatEntry.getIOWrite()); if (!_dailyOrHourly) { //update agg bytes @@ -1493,22 +1519,15 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc Transaction.execute(new TransactionCallbackNoReturn() { @Override public void doInTransactionWithoutResult(TransactionStatus status) { - List vms = _userVmDao.listRunningByHostId(host.getId()); - List vmIds = new ArrayList(); - - for (UserVmVO vm : vms) { - if (vm.getType() == VirtualMachine.Type.User) // user vm - vmIds.add(vm.getId()); - } - - HashMap> vmNetworkStatsById = _userVmMgr.getVmNetworkStatistics(host.getId(), host.getName(), vmIds); + Map vmMap = getVmMapForStatsForHost(host); + HashMap> vmNetworkStatsById = virtualMachineManager.getVmNetworkStatistics(host.getId(), host.getName(), vmMap); if (vmNetworkStatsById == null) return; Set vmIdSet = vmNetworkStatsById.keySet(); for (Long vmId : vmIdSet) { - List vmNetworkStats = vmNetworkStatsById.get(vmId); - if (vmNetworkStats == null) + List vmNetworkStats = vmNetworkStatsById.get(vmId); + if (CollectionUtils.isEmpty(vmNetworkStats)) continue; UserVmVO userVm = _userVmDao.findById(vmId); if (userVm == null) { @@ -1517,9 +1536,10 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc } LOGGER.debug("Now we are updating the user_statistics table for VM: " + userVm.getInstanceName() + " after collecting vm network statistics from host: " + host.getName()); - for (VmNetworkStatsEntry vmNetworkStat : vmNetworkStats) { + for (VmNetworkStats vmNetworkStat : vmNetworkStats) { + VmNetworkStatsEntry vmNetworkStatEntry = (VmNetworkStatsEntry)vmNetworkStat; SearchCriteria sc_nic = _nicDao.createSearchCriteria(); - sc_nic.addAnd("macAddress", SearchCriteria.Op.EQ, vmNetworkStat.getMacAddress()); + sc_nic.addAnd("macAddress", SearchCriteria.Op.EQ, vmNetworkStatEntry.getMacAddress()); NicVO nic = _nicDao.search(sc_nic, null).get(0); List vlan = _vlanDao.listVlansByNetworkId(nic.getNetworkId()); if (vlan == null || vlan.size() == 0 || vlan.get(0).getVlanType() != VlanType.DirectAttached) @@ -1534,7 +1554,7 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc UserStatisticsVO vmNetworkStat_lock = _userStatsDao.lock(userVm.getAccountId(), userVm.getDataCenterId(), nic.getNetworkId(), nic.getIPv4Address(), vmId, "UserVm"); - if ((vmNetworkStat.getBytesSent() == 0) && (vmNetworkStat.getBytesReceived() == 0)) { + if ((vmNetworkStatEntry.getBytesSent() == 0) && (vmNetworkStatEntry.getBytesReceived() == 0)) { LOGGER.debug("bytes sent and received are all 0. Not updating user_statistics"); continue; } @@ -1548,30 +1568,22 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc if (previousvmNetworkStats != null && ((previousvmNetworkStats.getCurrentBytesSent() != vmNetworkStat_lock.getCurrentBytesSent()) || (previousvmNetworkStats.getCurrentBytesReceived() != vmNetworkStat_lock.getCurrentBytesReceived()))) { LOGGER.debug("vm network stats changed from the time GetNmNetworkStatsCommand was sent. " + "Ignoring current answer. Host: " - + host.getName() + " . VM: " + vmNetworkStat.getVmName() + " Sent(Bytes): " + vmNetworkStat.getBytesSent() + " Received(Bytes): " - + vmNetworkStat.getBytesReceived()); + + host.getName() + " . VM: " + vmNetworkStatEntry.getVmName() + " Sent(Bytes): " + vmNetworkStatEntry.getBytesSent() + " Received(Bytes): " + + vmNetworkStatEntry.getBytesReceived()); continue; } - if (vmNetworkStat_lock.getCurrentBytesSent() > vmNetworkStat.getBytesSent()) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Sent # of bytes that's less than the last one. " + "Assuming something went wrong and persisting it. Host: " - + host.getName() + " . VM: " + vmNetworkStat.getVmName() + " Reported: " + toHumanReadableSize(vmNetworkStat.getBytesSent()) + " Stored: " - + toHumanReadableSize(vmNetworkStat_lock.getCurrentBytesSent())); - } + if (vmNetworkStat_lock.getCurrentBytesSent() > vmNetworkStatEntry.getBytesSent()) { + logLessLatestStatDiscrepancy("Sent # of bytes", host.getName(), vmNetworkStatEntry.getVmName(), vmNetworkStatEntry.getBytesSent(), vmNetworkStat_lock.getCurrentBytesSent(), true); vmNetworkStat_lock.setNetBytesSent(vmNetworkStat_lock.getNetBytesSent() + vmNetworkStat_lock.getCurrentBytesSent()); } - vmNetworkStat_lock.setCurrentBytesSent(vmNetworkStat.getBytesSent()); + vmNetworkStat_lock.setCurrentBytesSent(vmNetworkStatEntry.getBytesSent()); - if (vmNetworkStat_lock.getCurrentBytesReceived() > vmNetworkStat.getBytesReceived()) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Received # of bytes that's less than the last one. " + "Assuming something went wrong and persisting it. Host: " - + host.getName() + " . VM: " + vmNetworkStat.getVmName() + " Reported: " + toHumanReadableSize(vmNetworkStat.getBytesReceived()) + " Stored: " - + toHumanReadableSize(vmNetworkStat_lock.getCurrentBytesReceived())); - } + if (vmNetworkStat_lock.getCurrentBytesReceived() > vmNetworkStatEntry.getBytesReceived()) { + logLessLatestStatDiscrepancy("Received # of bytes", host.getName(), vmNetworkStatEntry.getVmName(), vmNetworkStatEntry.getBytesReceived(), vmNetworkStat_lock.getCurrentBytesReceived(), true); vmNetworkStat_lock.setNetBytesReceived(vmNetworkStat_lock.getNetBytesReceived() + vmNetworkStat_lock.getCurrentBytesReceived()); } - vmNetworkStat_lock.setCurrentBytesReceived(vmNetworkStat.getBytesReceived()); + vmNetworkStat_lock.setCurrentBytesReceived(vmNetworkStatEntry.getBytesReceived()); if (!_dailyOrHourly) { //update agg bytes @@ -1882,6 +1894,39 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc vmStatsDao.persist(vmStatsVO); } + private String getVmDiskStatsEntryAsString(VmDiskStatsEntry statsForCurrentIteration, Hypervisor.HypervisorType hypervisorType) { + VmDiskStatsEntry entry; + if (Hypervisor.HypervisorType.KVM.equals(hypervisorType)) { + entry = new VmDiskStatsEntry(statsForCurrentIteration.getVmName(), + statsForCurrentIteration.getPath(), + statsForCurrentIteration.getDeltaIoWrite(), + statsForCurrentIteration.getDeltaIoRead(), + statsForCurrentIteration.getDeltaBytesWrite(), + statsForCurrentIteration.getDeltaBytesRead()); + } else { + entry = statsForCurrentIteration; + } + JsonElement element = gson.toJsonTree(entry); + JsonObject obj = element.getAsJsonObject(); + for (String key : Arrays.asList("deltaIoRead", "deltaIoWrite", "deltaBytesWrite", "deltaBytesRead")) { + obj.remove(key); + } + return obj.toString(); + } + + /** + * Persists VM disk stats in the database. + * @param statsForCurrentIteration the metrics stats data to persist. + * @param timestamp the time that will be stamped. + */ + protected void persistVolumeStats(long volumeId, VmDiskStatsEntry statsForCurrentIteration, Hypervisor.HypervisorType hypervisorType, Date timestamp) { + VolumeStatsVO volumeStatsVO = new VolumeStatsVO(volumeId, msId, timestamp, getVmDiskStatsEntryAsString(statsForCurrentIteration, hypervisorType)); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(String.format("Recording volume stats: [%s].", volumeStatsVO)); + } + volumeStatsDao.persist(volumeStatsVO); + } + /** * Removes the oldest VM stats records according to the global * parameter {@code vm.stats.max.retention.time}. @@ -1899,6 +1944,25 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc vmStatsDao.removeAllByTimestampLessThan(limit); } + /** + * Removes the oldest Volume stats records according to the global + * parameter {@code vm.disk.stats.max.retention.time}. + */ + protected void cleanUpVolumeStats() { + Integer maxRetentionTime = vmDiskStatsMaxRetentionTime.value(); + if (maxRetentionTime <= 0) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(String.format("Skipping Volume stats cleanup. The [%s] parameter [%s] is set to 0 or less than 0.", + vmDiskStatsMaxRetentionTime.scope(), vmDiskStatsMaxRetentionTime.toString())); + } + return; + } + LOGGER.trace("Removing older Volume stats records."); + Date now = new Date(); + Date limit = DateUtils.addMinutes(now, -maxRetentionTime); + volumeStatsDao.removeAllByTimestampLessThan(limit); + } + /** * Sends host metrics to a configured InfluxDB host. The metrics respects the following specification.
* Tags:vm_id, uuid, instance_name, data_center_id, host_id
@@ -1929,10 +1993,9 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc */ protected Point createInfluxDbPointForVmMetrics(Object metricsObject) { VmStatsEntry vmStatsEntry = (VmStatsEntry)metricsObject; - UserVmVO userVmVO = vmStatsEntry.getUserVmVO(); Map tagsToAdd = new HashMap<>(); - tagsToAdd.put(UUID_TAG, userVmVO.getUuid()); + tagsToAdd.put(UUID_TAG, vmStatsEntry.getVmUuid()); Map fieldsToAdd = new HashMap<>(); fieldsToAdd.put(TOTAL_MEMORY_KBS_FIELD, vmStatsEntry.getMemoryKBs()); @@ -2053,7 +2116,7 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc @Override public ConfigKey[] getConfigKeys() { return new ConfigKey[] {vmDiskStatsInterval, vmDiskStatsIntervalMin, vmNetworkStatsInterval, vmNetworkStatsIntervalMin, StatsTimeout, statsOutputUri, - vmStatsIncrementMetrics, vmStatsMaxRetentionTime, + vmStatsIncrementMetrics, vmStatsMaxRetentionTime, vmStatsCollectUserVMOnly, vmDiskStatsRetentionEnabled, vmDiskStatsMaxRetentionTime, VM_STATS_INCREMENT_METRICS_IN_MEMORY, MANAGEMENT_SERVER_STATUS_COLLECTION_INTERVAL, DATABASE_SERVER_STATUS_COLLECTION_INTERVAL, diff --git a/server/src/main/java/com/cloud/vm/UserVmManager.java b/server/src/main/java/com/cloud/vm/UserVmManager.java index 5029615b241..39f1e5d2d28 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManager.java +++ b/server/src/main/java/com/cloud/vm/UserVmManager.java @@ -20,14 +20,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import com.cloud.offering.ServiceOffering; -import com.cloud.template.VirtualMachineTemplate; import org.apache.cloudstack.api.BaseCmd.HTTPMethod; import org.apache.cloudstack.framework.config.ConfigKey; -import com.cloud.agent.api.VmDiskStatsEntry; -import com.cloud.agent.api.VmNetworkStatsEntry; -import com.cloud.agent.api.VmStatsEntry; import com.cloud.agent.api.VolumeStatsEntry; import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.InsufficientCapacityException; @@ -35,8 +30,10 @@ import com.cloud.exception.ManagementServerException; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.exception.VirtualMachineMigrationException; +import com.cloud.offering.ServiceOffering; import com.cloud.service.ServiceOfferingVO; import com.cloud.storage.Storage.StoragePoolType; +import com.cloud.template.VirtualMachineTemplate; import com.cloud.uservm.UserVm; import com.cloud.utils.Pair; @@ -85,17 +82,6 @@ public interface UserVmManager extends UserVmService { */ boolean stopVirtualMachine(long userId, long vmId); - /** - * Obtains statistics for a list of host or VMs; CPU and network utilization - * @param host ID - * @param host name - * @param list of VM IDs or host id - * @return GetVmStatsAnswer - */ - HashMap getVirtualMachineStatistics(long hostId, String hostName, List vmIds); - - HashMap> getVmDiskStatistics(long hostId, String hostName, List vmIds); - HashMap getVolumeStatistics(long clusterId, String poolUuid, StoragePoolType poolType, int timeout); boolean deleteVmGroup(long groupId); @@ -136,8 +122,6 @@ public interface UserVmManager extends UserVmService { void persistDeviceBusInfo(UserVmVO paramUserVmVO, String paramString); - HashMap> getVmNetworkStatistics(long hostId, String hostName, List vmIds); - boolean checkIfDynamicScalingCanBeEnabled(VirtualMachine vm, ServiceOffering offering, VirtualMachineTemplate template, Long zoneId); Boolean getDestroyRootVolumeOnVmDestruction(Long domainId); diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 8815ea65eb0..ea72a38cbb9 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -148,8 +148,6 @@ import com.cloud.agent.api.GetVmDiskStatsCommand; import com.cloud.agent.api.GetVmIpAddressCommand; import com.cloud.agent.api.GetVmNetworkStatsAnswer; import com.cloud.agent.api.GetVmNetworkStatsCommand; -import com.cloud.agent.api.GetVmStatsAnswer; -import com.cloud.agent.api.GetVmStatsCommand; import com.cloud.agent.api.GetVolumeStatsAnswer; import com.cloud.agent.api.GetVolumeStatsCommand; import com.cloud.agent.api.ModifyTargetsCommand; @@ -159,7 +157,6 @@ import com.cloud.agent.api.RestoreVMSnapshotCommand; import com.cloud.agent.api.StartAnswer; import com.cloud.agent.api.VmDiskStatsEntry; import com.cloud.agent.api.VmNetworkStatsEntry; -import com.cloud.agent.api.VmStatsEntry; import com.cloud.agent.api.VolumeStatsEntry; import com.cloud.agent.api.to.DiskTO; import com.cloud.agent.api.to.NicTO; @@ -1867,41 +1864,6 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } } - @Override - public HashMap> getVmDiskStatistics(long hostId, String hostName, List vmIds) throws CloudRuntimeException { - HashMap> vmDiskStatsById = new HashMap>(); - - if (vmIds.isEmpty()) { - return vmDiskStatsById; - } - - List vmNames = new ArrayList(); - - for (Long vmId : vmIds) { - UserVmVO vm = _vmDao.findById(vmId); - vmNames.add(vm.getInstanceName()); - } - - Answer answer = _agentMgr.easySend(hostId, new GetVmDiskStatsCommand(vmNames, _hostDao.findById(hostId).getGuid(), hostName)); - if (answer == null || !answer.getResult()) { - s_logger.warn("Unable to obtain VM disk statistics."); - return null; - } else { - HashMap> vmDiskStatsByName = ((GetVmDiskStatsAnswer)answer).getVmDiskStatsMap(); - - if (vmDiskStatsByName == null) { - s_logger.warn("Unable to obtain VM disk statistics."); - return null; - } - - for (Map.Entry> entry: vmDiskStatsByName.entrySet()) { - vmDiskStatsById.put(vmIds.get(vmNames.indexOf(entry.getKey())), entry.getValue()); - } - } - - return vmDiskStatsById; - } - @Override public boolean upgradeVirtualMachine(Long vmId, Long newServiceOfferingId, Map customParameters) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException, VirtualMachineMigrationException { @@ -2171,41 +2133,6 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } } - @Override - public HashMap getVirtualMachineStatistics(long hostId, String hostName, List vmIds) throws CloudRuntimeException { - HashMap vmStatsById = new HashMap(); - - if (vmIds.isEmpty()) { - return vmStatsById; - } - - List vmNames = new ArrayList(); - - for (Long vmId : vmIds) { - UserVmVO vm = _vmDao.findById(vmId); - vmNames.add(vm.getInstanceName()); - } - - Answer answer = _agentMgr.easySend(hostId, new GetVmStatsCommand(vmNames, _hostDao.findById(hostId).getGuid(), hostName)); - if (answer == null || !answer.getResult()) { - s_logger.warn("Unable to obtain VM statistics."); - return null; - } else { - HashMap vmStatsByName = ((GetVmStatsAnswer)answer).getVmStatsMap(); - - if (vmStatsByName == null) { - s_logger.warn("Unable to obtain VM statistics."); - return null; - } - - for (Map.Entry entry : vmStatsByName.entrySet()) { - vmStatsById.put(vmIds.get(vmNames.indexOf(entry.getKey())), entry.getValue()); - } - } - - return vmStatsById; - } - @Override public HashMap getVolumeStatistics(long clusterId, String poolUuid, StoragePoolType poolType, int timeout) { List neighbors = _resourceMgr.listHostsInClusterByStatus(clusterId, Status.Up); @@ -4718,41 +4645,6 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } } - @Override - public HashMap> getVmNetworkStatistics(long hostId, String hostName, List vmIds) { - HashMap> vmNetworkStatsById = new HashMap>(); - - if (vmIds.isEmpty()) { - return vmNetworkStatsById; - } - - List vmNames = new ArrayList(); - - for (Long vmId : vmIds) { - UserVmVO vm = _vmDao.findById(vmId); - vmNames.add(vm.getInstanceName()); - } - - Answer answer = _agentMgr.easySend(hostId, new GetVmNetworkStatsCommand(vmNames, _hostDao.findById(hostId).getGuid(), hostName)); - if (answer == null || !answer.getResult()) { - s_logger.warn("Unable to obtain VM network statistics."); - return null; - } else { - HashMap> vmNetworkStatsByName = ((GetVmNetworkStatsAnswer)answer).getVmNetworkStatsMap(); - - if (vmNetworkStatsByName == null) { - s_logger.warn("Unable to obtain VM network statistics."); - return null; - } - - for (String vmName : vmNetworkStatsByName.keySet()) { - vmNetworkStatsById.put(vmIds.get(vmNames.indexOf(vmName)), vmNetworkStatsByName.get(vmName)); - } - } - - return vmNetworkStatsById; - } - @Override public void collectVmNetworkStatistics (final UserVm userVm) { if (!userVm.getHypervisorType().equals(HypervisorType.KVM)) { diff --git a/server/src/test/java/com/cloud/network/as/AutoScaleManagerImplTest.java b/server/src/test/java/com/cloud/network/as/AutoScaleManagerImplTest.java index 870f76d2c36..e60ce86fc3f 100644 --- a/server/src/test/java/com/cloud/network/as/AutoScaleManagerImplTest.java +++ b/server/src/test/java/com/cloud/network/as/AutoScaleManagerImplTest.java @@ -16,15 +16,74 @@ // under the License. package com.cloud.network.as; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.matches; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.when; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletionService; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; + +import org.apache.cloudstack.affinity.AffinityGroupVO; +import org.apache.cloudstack.affinity.dao.AffinityGroupDao; +import org.apache.cloudstack.annotation.AnnotationService; +import org.apache.cloudstack.annotation.dao.AnnotationDao; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.command.admin.autoscale.CreateCounterCmd; +import org.apache.cloudstack.api.command.user.autoscale.CreateAutoScalePolicyCmd; +import org.apache.cloudstack.api.command.user.autoscale.CreateAutoScaleVmGroupCmd; +import org.apache.cloudstack.api.command.user.autoscale.CreateAutoScaleVmProfileCmd; +import org.apache.cloudstack.api.command.user.autoscale.CreateConditionCmd; +import org.apache.cloudstack.api.command.user.autoscale.ListCountersCmd; +import org.apache.cloudstack.api.command.user.autoscale.UpdateAutoScaleVmGroupCmd; +import org.apache.cloudstack.api.command.user.autoscale.UpdateAutoScaleVmProfileCmd; +import org.apache.cloudstack.api.command.user.autoscale.UpdateConditionCmd; +import org.apache.cloudstack.api.command.user.vm.DeployVMCmd; +import org.apache.cloudstack.config.ApiServiceConfiguration; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.framework.config.ConfigKey; +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.Mockito; +import org.mockito.Spy; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.springframework.test.util.ReflectionTestUtils; + import com.cloud.agent.AgentManager; import com.cloud.agent.api.PerformanceMonitorAnswer; import com.cloud.agent.api.PerformanceMonitorCommand; import com.cloud.agent.api.VmStatsEntry; import com.cloud.agent.api.routing.GetAutoScaleMetricsAnswer; import com.cloud.agent.api.routing.GetAutoScaleMetricsCommand; -import com.cloud.agent.api.to.LoadBalancerTO.AutoScaleVmProfileTO; -import com.cloud.agent.api.to.LoadBalancerTO.AutoScaleVmGroupTO; import com.cloud.agent.api.to.LoadBalancerTO.AutoScalePolicyTO; +import com.cloud.agent.api.to.LoadBalancerTO.AutoScaleVmGroupTO; +import com.cloud.agent.api.to.LoadBalancerTO.AutoScaleVmProfileTO; import com.cloud.agent.api.to.LoadBalancerTO.ConditionTO; import com.cloud.agent.api.to.LoadBalancerTO.CounterTO; import com.cloud.api.dispatch.DispatchChain; @@ -99,67 +158,12 @@ import com.cloud.vm.UserVmManager; import com.cloud.vm.UserVmService; import com.cloud.vm.UserVmVO; import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachineManager; import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.VmStats; import com.cloud.vm.dao.DomainRouterDao; import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.VMInstanceDao; -import org.apache.cloudstack.affinity.AffinityGroupVO; -import org.apache.cloudstack.affinity.dao.AffinityGroupDao; -import org.apache.cloudstack.annotation.AnnotationService; -import org.apache.cloudstack.annotation.dao.AnnotationDao; -import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.command.admin.autoscale.CreateCounterCmd; -import org.apache.cloudstack.api.command.user.autoscale.CreateAutoScalePolicyCmd; -import org.apache.cloudstack.api.command.user.autoscale.CreateAutoScaleVmGroupCmd; -import org.apache.cloudstack.api.command.user.autoscale.CreateAutoScaleVmProfileCmd; -import org.apache.cloudstack.api.command.user.autoscale.CreateConditionCmd; -import org.apache.cloudstack.api.command.user.autoscale.ListCountersCmd; -import org.apache.cloudstack.api.command.user.autoscale.UpdateAutoScaleVmGroupCmd; -import org.apache.cloudstack.api.command.user.autoscale.UpdateAutoScaleVmProfileCmd; -import org.apache.cloudstack.api.command.user.autoscale.UpdateConditionCmd; -import org.apache.cloudstack.api.command.user.vm.DeployVMCmd; -import org.apache.cloudstack.config.ApiServiceConfiguration; -import org.apache.cloudstack.context.CallContext; -import org.apache.cloudstack.framework.config.ConfigKey; -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.Mockito; -import org.mockito.Spy; -import org.powermock.api.mockito.PowerMockito; -import org.powermock.core.classloader.annotations.PowerMockIgnore; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; -import org.springframework.test.util.ReflectionTestUtils; - -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.CompletionService; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.ScheduledExecutorService; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.matches; -import static org.mockito.ArgumentMatchers.nullable; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.when; @RunWith(PowerMockRunner.class) @PowerMockIgnore("javax.management.*") @@ -251,6 +255,9 @@ public class AutoScaleManagerImplTest { @Mock VMInstanceDao vmInstanceDao; + @Mock + VirtualMachineManager virtualMachineManager; + AccountVO account; UserVO user; @@ -2204,14 +2211,14 @@ public class AutoScaleManagerImplTest { @Test public void getVmStatsByIdFromHost() { List vmIds = Mockito.mock(ArrayList.class); - HashMap vmStatsById = Mockito.mock(HashMap.class); - when(userVmMgr.getVirtualMachineStatistics(anyLong(), anyString(), any())).thenReturn(vmStatsById); + Map vmStatsById = Mockito.mock(HashMap.class); + Mockito.doReturn(vmStatsById).when(virtualMachineManager).getVirtualMachineStatistics(anyLong(), anyString(), anyList()); - Map result = autoScaleManagerImplSpy.getVmStatsByIdFromHost(-1L, vmIds); + Map result = autoScaleManagerImplSpy.getVmStatsByIdFromHost(-1L, vmIds); Assert.assertEquals(0, result.size()); - Mockito.verify(userVmMgr, never()).getVirtualMachineStatistics(anyLong(), anyString(), any()); + Mockito.verify(virtualMachineManager, never()).getVirtualMachineStatistics(anyLong(), anyString(), anyList()); } @Test @@ -2223,13 +2230,13 @@ public class AutoScaleManagerImplTest { when(hostDao.findById(hostId)).thenReturn(hostMock); when(hostMock.getId()).thenReturn(hostId); when(hostMock.getName()).thenReturn(hostName); - when(userVmMgr.getVirtualMachineStatistics(anyLong(), anyString(), any())).thenReturn(vmStatsById); + Mockito.doReturn(vmStatsById).when(virtualMachineManager).getVirtualMachineStatistics(anyLong(), anyString(), anyList()); - Map result = autoScaleManagerImplSpy.getVmStatsByIdFromHost(hostId, vmIds); + Map result = autoScaleManagerImplSpy.getVmStatsByIdFromHost(hostId, vmIds); Assert.assertEquals(vmStatsById, result); - Mockito.verify(userVmMgr).getVirtualMachineStatistics(anyLong(), anyString(), any()); + Mockito.verify(virtualMachineManager).getVirtualMachineStatistics(anyLong(), anyString(), anyList()); } @Test diff --git a/server/src/test/java/com/cloud/server/StatsCollectorTest.java b/server/src/test/java/com/cloud/server/StatsCollectorTest.java index 206b6b81f7a..e010c8d1c2a 100644 --- a/server/src/test/java/com/cloud/server/StatsCollectorTest.java +++ b/server/src/test/java/com/cloud/server/StatsCollectorTest.java @@ -18,9 +18,10 @@ // package com.cloud.server; +import static org.mockito.Mockito.when; + import java.net.URI; import java.net.URISyntaxException; -import java.util.concurrent.TimeUnit; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; @@ -28,8 +29,10 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.concurrent.TimeUnit; import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.commons.collections.CollectionUtils; import org.influxdb.InfluxDB; import org.influxdb.InfluxDBFactory; import org.influxdb.dto.BatchPoints; @@ -43,6 +46,7 @@ import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; +import org.mockito.stubbing.Answer; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; @@ -50,17 +54,19 @@ import org.powermock.modules.junit4.PowerMockRunnerDelegate; import com.cloud.agent.api.VmDiskStatsEntry; import com.cloud.agent.api.VmStatsEntry; +import com.cloud.hypervisor.Hypervisor; import com.cloud.server.StatsCollector.ExternalStatsProtocol; +import com.cloud.storage.VolumeStatsVO; +import com.cloud.storage.dao.VolumeStatsDao; import com.cloud.user.VmDiskStatisticsVO; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.VmStats; import com.cloud.vm.VmStatsVO; import com.cloud.vm.dao.VmStatsDao; +import com.google.gson.Gson; import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; -import static org.mockito.Mockito.when; - @RunWith(PowerMockRunner.class) @PowerMockRunnerDelegate(DataProviderRunner.class) @PrepareForTest({InfluxDBFactory.class, BatchPoints.class}) @@ -97,6 +103,11 @@ public class StatsCollectorTest { @Mock VmStatsEntry vmStatsEntryMock; + @Mock + VolumeStatsDao volumeStatsDao; + + private static Gson gson = new Gson(); + @Test public void createInfluxDbConnectionTest() { configureAndTestCreateInfluxDbConnection(true); @@ -421,4 +432,61 @@ public class StatsCollectorTest { Assert.assertFalse(statsCollector.isDbLocal()); } + + private void performPersistVolumeStatsTest(Hypervisor.HypervisorType hypervisorType) { + Date timestamp = new Date(); + String vmName= "vm"; + String path = "path"; + long ioReadDiff = 100; + long ioWriteDiff = 200; + long readDiff = 1024; + long writeDiff = 0; + Long volumeId = 1L; + VmDiskStatsEntry statsForCurrentIteration = null; + if (Hypervisor.HypervisorType.KVM.equals(hypervisorType)) { + statsForCurrentIteration = new VmDiskStatsEntry(vmName, path, + 2000 + ioWriteDiff, + 1000 + ioReadDiff, + 20480 + writeDiff, + 10240 + readDiff); + statsForCurrentIteration.setDeltaIoRead(ioReadDiff); + statsForCurrentIteration.setDeltaIoWrite(ioWriteDiff); + statsForCurrentIteration.setDeltaBytesRead(readDiff); + statsForCurrentIteration.setDeltaBytesWrite(writeDiff); + } else { + statsForCurrentIteration = new VmDiskStatsEntry(vmName, path, + ioWriteDiff, + ioReadDiff, + writeDiff, + readDiff); + } + List persistedStats = new ArrayList<>(); + Mockito.when(volumeStatsDao.persist(Mockito.any(VolumeStatsVO.class))).thenAnswer((Answer) invocation -> { + VolumeStatsVO statsVO = (VolumeStatsVO)invocation.getArguments()[0]; + persistedStats.add(statsVO); + return statsVO; + }); + statsCollector.persistVolumeStats(volumeId, statsForCurrentIteration, hypervisorType, timestamp); + Assert.assertTrue(CollectionUtils.isNotEmpty(persistedStats)); + Assert.assertNotNull(persistedStats.get(0)); + VolumeStatsVO stat = persistedStats.get(0); + Assert.assertEquals(volumeId, stat.getVolumeId()); + VmDiskStatsEntry entry = gson.fromJson(stat.getVolumeStatsData(), VmDiskStatsEntry.class); + Assert.assertEquals(vmName, entry.getVmName()); + Assert.assertEquals(path, entry.getPath()); + Assert.assertEquals(ioReadDiff, entry.getIORead()); + Assert.assertEquals(ioWriteDiff, entry.getIOWrite()); + Assert.assertEquals(readDiff, entry.getBytesRead()); + Assert.assertEquals(writeDiff, entry.getBytesWrite()); + } + + @Test + public void testPersistVolumeStatsKVM() { + performPersistVolumeStatsTest(Hypervisor.HypervisorType.KVM); + } + + @Test + public void testPersistVolumeStatsVmware() { + performPersistVolumeStatsTest(Hypervisor.HypervisorType.VMware); + } } diff --git a/test/integration/smoke/test_metrics_api.py b/test/integration/smoke/test_metrics_api.py index 3ff602c1a71..d5ad559fad0 100644 --- a/test/integration/smoke/test_metrics_api.py +++ b/test/integration/smoke/test_metrics_api.py @@ -23,6 +23,7 @@ from marvin.lib.base import * from marvin.lib.common import * from marvin.lib.utils import (random_gen) from nose.plugins.attrib import attr +from marvin.lib.decoratorGenerators import skipTestIf import time @@ -42,6 +43,7 @@ class TestMetrics(cloudstackTestCase): zoneid=cls.zone.id, type='Routing')[0] cls.cluster = cls.apiclient.listClusters(listClusters.listClustersCmd())[0] + cls.mgtSvrDetails = cls.config.__dict__["mgtSvr"][0].__dict__ cls._cleanup = [] cls.disk_offering = DiskOffering.create( cls.apiclient, @@ -52,12 +54,12 @@ class TestMetrics(cloudstackTestCase): cls.apiclient, cls.services["service_offering"] ) + cls._cleanup.append(cls.service_offering) cls.template = get_test_template( cls.apiclient, cls.zone.id, cls.hypervisor ) - cls._cleanup.append(cls.service_offering) cls.domain = get_domain(cls.apiclient) cls.account = Account.create( cls.apiclient, @@ -66,11 +68,80 @@ class TestMetrics(cloudstackTestCase): domainid=cls.domain.id ) cls._cleanup.append(cls.account) + cls.hypervisorNotSupported = True + if cls.hypervisor.lower() != 'simulator': + cls.hypervisorNotSupported = False + cls.vm_stats_interval_cfg = Configurations.list(cls.apiclient, name='vm.stats.interval')[0].value + cls.vm_stats_max_retention_time_cfg = Configurations.list(cls.apiclient, name='vm.stats.max.retention.time')[0].value + cls.vm_stats_user_vm_only_cfg = Configurations.list(cls.apiclient, name='vm.stats.user.vm.only')[0].value + cls.vm_disk_stats_interval_cfg = Configurations.list(cls.apiclient, name='vm.disk.stats.interval')[0].value + cls.vm_disk_stats_interval_min_cfg = Configurations.list(cls.apiclient, name='vm.disk.stats.interval.min')[0].value + cls.vm_disk_stats_max_retention_time_cfg = Configurations.list(cls.apiclient, name='vm.disk.stats.max.retention.time')[0].value + cls.vm_disk_stats_retention_enabled_cfg = Configurations.list(cls.apiclient, name='vm.disk.stats.retention.enabled')[0].value + Configurations.update(cls.apiclient, 'vm.stats.interval', value='60000') + Configurations.update(cls.apiclient, 'vm.stats.max.retention.time', value='7200') + Configurations.update(cls.apiclient, 'vm.stats.user.vm.only', value='false') + Configurations.update(cls.apiclient, 'vm.disk.stats.interval', value='60') + Configurations.update(cls.apiclient, 'vm.disk.stats.interval.min', value='60') + Configurations.update(cls.apiclient, 'vm.disk.stats.max.retention.time', value='7200') + Configurations.update(cls.apiclient, 'vm.disk.stats.retention.enabled', value='true') + cls.restartServer() @classmethod def tearDownClass(cls): + if cls.hypervisor.lower() != 'simulator': + cls.updateConfiguration('vm.stats.interval', cls.vm_stats_interval_cfg) + cls.updateConfiguration('vm.stats.max.retention.time', cls.vm_stats_max_retention_time_cfg) + cls.updateConfiguration('vm.stats.user.vm.only', cls.vm_stats_user_vm_only_cfg) + cls.updateConfiguration('vm.disk.stats.interval', cls.vm_disk_stats_interval_cfg) + cls.updateConfiguration('vm.disk.stats.interval.min', cls.vm_disk_stats_interval_min_cfg) + cls.updateConfiguration('vm.disk.stats.max.retention.time', cls.vm_disk_stats_max_retention_time_cfg) + cls.updateConfiguration('vm.disk.stats.retention.enabled', cls.vm_disk_stats_retention_enabled_cfg) + cls.restartServer() super(TestMetrics, cls).tearDownClass() + @classmethod + def restartServer(cls): + """Restart management server""" + + cls.debug("Restarting management server") + sshClient = SshClient( + cls.mgtSvrDetails["mgtSvrIp"], + 22, + cls.mgtSvrDetails["user"], + cls.mgtSvrDetails["passwd"] + ) + command = "service cloudstack-management stop" + sshClient.execute(command) + + command = "service cloudstack-management start" + sshClient.execute(command) + + #Waits for management to come up in 5 mins, when it's up it will continue + timeout = time.time() + 300 + while time.time() < timeout: + if cls.isManagementUp() is True: + # allow hosts to be ready for deployment + time.sleep(30) + return + time.sleep(5) + cls.setup_failed = True + cls.debug("Management server did not come up, failing") + return + + @classmethod + def isManagementUp(cls): + try: + cls.apiclient.listInfrastructure(listInfrastructure.listInfrastructureCmd()) + return True + except Exception: + return False + + @classmethod + def updateConfiguration(cls, config, value): + if value is not None: + Configurations.update(cls.apiclient, config, value=value) + def setUp(self): self.userapiclient = self.testClient.getUserApiClient( UserName=self.account.name, @@ -389,6 +460,111 @@ class TestMetrics(cloudstackTestCase): return + @attr(tags = ["advanced", "advancedns", "smoke", "basic"], required_hardware="true") + @skipTestIf("hypervisorNotSupported") + def test_list_vms_metrics_history(self): + #deploy VM + self.small_virtual_machine = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + serviceofferingid=self.service_offering.id, + templateid=self.template.id, + zoneid=self.zone.id + ) + self.cleanup.append(self.small_virtual_machine) + + # Wait for 2 minutes + time.sleep(120) + + cmd = listVirtualMachinesUsageHistory.listVirtualMachinesUsageHistoryCmd() + cmd.id = self.small_virtual_machine.id + + result = self.apiclient.listVirtualMachinesUsageHistory(cmd)[0] + + self.assertEqual(result.id, self.small_virtual_machine.id) + self.assertTrue(hasattr(result, 'stats')) + self.assertTrue(type(result.stats) == list and len(result.stats) > 0) + self.validate_vm_stats(result.stats[0]) + + return + + @attr(tags = ["advanced", "advancedns", "smoke", "basic"], required_hardware="true") + @skipTestIf("hypervisorNotSupported") + def test_list_system_vms_metrics_history(self): + cmd = listSystemVmsUsageHistory.listSystemVmsUsageHistoryCmd() + now = datetime.datetime.now() - datetime.timedelta(minutes=15) + start_time = now.strftime("%Y-%m-%d %H:%M:%S") + cmd.startdate = start_time + + result = self.apiclient.listSystemVmsUsageHistory(cmd)[0] + + self.assertTrue(hasattr(result, 'stats')) + self.assertTrue(type(result.stats) == list and len(result.stats) > 0) + self.validate_vm_stats(result.stats[0]) + + return + + @attr(tags = ["advanced", "advancedns", "smoke", "basic"], required_hardware="true") + @skipTestIf("hypervisorNotSupported") + def test_list_volumes_metrics_history(self): + #deploy VM + self.small_virtual_machine = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + serviceofferingid=self.service_offering.id, + templateid=self.template.id, + zoneid=self.zone.id + ) + self.cleanup.append(self.small_virtual_machine) + + currentHost = Host.list(self.apiclient, id=self.small_virtual_machine.hostid)[0] + if currentHost.hypervisor.lower() == "xenserver" and currentHost.hypervisorversion == "7.1.0": + # Skip tests as volume metrics doesn't see to work + self.skipTest("Skipping test because volume metrics doesn't work on hypervisor\ + %s, %s" % (currentHost.hypervisor, currentHost.hypervisorversion)) + + # Wait for 2 minutes + time.sleep(120) + + volume = Volume.list( + self.apiclient, + virtualmachineid=self.small_virtual_machine.id)[0] + + cmd = listVolumesUsageHistory.listVolumesUsageHistoryCmd() + cmd.id = volume.id + + result = self.apiclient.listVolumesUsageHistory(cmd)[0] + self.assertEqual(result.id, volume.id) + self.assertTrue(hasattr(result, 'stats')) + self.assertTrue(type(result.stats) == list and len(result.stats) > 0) + stats = result.stats[0] + self.assertTrue(hasattr(stats, 'diskioread')) + self.assertTrue(hasattr(stats, 'diskiowrite')) + self.assertTrue(hasattr(stats, 'diskiopstotal')) + self.assertTrue(hasattr(stats, 'diskkbsread')) + self.assertTrue(hasattr(stats, 'diskkbswrite')) + self.assertTrue(hasattr(stats, 'timestamp')) + self.assertTrue(self.valid_date(stats.timestamp)) + + return + + def validate_vm_stats(self, stats): + self.assertTrue(hasattr(stats, 'cpuused')) + self.assertTrue(hasattr(stats, 'diskiopstotal')) + self.assertTrue(hasattr(stats, 'diskioread')) + self.assertTrue(hasattr(stats, 'diskiowrite')) + self.assertTrue(hasattr(stats, 'diskkbsread')) + self.assertTrue(hasattr(stats, 'diskkbswrite')) + self.assertTrue(hasattr(stats, 'memoryintfreekbs')) + self.assertTrue(hasattr(stats, 'memorykbs')) + self.assertTrue(hasattr(stats, 'memorytargetkbs')) + self.assertTrue(hasattr(stats, 'networkkbsread')) + self.assertTrue(hasattr(stats, 'networkkbswrite')) + self.assertTrue(hasattr(stats, 'networkread')) + self.assertTrue(hasattr(stats, 'networkwrite')) + self.assertTrue(hasattr(stats, 'timestamp')) + self.assertTrue(self.valid_date(stats.timestamp)) + def valid_date(cls, date_text): try: datetime.datetime.strptime(date_text, '%Y-%m-%dT%H:%M:%S%z') diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 1154af55557..a4d0d0b6197 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -416,6 +416,8 @@ "label.community": "Community", "label.complete": "Complete", "label.compute": "Compute", +"label.computeonly.offering": "Compute only disk offering", +"label.computeonly.offering.tooltip": "Option to specify root disk related information in the compute offering or to directly link a disk offering to the compute offering", "label.compute.offerings": "Compute offerings", "label.conditions": "Conditions", "label.configuration": "Configuration", @@ -616,10 +618,12 @@ "label.diskoffering": "Disk offering", "label.diskofferingdisplaytext": "Disk offering", "label.diskofferingid": "Disk offering", +"label.diskofferingstrictness": "Disk offering strictness", "label.disksize": "Disk size (in GB)", "label.disksizeallocated": "Disk allocated", "label.disksizeallocatedgb": "Allocated", "label.disksizefree": "Disk free", +"label.disksizestrictness": "Disk size strictness", "label.disksizetotal": "Disk total", "label.disksizetotalgb": "Total", "label.disksizeunallocatedgb": "Unallocated", @@ -654,16 +658,14 @@ "label.dpd": "Dead peer detection", "label.driver": "Driver", "label.duration": "Duration (in sec)", +"label.duration.custom": "Custom", +"label.duration.1hour": "1 hour", +"label.duration.6hours": "6 hours", +"label.duration.12hours": "12 hours", +"label.duration.24hours": "24 hours", +"label.duration.7days": "7 days", "label.dynamicscalingenabled": "Dynamic scaling enabled", "label.dynamicscalingenabled.tooltip": "VM can dynamically scale only when dynamic scaling is enabled on template, service offering and global setting.", -"label.iothreadsenabled" : "IOThreads", -"label.iothreadsenabled.tooltip" : "Enable iothreads allocation for KVM hypervisor", -"label.iodriverpolicy" : "IO driver policy", -"label.iodriverpolicy.tooltip" : "IO driver policy could be native, io_uring or threads. Choosing the IO policy for a VM will override the storage pool option 'kvm.storage.pool.io.policy' if set (only if iothreads is enabled)", -"label.diskofferingstrictness": "Disk offering strictness", -"label.disksizestrictness": "Disk size strictness", -"label.computeonly.offering": "Compute only disk offering", -"label.computeonly.offering.tooltip": "Option to specify root disk related information in the compute offering or to directly link a disk offering to the compute offering", "label.edit": "Edit", "label.edit.acl.list": "Edit ACL list", "label.edit.acl.rule": "Edit ACL rule", @@ -892,6 +894,11 @@ "label.invalid.number": "Invalid number", "label.invitations": "Invitations", "label.invite": "Invite", +"label.iodriverpolicy" : "IO driver policy", +"label.iodriverpolicy.tooltip" : "IO driver policy could be native, io_uring or threads. Choosing the IO policy for a VM will override the storage pool option 'kvm.storage.pool.io.policy' if set (only if iothreads is enabled)", +"label.iops": "IOPS", +"label.iothreadsenabled" : "IOThreads", +"label.iothreadsenabled.tooltip" : "Enable iothreads allocation for KVM hypervisor", "label.ip": "IP address", "label.ip6firewall": "IPv6 firewall", "label.ip6routes": "IPv6 routes", @@ -1681,7 +1688,6 @@ "label.startquota": "Quota value", "label.state": "State", "label.static.routes": "Static routes", -"label.statistics": "Statistics", "label.status": "Status", "label.step.1": "Step 1", "label.step.2": "Step 2", diff --git a/ui/src/components/view/StatsTab.vue b/ui/src/components/view/StatsTab.vue index 45026c0ff16..27595223899 100644 --- a/ui/src/components/view/StatsTab.vue +++ b/ui/src/components/view/StatsTab.vue @@ -34,32 +34,58 @@ :footer="null"> - - - +
+ +
+ + + {{ $t('1 hour') }} + + + {{ $t('label.duration.6hours') }} + + + {{ $t('label.duration.12hours') }} + + + {{ $t('label.duration.24hours') }} + + + {{ $t('label.duration.7days') }} + + + {{ $t('label.duration.custom') }} + + + +
+
- - - - +
+
+
- + CPU - - + {{ $t('label.memory') }} @@ -78,51 +104,107 @@ {{ unit }} - - - - - - - + + + {{ $t('label.disk') }} + +
+ {{ $t('label.iops') }} +
+ +
+ {{ $t('label.read.and.write') }} + + + {{ unit }} + + +
+ + + +
+
+ {{ $t('label.network') }} @@ -131,72 +213,29 @@ {{ unit }} - - - - - - - - {{ $t('label.disk') }} - - - - {{ type }} - - - - - {{ unit }} - - - - - - @@ -214,22 +253,27 @@ import moment from 'moment' import 'chartjs-adapter-moment' import FilterStats from './stats/FilterStats' import ResourceStatsInfo from './stats/ResourceStatsInfo' -import LineChart from './chart/LineChart' +import ResourceStatsLineChart from './stats/ResourceStatsLineChart' export default { props: { resource: { type: Object, required: true + }, + resourceType: { + type: String, + default: 'VirtualMachine' } }, components: { FilterStats, ResourceStatsInfo, - LineChart + ResourceStatsLineChart }, data () { return { + durationSelectorValue: '', resourceTypeToShowInfo: null, showResourceInfoModal: false, resourceInfoModalTitle: null, @@ -289,6 +333,37 @@ export default { mounted () { this.fetchData() }, + computed: { + statsRetentionTime () { + if (this.resourceType === 'Volume') { + return this.$store.getters.features.instancesdisksstatsretentiontime + } + return this.$store.getters.features.instancesstatsretentiontime + }, + resourceStatsApi () { + switch (this.resourceType) { + case 'SystemVm': + case 'DomainRouter': + return 'listSystemVmsUsageHistory' + case 'Volume': + return 'listVolumesUsageHistory' + } + return 'listVirtualMachinesUsageHistory' + }, + resourceStatsApiResponseObject () { + switch (this.resourceType) { + case 'Volume': + return this.resourceType.toLowerCase() + } + return 'virtualmachine' + }, + resourceIsVirtualMachine () { + return ['VirtualMachine', 'SystemVm', 'DomainRouter'].includes(this.resourceType) + }, + diskStatsAvailable () { + return ['VirtualMachine', 'SystemVm', 'DomainRouter', 'Volume'].includes(this.resourceType) + } + }, watch: { resource: function (newItem) { if (!newItem || !newItem.id) { @@ -322,6 +397,27 @@ export default { this.resourceTypeToShowInfo = resource this.showResourceInfoModal = true }, + handleDurationChange () { + var now = this.getEndDate() + var start = new Date(now) + switch (this.durationSelectorValue) { + case '6hours': + start.setHours(start.getHours() - 6) + break + case '12hours': + start.setHours(start.getHours() - 12) + break + case 'day': + start.setDate(start.getDate() - 1) + break + case 'week': + start.setDate(start.getDate() - 7) + break + default: + start.setHours(start.getHours() - 1) + } + this.handleSubmit({ startDate: start, endDate: now }) + }, handleSubmit (values) { if (values.startDate) { this.startDate = new Date(values.startDate) @@ -358,7 +454,7 @@ export default { if (this.endDate) { params.endDate = moment(this.endDate).format() } - api('listVirtualMachinesUsageHistory', params).then(response => { + api(this.resourceStatsApi, params).then(response => { this.handleStatsResponse(response) }).catch(error => { this.$notifyError(error) @@ -385,7 +481,7 @@ export default { }, handleStatsResponse (responseData) { this.resetData() - const vm = responseData.listvirtualmachinesusagehistoryresponse.virtualmachine + const vm = responseData[this.resourceStatsApi.toLowerCase() + 'response'][this.resourceStatsApiResponseObject] const chartPointRadius = this.getChartPointRadius(vm[0].stats.length) @@ -424,61 +520,69 @@ export default { const currentLabel = ts.split('T')[0] + ' ' + ts.split('T')[1].split('-')[0] this.chartLabels.push(currentLabel) - cpuLine.data.push({ timestamp: currentLabel, stat: element.cpuused.split('%')[0] }) + if (this.resourceIsVirtualMachine) { + cpuLine.data.push({ timestamp: currentLabel, stat: element.cpuused.split('%')[0] }) - element.memoryusedkbs = element.memorykbs - element.memoryintfreekbs - memFreeLinePercent.data.push({ timestamp: currentLabel, stat: this.calculateMemoryPercentage(false, element.memorykbs, element.memoryintfreekbs) }) - memUsedLinePercent.data.push({ timestamp: currentLabel, stat: this.calculateMemoryPercentage(true, element.memorykbs, element.memoryintfreekbs) }) - memAllocatedLineInMB.data.push({ timestamp: currentLabel, stat: this.convertByteBasedUnitOfMeasure(element.memorykbs, 1) }) - memFreeLineInMB.data.push({ timestamp: currentLabel, stat: this.convertByteBasedUnitOfMeasure(element.memoryintfreekbs, 1) }) - memUsedLineInMB.data.push({ timestamp: currentLabel, stat: this.convertByteBasedUnitOfMeasure(element.memoryusedkbs, 1) }) - memAllocatedLineInGB.data.push({ timestamp: currentLabel, stat: this.convertByteBasedUnitOfMeasure(element.memorykbs, 2) }) - memFreeLineInGB.data.push({ timestamp: currentLabel, stat: this.convertByteBasedUnitOfMeasure(element.memoryintfreekbs, 2) }) - memUsedLineInGB.data.push({ timestamp: currentLabel, stat: this.convertByteBasedUnitOfMeasure(element.memoryusedkbs, 2) }) + element.memoryusedkbs = element.memorykbs - element.memoryintfreekbs + memFreeLinePercent.data.push({ timestamp: currentLabel, stat: this.calculateMemoryPercentage(false, element.memorykbs, element.memoryintfreekbs) }) + memUsedLinePercent.data.push({ timestamp: currentLabel, stat: this.calculateMemoryPercentage(true, element.memorykbs, element.memoryintfreekbs) }) + memAllocatedLineInMB.data.push({ timestamp: currentLabel, stat: this.convertByteBasedUnitOfMeasure(element.memorykbs, 1) }) + memFreeLineInMB.data.push({ timestamp: currentLabel, stat: this.convertByteBasedUnitOfMeasure(element.memoryintfreekbs, 1) }) + memUsedLineInMB.data.push({ timestamp: currentLabel, stat: this.convertByteBasedUnitOfMeasure(element.memoryusedkbs, 1) }) + memAllocatedLineInGB.data.push({ timestamp: currentLabel, stat: this.convertByteBasedUnitOfMeasure(element.memorykbs, 2) }) + memFreeLineInGB.data.push({ timestamp: currentLabel, stat: this.convertByteBasedUnitOfMeasure(element.memoryintfreekbs, 2) }) + memUsedLineInGB.data.push({ timestamp: currentLabel, stat: this.convertByteBasedUnitOfMeasure(element.memoryusedkbs, 2) }) - netDownloadLineInKiB.data.push({ timestamp: currentLabel, stat: element.networkkbsread }) - netUploadLineInKiB.data.push({ timestamp: currentLabel, stat: element.networkkbswrite }) - netDownloadLineInMiB.data.push({ timestamp: currentLabel, stat: this.convertByteBasedUnitOfMeasure(element.networkkbsread, 1) }) - netUploadLineInMiB.data.push({ timestamp: currentLabel, stat: this.convertByteBasedUnitOfMeasure(element.networkkbswrite, 1) }) - netDownloadLineInGiB.data.push({ timestamp: currentLabel, stat: this.convertByteBasedUnitOfMeasure(element.networkkbsread, 2) }) - netUploadLineInGiB.data.push({ timestamp: currentLabel, stat: this.convertByteBasedUnitOfMeasure(element.networkkbswrite, 2) }) + netDownloadLineInKiB.data.push({ timestamp: currentLabel, stat: element.networkkbsread }) + netUploadLineInKiB.data.push({ timestamp: currentLabel, stat: element.networkkbswrite }) + netDownloadLineInMiB.data.push({ timestamp: currentLabel, stat: this.convertByteBasedUnitOfMeasure(element.networkkbsread, 1) }) + netUploadLineInMiB.data.push({ timestamp: currentLabel, stat: this.convertByteBasedUnitOfMeasure(element.networkkbswrite, 1) }) + netDownloadLineInGiB.data.push({ timestamp: currentLabel, stat: this.convertByteBasedUnitOfMeasure(element.networkkbsread, 2) }) + netUploadLineInGiB.data.push({ timestamp: currentLabel, stat: this.convertByteBasedUnitOfMeasure(element.networkkbswrite, 2) }) + } - diskReadLineInKiB.data.push({ timestamp: currentLabel, stat: element.diskkbsread }) - diskWriteLineInKiB.data.push({ timestamp: currentLabel, stat: element.diskkbswrite }) - diskReadLineInMiB.data.push({ timestamp: currentLabel, stat: this.convertByteBasedUnitOfMeasure(element.diskkbsread, 1) }) - diskWriteLineInMiB.data.push({ timestamp: currentLabel, stat: this.convertByteBasedUnitOfMeasure(element.diskkbswrite, 1) }) - diskReadLineInGiB.data.push({ timestamp: currentLabel, stat: this.convertByteBasedUnitOfMeasure(element.diskkbsread, 2) }) - diskWriteLineInGiB.data.push({ timestamp: currentLabel, stat: this.convertByteBasedUnitOfMeasure(element.diskkbswrite, 2) }) - diskIopsLine.data.push({ timestamp: currentLabel, stat: element.diskiopstotal }) + if (this.diskStatsAvailable) { + diskReadLineInKiB.data.push({ timestamp: currentLabel, stat: element.diskkbsread }) + diskWriteLineInKiB.data.push({ timestamp: currentLabel, stat: element.diskkbswrite }) + diskReadLineInMiB.data.push({ timestamp: currentLabel, stat: this.convertByteBasedUnitOfMeasure(element.diskkbsread, 1) }) + diskWriteLineInMiB.data.push({ timestamp: currentLabel, stat: this.convertByteBasedUnitOfMeasure(element.diskkbswrite, 1) }) + diskReadLineInGiB.data.push({ timestamp: currentLabel, stat: this.convertByteBasedUnitOfMeasure(element.diskkbsread, 2) }) + diskWriteLineInGiB.data.push({ timestamp: currentLabel, stat: this.convertByteBasedUnitOfMeasure(element.diskkbswrite, 2) }) + diskIopsLine.data.push({ timestamp: currentLabel, stat: element.diskiopstotal }) + } } - this.resourceUsageHistory.cpu.push(cpuLine) + if (this.resourceIsVirtualMachine) { + this.resourceUsageHistory.cpu.push(cpuLine) - this.resourceUsageHistory.memory.percentage.free.push(memFreeLinePercent) - this.resourceUsageHistory.memory.percentage.used.push(memUsedLinePercent) - this.resourceUsageHistory.memory.rawData.free.inMB.push(memFreeLineInMB) - this.resourceUsageHistory.memory.rawData.free.inMB.push(memAllocatedLineInMB) - this.resourceUsageHistory.memory.rawData.used.inMB.push(memUsedLineInMB) - this.resourceUsageHistory.memory.rawData.used.inMB.push(memAllocatedLineInMB) - this.resourceUsageHistory.memory.rawData.free.inGB.push(memFreeLineInGB) - this.resourceUsageHistory.memory.rawData.free.inGB.push(memAllocatedLineInGB) - this.resourceUsageHistory.memory.rawData.used.inGB.push(memUsedLineInGB) - this.resourceUsageHistory.memory.rawData.used.inGB.push(memAllocatedLineInGB) + this.resourceUsageHistory.memory.percentage.free.push(memFreeLinePercent) + this.resourceUsageHistory.memory.percentage.used.push(memUsedLinePercent) + this.resourceUsageHistory.memory.rawData.free.inMB.push(memFreeLineInMB) + this.resourceUsageHistory.memory.rawData.free.inMB.push(memAllocatedLineInMB) + this.resourceUsageHistory.memory.rawData.used.inMB.push(memUsedLineInMB) + this.resourceUsageHistory.memory.rawData.used.inMB.push(memAllocatedLineInMB) + this.resourceUsageHistory.memory.rawData.free.inGB.push(memFreeLineInGB) + this.resourceUsageHistory.memory.rawData.free.inGB.push(memAllocatedLineInGB) + this.resourceUsageHistory.memory.rawData.used.inGB.push(memUsedLineInGB) + this.resourceUsageHistory.memory.rawData.used.inGB.push(memAllocatedLineInGB) - this.resourceUsageHistory.network.inKiB.push(netDownloadLineInKiB) - this.resourceUsageHistory.network.inKiB.push(netUploadLineInKiB) - this.resourceUsageHistory.network.inMiB.push(netDownloadLineInMiB) - this.resourceUsageHistory.network.inMiB.push(netUploadLineInMiB) - this.resourceUsageHistory.network.inGiB.push(netDownloadLineInGiB) - this.resourceUsageHistory.network.inGiB.push(netUploadLineInGiB) + this.resourceUsageHistory.network.inKiB.push(netDownloadLineInKiB) + this.resourceUsageHistory.network.inKiB.push(netUploadLineInKiB) + this.resourceUsageHistory.network.inMiB.push(netDownloadLineInMiB) + this.resourceUsageHistory.network.inMiB.push(netUploadLineInMiB) + this.resourceUsageHistory.network.inGiB.push(netDownloadLineInGiB) + this.resourceUsageHistory.network.inGiB.push(netUploadLineInGiB) + } - this.resourceUsageHistory.disk.readAndWrite.inKiB.push(diskReadLineInKiB) - this.resourceUsageHistory.disk.readAndWrite.inKiB.push(diskWriteLineInKiB) - this.resourceUsageHistory.disk.readAndWrite.inMiB.push(diskReadLineInMiB) - this.resourceUsageHistory.disk.readAndWrite.inMiB.push(diskWriteLineInMiB) - this.resourceUsageHistory.disk.readAndWrite.inGiB.push(diskReadLineInGiB) - this.resourceUsageHistory.disk.readAndWrite.inGiB.push(diskWriteLineInGiB) - this.resourceUsageHistory.disk.iops.push(diskIopsLine) + if (this.diskStatsAvailable) { + this.resourceUsageHistory.disk.readAndWrite.inKiB.push(diskReadLineInKiB) + this.resourceUsageHistory.disk.readAndWrite.inKiB.push(diskWriteLineInKiB) + this.resourceUsageHistory.disk.readAndWrite.inMiB.push(diskReadLineInMiB) + this.resourceUsageHistory.disk.readAndWrite.inMiB.push(diskWriteLineInMiB) + this.resourceUsageHistory.disk.readAndWrite.inGiB.push(diskReadLineInGiB) + this.resourceUsageHistory.disk.readAndWrite.inGiB.push(diskWriteLineInGiB) + this.resourceUsageHistory.disk.iops.push(diskIopsLine) + } this.loaded = true }, @@ -551,128 +655,6 @@ export default { return parseFloat(100.0 * (memoryTotalInKB - memoryFreeInKB) / memoryTotalInKB).toFixed(2) } return parseFloat(100.0 * memoryFreeInKB / memoryTotalInKB).toFixed(2) - }, - /** - * Calculates the maximum Y axis and the step size based on the chart data. - * @param chartLines the chart lines with their respective data. - * @param initialMaxValue the initial maximum value to the Y axis. - * @param incrementValue the increment value. - * @returns an object containing the maximum Y axis and the step size for the chart. - */ - calculateMaxYAxisAndStepSize (chartLines, initialMaxYAxis, incrementValue) { - const numberOfLabelsOnYaxis = 4 - var highestValue = 0 - var maxYAxis = initialMaxYAxis - for (const line of chartLines) { - for (const d of line.data) { - const currentValue = parseFloat(d.stat) - if (currentValue > highestValue) { - highestValue = currentValue - while (highestValue > maxYAxis) { - maxYAxis += incrementValue - } - } - } - } - return { maxYAxes: maxYAxis, stepSize: maxYAxis / numberOfLabelsOnYaxis } - }, - /** - * Returns the chart options. - * @param yAxesStepSize the step size for the Y axes. - * @param yAxesUnitOfMeasurement the unit of measurement label used on the Y axes. - * @returns the chart options. - */ - getChartOptions (yAxesOptions, yAxesUnitOfMeasurement) { - var chartOptions = { - responsive: true, - maintainAspectRatio: false, - scales: { - yAxis: { - min: 0, - max: yAxesOptions.maxYAxes, - reverse: false, - ticks: { - stepSize: yAxesOptions.stepSize, - callback: function (label) { - return label + yAxesUnitOfMeasurement - } - } - }, - xAxis: { - type: 'time', - autoSkip: false, - time: { - parser: 'YYYY-MM-DD HH:mm:ss', - unit: 'second', - displayFormats: { - second: 'HH:mm:ss' - } - } - } - } - } - const dateTimes = this.convertStringArrayToDateArray(JSON.parse(JSON.stringify(this.chartLabels))) - const averageDifference = this.averageDifferenceBetweenTimes(dateTimes) - chartOptions.scales.xAxis.time.stepSize = this.calculateStepSize(this.chartLabels.length, averageDifference) - return chartOptions - }, - convertStringArrayToDateArray (stringArray) { - const dateArray = [] - for (const element of stringArray) { - dateArray.push(new Date(element.replace(' ', 'T'))) - } - return dateArray - }, - averageDifferenceBetweenTimes (timeList) { - const oneSecond = 1000 // 1 second represented as milliseconds - const differences = [] - var previus = timeList.splice(0, 1)[0] - for (const time of timeList) { - differences.push((time - previus) / oneSecond) // push the difference in seconds - previus = time - } - if (differences.length === 0) { - return 1 - } - const averageDifference = Math.trunc(differences.reduce((a, b) => a + b, 0) / differences.length) - return averageDifference - }, - calculateStepSize (numberOfDataPoints, differenceBetweenTimes) { - const idealNumberOfLabels = 8 - const result = numberOfDataPoints / idealNumberOfLabels - if (result > 1) { - return result * differenceBetweenTimes - } - return differenceBetweenTimes - }, - prepareData (chartData) { - const datasetList = [] - for (const element of chartData) { - datasetList.push( - { - backgroundColor: element.backgroundColor, - borderColor: element.borderColor, - borderWidth: 3, - label: element.label, - data: element.data.map(d => d.stat), - hidden: this.hideLine(element.data.map(d => d.stat)), - pointRadius: element.pointRadius, - fill: 'origin' - } - ) - } - return { - labels: this.chartLabels, - datasets: datasetList - } - }, - hideLine (data) { - for (const d of data) { - if (d < 0) { - return true - } - } - return false } } } diff --git a/ui/src/components/view/chart/LineChart.vue b/ui/src/components/view/chart/LineChart.vue deleted file mode 100644 index 1b9206917d7..00000000000 --- a/ui/src/components/view/chart/LineChart.vue +++ /dev/null @@ -1,55 +0,0 @@ -// 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. - - - - diff --git a/ui/src/components/view/stats/ResourceStatsInfo.vue b/ui/src/components/view/stats/ResourceStatsInfo.vue index be5f3a16bac..9db3384bc5f 100644 --- a/ui/src/components/view/stats/ResourceStatsInfo.vue +++ b/ui/src/components/view/stats/ResourceStatsInfo.vue @@ -20,12 +20,7 @@
  • -
    - {{ msg }}. -
    -
    - {{ msg }}; -
    + {{ msg }}
@@ -88,6 +83,9 @@ export default { for (const element of this.info) { if (element.resourceType === this.resourceType) { this.messages = element.messageList + if (this.$route.fullPath.startsWith('/volume/')) { + this.messages = this.messages.filter(x => x !== this.$t('message.disk.usage.info.sum.of.disks')) + } } } } diff --git a/ui/src/components/view/stats/ResourceStatsLineChart.vue b/ui/src/components/view/stats/ResourceStatsLineChart.vue new file mode 100644 index 00000000000..fa15ea398a5 --- /dev/null +++ b/ui/src/components/view/stats/ResourceStatsLineChart.vue @@ -0,0 +1,239 @@ +// 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. + + + + diff --git a/ui/src/config/section/infra/routers.js b/ui/src/config/section/infra/routers.js index 498f8ac1176..6f24d6b7d82 100644 --- a/ui/src/config/section/infra/routers.js +++ b/ui/src/config/section/infra/routers.js @@ -36,6 +36,11 @@ export default { tabs: [{ name: 'details', component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue'))) + }, { + name: 'metrics', + resourceType: 'DomainRouter', + component: shallowRef(defineAsyncComponent(() => import('@/components/view/StatsTab.vue'))), + show: () => { return store.getters.features.instancesstatsuseronly === false } }, { name: 'nics', component: shallowRef(defineAsyncComponent(() => import('@/views/network/NicsTable.vue'))) diff --git a/ui/src/config/section/infra/systemVms.js b/ui/src/config/section/infra/systemVms.js index 9774e2290c7..a649ef1c11a 100644 --- a/ui/src/config/section/infra/systemVms.js +++ b/ui/src/config/section/infra/systemVms.js @@ -32,6 +32,12 @@ export default { name: 'details', component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue'))) }, + { + name: 'metrics', + resourceType: 'SystemVm', + component: shallowRef(defineAsyncComponent(() => import('@/components/view/StatsTab.vue'))), + show: () => { return store.getters.features.instancesstatsuseronly === false } + }, { name: 'volume', component: shallowRef(defineAsyncComponent(() => import('@/components/view/VolumesTab.vue'))) diff --git a/ui/src/config/section/storage.js b/ui/src/config/section/storage.js index 917027c78b3..e8a5ecd8128 100644 --- a/ui/src/config/section/storage.js +++ b/ui/src/config/section/storage.js @@ -75,6 +75,12 @@ export default { name: 'details', component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue'))) }, + { + name: 'metrics', + resourceType: 'Volume', + component: shallowRef(defineAsyncComponent(() => import('@/components/view/StatsTab.vue'))), + show: (record) => { return store.getters.features.instancesdisksstatsretentionenabled } + }, { name: 'events', resourceType: 'Volume', diff --git a/ui/src/core/lazy_lib/components_use.js b/ui/src/core/lazy_lib/components_use.js index b28f26b787f..10790d61bc0 100644 --- a/ui/src/core/lazy_lib/components_use.js +++ b/ui/src/core/lazy_lib/components_use.js @@ -62,7 +62,8 @@ import { Calendar, Slider, AutoComplete, - Collapse + Collapse, + Space } from 'ant-design-vue' import VueClipboard from 'vue3-clipboard' import VueCropper from 'vue-cropper' @@ -125,5 +126,6 @@ export default { app.use(AutoComplete) app.use(Collapse) app.use(Descriptions) + app.use(Space) } } diff --git a/ui/src/style/components/view/StatsTab.scss b/ui/src/style/components/view/StatsTab.scss index 89e51ab0cb9..5ad02966bbd 100644 --- a/ui/src/style/components/view/StatsTab.scss +++ b/ui/src/style/components/view/StatsTab.scss @@ -25,7 +25,10 @@ margin: 0 10px 0 5px; } .chart-row { - margin-bottom: 10%; + margin-bottom: 5%; +} +.chart-row-inner { + margin-top: 3%; } .chart-type-select { min-width: 130px; diff --git a/ui/src/views/compute/InstanceTab.vue b/ui/src/views/compute/InstanceTab.vue index b0dff1c2158..2b1572b1c16 100644 --- a/ui/src/views/compute/InstanceTab.vue +++ b/ui/src/views/compute/InstanceTab.vue @@ -25,7 +25,7 @@ - +