diff --git a/core/src/main/java/com/cloud/agent/api/HostStatsEntryBase.java b/core/src/main/java/com/cloud/agent/api/HostStatsEntryBase.java new file mode 100644 index 000000000000..2ce3f326401b --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/HostStatsEntryBase.java @@ -0,0 +1,134 @@ +// +// 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.agent.api; + +import com.cloud.host.HostStats; + +/** + * Serializable host-stats payload persisted as JSON in {@code host_stats.host_stats_data}. Unlike + * {@link HostStatsEntry} it carries no {@code HostVO}, so serialization does not pull in a host entity. + */ +public class HostStatsEntryBase implements HostStats { + + private long hostId; + private String entityType; + private double cpuUtilization; + private double averageLoad; + private double networkReadKBs; + private double networkWriteKBs; + private double totalMemoryKBs; + private double freeMemoryKBs; + + public HostStatsEntryBase() { + } + + public HostStatsEntryBase(long hostId, String entityType, double cpuUtilization, double averageLoad, + double networkReadKBs, double networkWriteKBs, double totalMemoryKBs, double freeMemoryKBs) { + this.hostId = hostId; + this.entityType = entityType; + this.cpuUtilization = cpuUtilization; + this.averageLoad = averageLoad; + this.networkReadKBs = networkReadKBs; + this.networkWriteKBs = networkWriteKBs; + this.totalMemoryKBs = totalMemoryKBs; + this.freeMemoryKBs = freeMemoryKBs; + } + + public long getHostId() { + return hostId; + } + + public void setHostId(long hostId) { + this.hostId = hostId; + } + + @Override + public String getEntityType() { + return entityType; + } + + public void setEntityType(String entityType) { + this.entityType = entityType; + } + + @Override + public double getCpuUtilization() { + return cpuUtilization; + } + + public void setCpuUtilization(double cpuUtilization) { + this.cpuUtilization = cpuUtilization; + } + + @Override + public double getLoadAverage() { + return averageLoad; + } + + public void setAverageLoad(double averageLoad) { + this.averageLoad = averageLoad; + } + + @Override + public double getNetworkReadKBs() { + return networkReadKBs; + } + + public void setNetworkReadKBs(double networkReadKBs) { + this.networkReadKBs = networkReadKBs; + } + + @Override + public double getNetworkWriteKBs() { + return networkWriteKBs; + } + + public void setNetworkWriteKBs(double networkWriteKBs) { + this.networkWriteKBs = networkWriteKBs; + } + + @Override + public double getTotalMemoryKBs() { + return totalMemoryKBs; + } + + public void setTotalMemoryKBs(double totalMemoryKBs) { + this.totalMemoryKBs = totalMemoryKBs; + } + + @Override + public double getFreeMemoryKBs() { + return freeMemoryKBs; + } + + public void setFreeMemoryKBs(double freeMemoryKBs) { + this.freeMemoryKBs = freeMemoryKBs; + } + + @Override + public double getUsedMemory() { + return (totalMemoryKBs - freeMemoryKBs) * 1024; + } + + @Override + public HostStats getHostStats() { + return this; + } +} diff --git a/engine/schema/src/main/java/com/cloud/host/HostStatsVO.java b/engine/schema/src/main/java/com/cloud/host/HostStatsVO.java new file mode 100644 index 000000000000..30869fa5cbef --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/host/HostStatsVO.java @@ -0,0 +1,88 @@ +// 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.host; + +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; + +/** One persisted historical host-stats sample. */ +@Entity +@Table(name = "host_stats") +public class HostStatsVO { + + @Id + @Column(name = "id", updatable = false, nullable = false) + protected long id; + + @Column(name = "host_id", updatable = false, nullable = false) + protected Long hostId; + + @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 = "host_stats_data", updatable = false, nullable = false, length = 65535) + protected String hostStatsData; + + public HostStatsVO(Long hostId, Long mgmtServerId, Date timestamp, String hostStatsData) { + this.hostId = hostId; + this.mgmtServerId = mgmtServerId; + this.timestamp = timestamp; + this.hostStatsData = hostStatsData; + } + + public HostStatsVO() { + + } + + public long getId() { + return id; + } + + public Long getHostId() { + return hostId; + } + + public Long getMgmtServerId() { + return mgmtServerId; + } + + public Date getTimestamp() { + return timestamp; + } + + public String getHostStatsData() { + return hostStatsData; + } + + @Override + public String toString() { + return ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "hostId", "mgmtServerId", "timestamp", "hostStatsData"); + } + +} diff --git a/engine/schema/src/main/java/com/cloud/host/dao/HostStatsDao.java b/engine/schema/src/main/java/com/cloud/host/dao/HostStatsDao.java new file mode 100644 index 000000000000..2a4bdd982f60 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/host/dao/HostStatsDao.java @@ -0,0 +1,42 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.host.dao; + +import java.util.Date; +import java.util.List; + +import com.cloud.host.HostStatsVO; +import com.cloud.utils.db.GenericDao; + +/** DAO for the host_stats table. */ +public interface HostStatsDao extends GenericDao { + + List findByHostId(long hostId); + + List findByHostIdAndTimestampGreaterThanEqual(long hostId, Date time); + + List findByHostIdAndTimestampLessThanEqual(long hostId, Date time); + + List findByHostIdAndTimestampBetween(long hostId, Date startTime, Date endTime); + + /** + * Expunges all host stats older than {@code limitDate}. + * @param limitPerQuery max rows removed per query; 0 or negative means no limit. + */ + void removeAllByTimestampLessThan(Date limitDate, long limitPerQuery); + +} diff --git a/engine/schema/src/main/java/com/cloud/host/dao/HostStatsDaoImpl.java b/engine/schema/src/main/java/com/cloud/host/dao/HostStatsDaoImpl.java new file mode 100644 index 000000000000..ac2211e24771 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/host/dao/HostStatsDaoImpl.java @@ -0,0 +1,115 @@ +// 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.host.dao; + +import java.util.Date; +import java.util.List; + +import javax.annotation.PostConstruct; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.host.HostStatsVO; +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; + +/** DAO for the host_stats table. */ +@Component +public class HostStatsDaoImpl extends GenericDaoBase implements HostStatsDao { + + protected Logger logger = LogManager.getLogger(getClass()); + + protected SearchBuilder hostIdSearch; + protected SearchBuilder hostIdTimestampGreaterThanEqualSearch; + protected SearchBuilder hostIdTimestampLessThanEqualSearch; + protected SearchBuilder hostIdTimestampBetweenSearch; + protected SearchBuilder timestampSearch; + + @PostConstruct + protected void init() { + hostIdSearch = createSearchBuilder(); + hostIdSearch.and("hostId", hostIdSearch.entity().getHostId(), Op.EQ); + hostIdSearch.done(); + + hostIdTimestampGreaterThanEqualSearch = createSearchBuilder(); + hostIdTimestampGreaterThanEqualSearch.and("hostId", hostIdTimestampGreaterThanEqualSearch.entity().getHostId(), Op.EQ); + hostIdTimestampGreaterThanEqualSearch.and("timestamp", hostIdTimestampGreaterThanEqualSearch.entity().getTimestamp(), Op.GTEQ); + hostIdTimestampGreaterThanEqualSearch.done(); + + hostIdTimestampLessThanEqualSearch = createSearchBuilder(); + hostIdTimestampLessThanEqualSearch.and("hostId", hostIdTimestampLessThanEqualSearch.entity().getHostId(), Op.EQ); + hostIdTimestampLessThanEqualSearch.and("timestamp", hostIdTimestampLessThanEqualSearch.entity().getTimestamp(), Op.LTEQ); + hostIdTimestampLessThanEqualSearch.done(); + + hostIdTimestampBetweenSearch = createSearchBuilder(); + hostIdTimestampBetweenSearch.and("hostId", hostIdTimestampBetweenSearch.entity().getHostId(), Op.EQ); + hostIdTimestampBetweenSearch.and("timestamp", hostIdTimestampBetweenSearch.entity().getTimestamp(), Op.BETWEEN); + hostIdTimestampBetweenSearch.done(); + + timestampSearch = createSearchBuilder(); + timestampSearch.and("timestamp", timestampSearch.entity().getTimestamp(), Op.LT); + timestampSearch.done(); + } + + @Override + public List findByHostId(long hostId) { + SearchCriteria sc = hostIdSearch.create(); + sc.setParameters("hostId", hostId); + return listBy(sc); + } + + @Override + public List findByHostIdAndTimestampGreaterThanEqual(long hostId, Date time) { + SearchCriteria sc = hostIdTimestampGreaterThanEqualSearch.create(); + sc.setParameters("hostId", hostId); + sc.setParameters("timestamp", time); + return listBy(sc); + } + + @Override + public List findByHostIdAndTimestampLessThanEqual(long hostId, Date time) { + SearchCriteria sc = hostIdTimestampLessThanEqualSearch.create(); + sc.setParameters("hostId", hostId); + sc.setParameters("timestamp", time); + return listBy(sc); + } + + @Override + public List findByHostIdAndTimestampBetween(long hostId, Date startTime, Date endTime) { + SearchCriteria sc = hostIdTimestampBetweenSearch.create(); + sc.setParameters("hostId", hostId); + sc.setParameters("timestamp", startTime, endTime); + return listBy(sc); + } + + @Override + public void removeAllByTimestampLessThan(Date limitDate, long limitPerQuery) { + SearchCriteria sc = timestampSearch.create(); + sc.setParameters("timestamp", limitDate); + + logger.debug(String.format("Starting to remove all host_stats rows older than [%s].", limitDate)); + + long totalRemoved = batchExpunge(sc, limitPerQuery); + + logger.info(String.format("Removed a total of [%s] host_stats rows older than [%s].", totalRemoved, limitDate)); + } + +} 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 932db538f30b..adc7b4b49b9d 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 @@ -227,6 +227,7 @@ + diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42300to2400.sql b/engine/schema/src/main/resources/META-INF/db/schema-42300to2400.sql index 7c11013a17d2..773257f1e8af 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42300to2400.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42300to2400.sql @@ -18,3 +18,15 @@ --; -- Schema upgrade from 4.23.0.0 to 24.0.0 --; + +-- Add host_stats table for the host usage history +CREATE TABLE IF NOT EXISTS `cloud`.`host_stats` ( + `id` bigint unsigned NOT NULL auto_increment COMMENT 'id', + `host_id` bigint unsigned NOT NULL, + `mgmt_server_id` bigint unsigned NOT NULL, + `timestamp` datetime NOT NULL, + `host_stats_data` text NOT NULL, + PRIMARY KEY (`id`), + KEY `i_host_stats__host_id` (`host_id`), + KEY `i_host_stats__timestamp` (`timestamp`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='historical per-host stats samples'; diff --git a/plugins/metrics/src/main/java/org/apache/cloudstack/api/ListHostsUsageHistoryCmd.java b/plugins/metrics/src/main/java/org/apache/cloudstack/api/ListHostsUsageHistoryCmd.java new file mode 100644 index 000000000000..b565ff04146a --- /dev/null +++ b/plugins/metrics/src/main/java/org/apache/cloudstack/api/ListHostsUsageHistoryCmd.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.HostResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.response.HostMetricsStatsResponse; + +@APICommand(name = "listHostsUsageHistory", description = "Lists host stats", responseObject = HostMetricsStatsResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "24.0.0", + authorized = {RoleType.Admin}) +public class ListHostsUsageHistoryCmd extends BaseResourceUsageHistoryCmd { + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = HostResponse.class, description = "The ID of the host.") + private Long id; + + @Parameter(name = ApiConstants.IDS, type = CommandType.LIST, collectionType = CommandType.UUID, entityType = HostResponse.class, description = "The IDs of the hosts, mutually exclusive with id.") + private List ids; + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "Name of the host (a substring match is made against the parameter value returning the data for all matching hosts).") + 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.searchForHostMetricsStats(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 bb7763688385..d81ab2db37f1 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 @@ -19,6 +19,7 @@ import java.util.List; +import org.apache.cloudstack.api.ListHostsUsageHistoryCmd; import org.apache.cloudstack.api.ListSystemVMsUsageHistoryCmd; import org.apache.cloudstack.api.ListVMsUsageHistoryCmd; import org.apache.cloudstack.api.ListVolumesUsageHistoryCmd; @@ -34,6 +35,7 @@ import org.apache.cloudstack.response.ClusterMetricsResponse; import org.apache.cloudstack.response.DbMetricsResponse; import org.apache.cloudstack.response.HostMetricsResponse; +import org.apache.cloudstack.response.HostMetricsStatsResponse; import org.apache.cloudstack.response.InfrastructureResponse; import org.apache.cloudstack.response.ManagementServerMetricsResponse; import org.apache.cloudstack.response.StoragePoolMetricsResponse; @@ -58,6 +60,7 @@ public interface MetricsService extends PluggableService { ListResponse searchForVmMetricsStats(ListVMsUsageHistoryCmd cmd); ListResponse searchForSystemVmMetricsStats(ListSystemVMsUsageHistoryCmd cmd); ListResponse searchForVolumeMetricsStats(ListVolumesUsageHistoryCmd cmd); + ListResponse searchForHostMetricsStats(ListHostsUsageHistoryCmd 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 0321d7c08d9e..194d6851b768 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,6 +37,7 @@ import org.apache.cloudstack.api.ListClustersMetricsCmd; import org.apache.cloudstack.api.ListDbMetricsCmd; import org.apache.cloudstack.api.ListHostsMetricsCmd; +import org.apache.cloudstack.api.ListHostsUsageHistoryCmd; import org.apache.cloudstack.api.ListInfrastructureCmd; import org.apache.cloudstack.api.ListMgmtsMetricsCmd; import org.apache.cloudstack.api.ListStoragePoolsMetricsCmd; @@ -67,6 +68,7 @@ import org.apache.cloudstack.response.ClusterMetricsResponse; import org.apache.cloudstack.response.DbMetricsResponse; import org.apache.cloudstack.response.HostMetricsResponse; +import org.apache.cloudstack.response.HostMetricsStatsResponse; import org.apache.cloudstack.response.HostMetricsSummary; import org.apache.cloudstack.response.InfrastructureResponse; import org.apache.cloudstack.response.ManagementServerMetricsResponse; @@ -88,6 +90,7 @@ import org.apache.commons.lang3.builder.ReflectionToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; +import com.cloud.agent.api.HostStatsEntryBase; import com.cloud.agent.api.VmDiskStatsEntry; import com.cloud.agent.api.VmStatsEntryBase; import com.cloud.alert.AlertManager; @@ -110,8 +113,11 @@ import com.cloud.exception.InvalidParameterValueException; import com.cloud.host.Host; import com.cloud.host.HostStats; +import com.cloud.host.HostStatsVO; +import com.cloud.host.HostVO; import com.cloud.host.Status; import com.cloud.host.dao.HostDao; +import com.cloud.host.dao.HostStatsDao; import com.cloud.network.router.VirtualRouter; import com.cloud.org.Cluster; import com.cloud.projects.Project; @@ -184,6 +190,8 @@ public class MetricsServiceImpl extends MutualExclusiveIdsManagerBase implements private VolumeDao volumeDao; @Inject private VolumeStatsDao volumeStatsDao; + @Inject + protected HostStatsDao hostStatsDao; @Inject private ObjectStoreDao objectStoreDao; @@ -248,6 +256,144 @@ public ListResponse searchForVolumeMetricsStats(List return createVolumeMetricsStatsResponse(volumeList, volumeStatsList); } + /** + * Searches for host stats based on the {@code ListHostsUsageHistoryCmd} parameters. + * + * @param cmd the {@link ListHostsUsageHistoryCmd} specifying what should be searched. + * @return the list of host metrics stats found. + */ + @Override + public ListResponse searchForHostMetricsStats(ListHostsUsageHistoryCmd cmd) { + Pair, Integer> hostList = searchForHostsInternal(cmd); + Map> hostStatsList = searchForHostMetricsStatsInternal(cmd.getStartDate(), cmd.getEndDate(), hostList.first()); + return createHostMetricsStatsResponse(hostList, hostStatsList); + } + + /** + * Searches routing hosts based on {@code ListHostsUsageHistoryCmd} parameters. + * + * @param cmd the {@link ListHostsUsageHistoryCmd} specifying the parameters. + * @return the list of hosts and the total count. + */ + protected Pair, Integer> searchForHostsInternal(ListHostsUsageHistoryCmd cmd) { + Filter searchFilter = new Filter(HostVO.class, "id", true, cmd.getStartIndex(), cmd.getPageSizeVal()); + List ids = getIdsListFromCmd(cmd.getId(), cmd.getIds()); + String name = cmd.getName(); + String keyword = cmd.getKeyword(); + + SearchBuilder sb = hostDao.createSearchBuilder(); + sb.and("idIN", sb.entity().getId(), SearchCriteria.Op.IN); + sb.and("name", sb.entity().getName(), SearchCriteria.Op.LIKE); + sb.and("type", sb.entity().getType(), SearchCriteria.Op.EQ); + + SearchCriteria sc = sb.create(); + sc.setParameters("type", Host.Type.Routing); + if (CollectionUtils.isNotEmpty(ids)) { + sc.setParameters("idIN", ids.toArray()); + } + if (StringUtils.isNotBlank(name)) { + sc.setParameters("name", "%" + name + "%"); + } + if (StringUtils.isNotBlank(keyword)) { + SearchCriteria ssc = hostDao.createSearchCriteria(); + ssc.addOr("name", SearchCriteria.Op.LIKE, "%" + keyword + "%"); + sc.addAnd("name", SearchCriteria.Op.SC, ssc); + } + + return hostDao.searchAndCount(sc, searchFilter); + } + + /** + * Searches stats for a list of hosts, based on date filtering parameters. + * + * @param startDate the start date for which stats should be searched. + * @param endDate the end date for which stats should be searched. + * @param hostList the list of hosts for which stats should be searched. + * @return the key-value map in which keys are host IDs and values are lists of host stats. + */ + protected Map> searchForHostMetricsStatsInternal(Date startDate, Date endDate, List hostList) { + Map> hostStatsVOList = new HashMap<>(); + validateDateParams(startDate, endDate); + + for (HostVO hostVO : hostList) { + Long hostId = hostVO.getId(); + hostStatsVOList.put(hostId, findHostStatsAccordingToDateParams(hostId, startDate, endDate)); + } + + return hostStatsVOList; + } + + /** + * Finds stats for a specific host based on date parameters. + * + * @param hostId the specific host. + * @param startDate the start date to filtering. + * @param endDate the end date to filtering. + * @return the list of stats for the specified host. + */ + protected List findHostStatsAccordingToDateParams(Long hostId, Date startDate, Date endDate) { + if (startDate != null && endDate != null) { + return hostStatsDao.findByHostIdAndTimestampBetween(hostId, startDate, endDate); + } + if (startDate != null) { + return hostStatsDao.findByHostIdAndTimestampGreaterThanEqual(hostId, startDate); + } + if (endDate != null) { + return hostStatsDao.findByHostIdAndTimestampLessThanEqual(hostId, endDate); + } + return hostStatsDao.findByHostId(hostId); + } + + /** + * Creates a {@code ListResponse}. For each host, this joins essential host info + * with its respective list of stats. + * + * @param hostList the list of hosts and the total count. + * @param hostStatsList the respective list of stats. + * @return the list of responses that was created. + */ + protected ListResponse createHostMetricsStatsResponse(Pair, Integer> hostList, + Map> hostStatsList) { + List responses = new ArrayList<>(); + for (HostVO hostVO : hostList.first()) { + HostMetricsStatsResponse hostMetricsStatsResponse = new HostMetricsStatsResponse(); + hostMetricsStatsResponse.setObjectName("host"); + hostMetricsStatsResponse.setId(hostVO.getUuid()); + hostMetricsStatsResponse.setName(hostVO.getName()); + hostMetricsStatsResponse.setStats(createHostStatsResponse(hostStatsList.get(hostVO.getId()))); + responses.add(hostMetricsStatsResponse); + } + + ListResponse response = new ListResponse<>(); + response.setResponses(responses, hostList.second()); + return response; + } + + /** + * Creates a {@code List} from a given {@code List}. + * + * @param hostStatsList the list of host stats. + * @return the list of responses that was created. + */ + protected List createHostStatsResponse(List hostStatsList) { + List statsResponseList = new ArrayList<>(); + DecimalFormat decimalFormat = new DecimalFormat("#.##"); + for (HostStatsVO hostStats : hostStatsList) { + StatsResponse response = new StatsResponse(); + response.setTimestamp(hostStats.getTimestamp()); + + HostStatsEntryBase statsEntry = gson.fromJson(hostStats.getHostStatsData(), HostStatsEntryBase.class); + response.setCpuUsed(decimalFormat.format(statsEntry.getCpuUtilization()) + "%"); + response.setNetworkKbsRead((long) statsEntry.getNetworkReadKBs()); + response.setNetworkKbsWrite((long) statsEntry.getNetworkWriteKBs()); + response.setMemoryKBs((long) statsEntry.getTotalMemoryKBs()); + response.setMemoryIntFreeKBs((long) statsEntry.getFreeMemoryKBs()); + + statsResponseList.add(response); + } + return statsResponseList; + } + /** * Outputs the parameters that should be used for access control in the query of a resource to * {@code permittedAccounts} and {@code domainIdRecursiveListProject}. @@ -1194,6 +1340,7 @@ public List> getCommands() { cmdList.add(ListVMsUsageHistoryCmd.class); cmdList.add(ListSystemVMsUsageHistoryCmd.class); cmdList.add(ListVolumesUsageHistoryCmd.class); + cmdList.add(ListHostsUsageHistoryCmd.class); // separate Admin commands cmdList.add(ListVMsMetricsCmdByAdmin.class); return cmdList; diff --git a/plugins/metrics/src/main/java/org/apache/cloudstack/response/HostMetricsStatsResponse.java b/plugins/metrics/src/main/java/org/apache/cloudstack/response/HostMetricsStatsResponse.java new file mode 100644 index 000000000000..2b3ba89929f5 --- /dev/null +++ b/plugins/metrics/src/main/java/org/apache/cloudstack/response/HostMetricsStatsResponse.java @@ -0,0 +1,53 @@ +// 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 HostMetricsStatsResponse extends BaseResponse { + @SerializedName(ApiConstants.ID) + @Param(description = "The ID of the host") + private String id; + + @SerializedName(ApiConstants.NAME) + @Param(description = "The name of the host") + private String name; + + @SerializedName("stats") + @Param(description = "The list of host stats", responseObject = StatsResponse.class) + 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 9184d7444105..f3d888814047 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 @@ -28,7 +28,9 @@ import org.apache.cloudstack.api.ListVMsUsageHistoryCmd; import org.apache.cloudstack.api.ListVolumesUsageHistoryCmd; import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.StatsResponse; import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.response.HostMetricsStatsResponse; import org.apache.cloudstack.response.VmMetricsStatsResponse; import org.apache.commons.lang3.time.DateUtils; import org.junit.Assert; @@ -43,7 +45,11 @@ import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; +import com.cloud.agent.api.HostStatsEntryBase; import com.cloud.exception.InvalidParameterValueException; +import com.cloud.host.HostStatsVO; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostStatsDao; import com.cloud.storage.VolumeVO; import com.cloud.storage.dao.VolumeDao; import com.cloud.user.Account; @@ -57,6 +63,8 @@ import com.cloud.vm.VmStatsVO; import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.VmStatsDao; +import com.google.gson.Gson; +import com.google.gson.JsonObject; @RunWith(MockitoJUnitRunner.class) @@ -122,6 +130,10 @@ public class MetricsServiceImplTest { SearchCriteria volumeSearchCriteriaMock; @Mock Filter filterMock; + @Mock + HostStatsDao hostStatsDaoMock; + @Mock + HostVO hostVOMock; private void prepareSearchCriteriaWhenUseSetParameters() { @@ -330,6 +342,104 @@ public void findVmStatsAccordingToDateParamsTestWithNoDate() { Mockito.verify(vmStatsDaoMock).findByVmId(Mockito.anyLong()); } + @Test + public void searchForHostMetricsStatsInternalTestWithAPopulatedListOfHosts() { + Mockito.doReturn(new ArrayList()).when(spy).findHostStatsAccordingToDateParams( + Mockito.anyLong(), Mockito.any(), Mockito.any()); + Mockito.doReturn(1L).when(hostVOMock).getId(); + Map> expected = new HashMap<>(); + expected.put(1L, new ArrayList<>()); + + Map> result = spy.searchForHostMetricsStatsInternal(null, null, Arrays.asList(hostVOMock)); + + Mockito.verify(spy).findHostStatsAccordingToDateParams(1L, null, null); + Assert.assertEquals(expected, result); + } + + @Test + public void searchForHostMetricsStatsInternalTestWithAnEmptyListOfHosts() { + Map> result = spy.searchForHostMetricsStatsInternal(null, null, new ArrayList<>()); + + Mockito.verify(spy, Mockito.never()).findHostStatsAccordingToDateParams( + Mockito.anyLong(), Mockito.any(), Mockito.any()); + Assert.assertTrue(result.isEmpty()); + } + + @Test(expected = InvalidParameterValueException.class) + public void searchForHostMetricsStatsInternalTestWithEndDateBeforeStartDate() { + Date startDate = new Date(); + + spy.searchForHostMetricsStatsInternal(startDate, DateUtils.addSeconds(startDate, -1), Arrays.asList(hostVOMock)); + } + + @Test + public void findHostStatsAccordingToDateParamsTestWithStartDateAndEndDate() { + Date startDate = new Date(); + Date endDate = DateUtils.addSeconds(startDate, 1); + + spy.findHostStatsAccordingToDateParams(1L, startDate, endDate); + + Mockito.verify(hostStatsDaoMock).findByHostIdAndTimestampBetween(1L, startDate, endDate); + } + + @Test + public void findHostStatsAccordingToDateParamsTestWithOnlyStartDate() { + Date startDate = new Date(); + + spy.findHostStatsAccordingToDateParams(1L, startDate, null); + + Mockito.verify(hostStatsDaoMock).findByHostIdAndTimestampGreaterThanEqual(1L, startDate); + } + + @Test + public void findHostStatsAccordingToDateParamsTestWithOnlyEndDate() { + Date endDate = new Date(); + + spy.findHostStatsAccordingToDateParams(1L, null, endDate); + + Mockito.verify(hostStatsDaoMock).findByHostIdAndTimestampLessThanEqual(1L, endDate); + } + + @Test + public void findHostStatsAccordingToDateParamsTestWithNoDate() { + spy.findHostStatsAccordingToDateParams(1L, null, null); + + Mockito.verify(hostStatsDaoMock).findByHostId(1L); + } + + @Test + public void createHostMetricsStatsResponseTestWithValidInput() { + Mockito.doReturn(1L).when(hostVOMock).getId(); + Mockito.doReturn("host-uuid").when(hostVOMock).getUuid(); + Mockito.doReturn("host-name").when(hostVOMock).getName(); + Map> statsMap = new HashMap<>(); + statsMap.put(1L, new ArrayList<>()); + + ListResponse result = spy.createHostMetricsStatsResponse( + new Pair<>(Arrays.asList(hostVOMock), 5), statsMap); + + Assert.assertEquals(Integer.valueOf(5), result.getCount()); + Assert.assertEquals(1, result.getResponses().size()); + } + + @Test + public void createHostStatsResponseTestMapsTheStoredValues() { + Date timestamp = new Date(); + HostStatsEntryBase entry = new HostStatsEntryBase(1L, "host", 12.345, 0.5, 100.0, 200.0, 4096.0, 1024.0); + HostStatsVO hostStatsVO = new HostStatsVO(1L, 2L, timestamp, new Gson().toJson(entry)); + + List result = spy.createHostStatsResponse(Arrays.asList(hostStatsVO)); + + Assert.assertEquals(1, result.size()); + JsonObject response = new Gson().toJsonTree(result.get(0)).getAsJsonObject(); + Assert.assertTrue(response.has("timestamp")); + Assert.assertEquals("12.35%", response.get("cpuused").getAsString()); + Assert.assertEquals(100L, response.get("networkkbsread").getAsLong()); + Assert.assertEquals(200L, response.get("networkkbswrite").getAsLong()); + Assert.assertEquals(4096L, response.get("memorykbs").getAsLong()); + Assert.assertEquals(1024L, response.get("memoryintfreekbs").getAsLong()); + } + @Test public void createVmMetricsStatsResponseTestWithValidInput() { Mockito.doReturn("").when(userVmVOMock).getUuid(); diff --git a/server/src/main/java/com/cloud/server/StatsCollector.java b/server/src/main/java/com/cloud/server/StatsCollector.java index 456792d14b75..787205ed502e 100644 --- a/server/src/main/java/com/cloud/server/StatsCollector.java +++ b/server/src/main/java/com/cloud/server/StatsCollector.java @@ -84,6 +84,7 @@ import com.cloud.agent.api.Answer; import com.cloud.agent.api.GetStorageStatsCommand; import com.cloud.agent.api.HostStatsEntry; +import com.cloud.agent.api.HostStatsEntryBase; import com.cloud.agent.api.VgpuTypesInfo; import com.cloud.agent.api.VmDiskStatsEntry; import com.cloud.agent.api.VmNetworkStatsEntry; @@ -110,7 +111,9 @@ import com.cloud.host.HostStats; import com.cloud.host.HostVO; import com.cloud.host.Status; +import com.cloud.host.HostStatsVO; import com.cloud.host.dao.HostDao; +import com.cloud.host.dao.HostStatsDao; import com.cloud.hypervisor.Hypervisor; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.network.as.AutoScaleManager; @@ -301,6 +304,9 @@ public String toString() { 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); + protected static ConfigKey hostStatsMaxRetentionTime = new ConfigKey<>("Advanced", Integer.class, "host.stats.max.retention.time", "720", + "The maximum time (in minutes) for keeping host stats records in the database. Host stats are not stored and the cleanup process is disabled if this is set to 0 or less than 0.", true); + private static StatsCollector s_instance = null; private static Gson gson = new Gson(); @@ -322,6 +328,8 @@ public String toString() { @Inject protected VmStatsDao vmStatsDao; @Inject + protected HostStatsDao hostStatsDao; + @Inject private VolumeDao _volsDao; @Inject protected VolumeStatsDao volumeStatsDao; @@ -510,6 +518,8 @@ protected void init(Map configs) { _executor.scheduleWithFixedDelay(new VmStatsCleaner(), DEFAULT_INITIAL_DELAY, 60000L, TimeUnit.MILLISECONDS); + _executor.scheduleWithFixedDelay(new HostStatsCleaner(), 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); @@ -676,12 +686,17 @@ protected void runInContext() { logger.debug(String.format("HostStatsCollector is running to process %d UP hosts", hosts.size())); Map metrics = new HashMap<>(); + boolean persistHostStats = hostStatsMaxRetentionTime.value() > 0; + Date timestamp = new Date(); for (HostVO host : hosts) { HostStatsEntry hostStatsEntry = (HostStatsEntry) _resourceMgr.getHostStatistics(host); if (hostStatsEntry != null) { hostStatsEntry.setHostVo(host); metrics.put(hostStatsEntry.getHostId(), hostStatsEntry); _hostStats.put(host.getId(), hostStatsEntry); + if (persistHostStats) { + persistHostStats(hostStatsEntry, timestamp); + } } else { logger.warn("The Host stats is null for host: {}", host); } @@ -1316,6 +1331,17 @@ protected void runInContext() { } } + class HostStatsCleaner extends ManagedContextRunnable{ + @Override + protected void runInContext() { + try { + cleanUpHostStats(); + } catch (RuntimeException e) { + logger.error("Error trying to clean up host stats", e); + } + } + } + class VolumeStatsCleaner extends ManagedContextRunnable{ @Override protected void runInContext() { @@ -2001,6 +2027,23 @@ protected void persistVirtualMachineStats(VmStatsEntry statsForCurrentIteration, vmStatsDao.persist(vmStatsVO); } + /** + * Persists the host stats of the current collection in the host_stats table. + * + * @param statsForCurrentIteration the host metrics to persist. + * @param timestamp the time that will be stamped. + */ + protected void persistHostStats(HostStatsEntry statsForCurrentIteration, Date timestamp) { + HostStatsEntryBase hostStats = new HostStatsEntryBase(statsForCurrentIteration.getHostId(), + statsForCurrentIteration.getEntityType(), statsForCurrentIteration.getCpuUtilization(), + statsForCurrentIteration.getLoadAverage(), statsForCurrentIteration.getNetworkReadKBs(), + statsForCurrentIteration.getNetworkWriteKBs(), statsForCurrentIteration.getTotalMemoryKBs(), + statsForCurrentIteration.getFreeMemoryKBs()); + HostStatsVO hostStatsVO = new HostStatsVO(statsForCurrentIteration.getHostId(), msId, timestamp, gson.toJson(hostStats)); + logger.trace(String.format("Recording host stats: [%s].", hostStatsVO.toString())); + hostStatsDao.persist(hostStatsVO); + } + private String getVmDiskStatsEntryAsString(VmDiskStatsEntry statsForCurrentIteration, Hypervisor.HypervisorType hypervisorType) { VmDiskStatsEntry entry; if (Hypervisor.HypervisorType.KVM.equals(hypervisorType)) { @@ -2051,6 +2094,23 @@ protected void cleanUpVirtualMachineStats() { vmStatsDao.removeAllByTimestampLessThan(limit, DELETE_QUERY_BATCH_SIZE.value()); } + /** + * Removes the oldest host stats records according to the global + * parameter {@code host.stats.max.retention.time}. + */ + protected void cleanUpHostStats() { + Integer maxRetentionTime = hostStatsMaxRetentionTime.value(); + if (maxRetentionTime <= 0) { + logger.debug(String.format("Skipping host stats cleanup. The [%s] parameter [%s] is set to 0 or less than 0.", + ConfigKey.Scope.decodeAsCsv(hostStatsMaxRetentionTime.getScopeBitmask()), hostStatsMaxRetentionTime.toString())); + return; + } + logger.trace("Removing older host stats records."); + Date now = new Date(); + Date limit = DateUtils.addMinutes(now, -maxRetentionTime); + hostStatsDao.removeAllByTimestampLessThan(limit, DELETE_QUERY_BATCH_SIZE.value()); + } + /** * Removes the oldest Volume stats records according to the global * parameter {@code vm.disk.stats.max.retention.time}. @@ -2245,6 +2305,7 @@ public String getConfigComponentName() { public ConfigKey[] getConfigKeys() { return new ConfigKey[] {vmDiskStatsInterval, vmDiskStatsIntervalMin, vmNetworkStatsInterval, vmNetworkStatsIntervalMin, StatsTimeout, statsOutputUri, vmStatsIncrementMetrics, vmStatsMaxRetentionTime, vmStatsCollectUserVMOnly, vmDiskStatsRetentionEnabled, vmDiskStatsMaxRetentionTime, + hostStatsMaxRetentionTime, MANAGEMENT_SERVER_STATUS_COLLECTION_INTERVAL, DATABASE_SERVER_STATUS_COLLECTION_INTERVAL, DATABASE_SERVER_LOAD_HISTORY_RETENTION_NUMBER}; diff --git a/server/src/test/java/com/cloud/server/StatsCollectorTest.java b/server/src/test/java/com/cloud/server/StatsCollectorTest.java index cb00d1652c9a..e10552e1208e 100644 --- a/server/src/test/java/com/cloud/server/StatsCollectorTest.java +++ b/server/src/test/java/com/cloud/server/StatsCollectorTest.java @@ -64,11 +64,15 @@ import com.cloud.agent.api.GetStorageStatsAnswer; import com.cloud.agent.api.GetStorageStatsCommand; +import com.cloud.agent.api.HostStatsEntry; +import com.cloud.agent.api.HostStatsEntryBase; import com.cloud.agent.api.VmDiskStatsEntry; import com.cloud.agent.api.VmStatsEntry; import com.cloud.dc.Vlan.VlanType; import com.cloud.dc.VlanVO; import com.cloud.dc.dao.VlanDao; +import com.cloud.host.HostStatsVO; +import com.cloud.host.dao.HostStatsDaoImpl; import com.cloud.hypervisor.Hypervisor; import com.cloud.network.dao.NetworkDao; import com.cloud.network.dao.NetworkVO; @@ -101,6 +105,12 @@ public class StatsCollectorTest { @Mock VmStatsDaoImpl vmStatsDaoMock; + @Mock + HostStatsDaoImpl hostStatsDaoMock; + + @Captor + ArgumentCaptor hostStatsVOCaptor = ArgumentCaptor.forClass(HostStatsVO.class); + @Mock VmStatsEntry statsForCurrentIterationMock; @@ -146,6 +156,7 @@ public class StatsCollectorTest { public void setUp() throws Exception { closeable = MockitoAnnotations.openMocks(this); statsCollector.vmStatsDao = vmStatsDaoMock; + statsCollector.hostStatsDao = hostStatsDaoMock; statsCollector.volumeStatsDao = volumeStatsDao; Field msStatsGsonField = StatsCollector.class.getDeclaredField("msStatsGson"); msStatsGsonField.setAccessible(true); @@ -394,6 +405,59 @@ public void volumeStatsCleanerTestCatchesCloudRuntimeExceptionAndKeepsRunning() Mockito.verify(statsCollector).cleanUpVolumeStats(); } + // host stats persistence + retention + + private void setHostStatsMaxRetentionTimeValue(String value) { + StatsCollector.hostStatsMaxRetentionTime = new ConfigKey("Advanced", Integer.class, "host.stats.max.retention.time", value, + "The maximum time (in minutes) for keeping host stats records in the database. The host stats cleanup process will be disabled if this is set to 0 or less than 0.", true); + } + + @Test + public void cleanUpHostStatsTestIsDisabled() { + setHostStatsMaxRetentionTimeValue("0"); + + statsCollector.cleanUpHostStats(); + + Mockito.verify(hostStatsDaoMock, Mockito.never()).removeAllByTimestampLessThan(Mockito.any(), Mockito.anyLong()); + } + + @Test + public void cleanUpHostStatsTestIsEnabled() { + setHostStatsMaxRetentionTimeValue("1"); + + statsCollector.cleanUpHostStats(); + + Mockito.verify(hostStatsDaoMock).removeAllByTimestampLessThan(Mockito.any(), Mockito.anyLong()); + } + + @Test + public void persistHostStatsTestPersistsSuccessfully() { + statsCollector.msId = 7L; + Date timestamp = new Date(); + // hostId, cpuUtilization, networkReadKBs, networkWriteKBs, entityType, totalMemoryKBs, freeMemoryKBs, xapiMemoryUsageKBs, averageLoad + HostStatsEntry statsForCurrentIteration = new HostStatsEntry(5L, 10.0, 20.0, 30.0, "host", 1000.0, 400.0, 0.0, 2.0); + Mockito.doReturn(new HostStatsVO()).when(hostStatsDaoMock).persist(Mockito.any()); + + statsCollector.persistHostStats(statsForCurrentIteration, timestamp); + + Mockito.verify(hostStatsDaoMock).persist(hostStatsVOCaptor.capture()); + HostStatsVO actual = hostStatsVOCaptor.getValue(); + Assert.assertEquals(Long.valueOf(5L), actual.getHostId()); + Assert.assertEquals(Long.valueOf(7L), actual.getMgmtServerId()); + Assert.assertEquals(timestamp, actual.getTimestamp()); + HostStatsEntryBase persisted = gson.fromJson(actual.getHostStatsData(), HostStatsEntryBase.class); + Assert.assertEquals(5L, persisted.getHostId()); + Assert.assertEquals("host", persisted.getEntityType()); + Assert.assertEquals(10.0, persisted.getCpuUtilization(), 0); + Assert.assertEquals(2.0, persisted.getLoadAverage(), 0); + Assert.assertEquals(20.0, persisted.getNetworkReadKBs(), 0); + Assert.assertEquals(30.0, persisted.getNetworkWriteKBs(), 0); + Assert.assertEquals(1000.0, persisted.getTotalMemoryKBs(), 0); + Assert.assertEquals(400.0, persisted.getFreeMemoryKBs(), 0); + // Lean payload must NOT carry a HostVO blob. + Assert.assertFalse(actual.getHostStatsData().contains("hostVo")); + } + @Test public void persistVirtualMachineStatsTestPersistsSuccessfully() { statsCollector.msId = 1L; diff --git a/test/integration/smoke/test_metrics_api.py b/test/integration/smoke/test_metrics_api.py index ab2644fc1aad..85f8e122bc2f 100644 --- a/test/integration/smoke/test_metrics_api.py +++ b/test/integration/smoke/test_metrics_api.py @@ -547,6 +547,29 @@ def test_list_volumes_metrics_history(self): return + @attr(tags = ["advanced", "advancedns", "smoke", "basic"], required_hardware="true") + @skipTestIf("hypervisorNotSupported") + def test_list_hosts_metrics_history(self): + cmd = listHostsUsageHistory.listHostsUsageHistoryCmd() + 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.listHostsUsageHistory(cmd)[0] + + self.assertTrue(hasattr(result, 'stats')) + self.assertTrue(type(result.stats) == list and len(result.stats) > 0) + stats = result.stats[0] + self.assertTrue(hasattr(stats, 'cpuused')) + self.assertTrue(hasattr(stats, 'memorykbs')) + self.assertTrue(hasattr(stats, 'memoryintfreekbs')) + self.assertTrue(hasattr(stats, 'networkkbsread')) + self.assertTrue(hasattr(stats, 'networkkbswrite')) + 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'))