diff --git a/api/src/com/cloud/storage/ImageStore.java b/api/src/com/cloud/storage/ImageStore.java index ec693c4e493..c019b17421d 100644 --- a/api/src/com/cloud/storage/ImageStore.java +++ b/api/src/com/cloud/storage/ImageStore.java @@ -41,4 +41,11 @@ public interface ImageStore extends Identity, InternalIdentity { * @return data store protocol */ String getProtocol(); + + /** + * + * @return uri + */ + String getUrl(); + } diff --git a/client/pom.xml b/client/pom.xml index 3a0c5a5a720..ae0fcaa20a5 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -414,6 +414,11 @@ cloud-plugin-database-quota ${project.version} + + org.apache.cloudstack + cloud-plugin-integrations-prometheus-exporter + ${project.version} + diff --git a/engine/schema/src/com/cloud/vm/dao/VMInstanceDao.java b/engine/schema/src/com/cloud/vm/dao/VMInstanceDao.java index 8d457fadff5..3c5024b833e 100644 --- a/engine/schema/src/com/cloud/vm/dao/VMInstanceDao.java +++ b/engine/schema/src/com/cloud/vm/dao/VMInstanceDao.java @@ -116,6 +116,8 @@ public interface VMInstanceDao extends GenericDao, StateDao< Long countRunningByAccount(long accountId); + Long countByZoneAndState(long zoneId, State state); + List listNonRemovedVmsByTypeAndNetwork(long networkId, VirtualMachine.Type... types); /** diff --git a/engine/schema/src/com/cloud/vm/dao/VMInstanceDaoImpl.java b/engine/schema/src/com/cloud/vm/dao/VMInstanceDaoImpl.java index df5e60e7a1a..7065350a57e 100644 --- a/engine/schema/src/com/cloud/vm/dao/VMInstanceDaoImpl.java +++ b/engine/schema/src/com/cloud/vm/dao/VMInstanceDaoImpl.java @@ -87,6 +87,7 @@ public class VMInstanceDaoImpl extends GenericDaoBase implem protected GenericSearchBuilder FindIdsOfVirtualRoutersByAccount; protected GenericSearchBuilder CountActiveByHost; protected GenericSearchBuilder CountRunningByAccount; + protected GenericSearchBuilder CountByZoneAndState; protected SearchBuilder NetworkTypeSearch; protected GenericSearchBuilder DistinctHostNameSearch; protected SearchBuilder HostAndStateSearch; @@ -242,6 +243,12 @@ public class VMInstanceDaoImpl extends GenericDaoBase implem CountRunningByAccount.and("state", CountRunningByAccount.entity().getState(), SearchCriteria.Op.EQ); CountRunningByAccount.done(); + CountByZoneAndState = createSearchBuilder(Long.class); + CountByZoneAndState.select(null, Func.COUNT, null); + CountByZoneAndState.and("zone", CountByZoneAndState.entity().getDataCenterId(), SearchCriteria.Op.EQ); + CountByZoneAndState.and("state", CountByZoneAndState.entity().getState(), SearchCriteria.Op.EQ); + CountByZoneAndState.done(); + HostAndStateSearch = createSearchBuilder(); HostAndStateSearch.and("host", HostAndStateSearch.entity().getHostId(), Op.EQ); HostAndStateSearch.and("states", HostAndStateSearch.entity().getState(), Op.IN); @@ -718,6 +725,14 @@ public class VMInstanceDaoImpl extends GenericDaoBase implem return customSearch(sc, null).get(0); } + @Override + public Long countByZoneAndState(long zoneId, State state) { + SearchCriteria sc = CountByZoneAndState.create(); + sc.setParameters("zone", zoneId); + sc.setParameters("state", state); + return customSearch(sc, null).get(0); + } + @Override public List listNonRemovedVmsByTypeAndNetwork(long networkId, VirtualMachine.Type... types) { if (NetworkTypeSearch == null) { diff --git a/engine/storage/image/src/org/apache/cloudstack/storage/image/store/ImageStoreImpl.java b/engine/storage/image/src/org/apache/cloudstack/storage/image/store/ImageStoreImpl.java index 182a8ec4cab..41ce5a230b0 100644 --- a/engine/storage/image/src/org/apache/cloudstack/storage/image/store/ImageStoreImpl.java +++ b/engine/storage/image/src/org/apache/cloudstack/storage/image/store/ImageStoreImpl.java @@ -180,6 +180,11 @@ public class ImageStoreImpl implements ImageStoreEntity { return imageDataStoreVO.getProtocol(); } + @Override + public String getUrl() { + return imageDataStoreVO.getUrl(); + } + @Override public DataStoreTO getTO() { DataStoreTO to = getDriver().getStoreTO(this); diff --git a/plugins/integrations/prometheus/pom.xml b/plugins/integrations/prometheus/pom.xml new file mode 100644 index 00000000000..66dbebbf1d1 --- /dev/null +++ b/plugins/integrations/prometheus/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + cloud-plugin-integrations-prometheus-exporter + Apache CloudStack Plugin - Prometheus Exporter + + org.apache.cloudstack + cloudstack-plugins + 4.11.0.0-SNAPSHOT + ../../pom.xml + + + + org.apache.cloudstack + cloud-utils + ${project.version} + + + org.apache.cloudstack + cloud-api + ${project.version} + + + org.apache.cloudstack + cloud-engine-schema + ${project.version} + + + diff --git a/plugins/integrations/prometheus/resources/META-INF/cloudstack/prometheus/module.properties b/plugins/integrations/prometheus/resources/META-INF/cloudstack/prometheus/module.properties new file mode 100644 index 00000000000..cb70ec81e54 --- /dev/null +++ b/plugins/integrations/prometheus/resources/META-INF/cloudstack/prometheus/module.properties @@ -0,0 +1,18 @@ +# 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. +name=prometheus +parent=api diff --git a/plugins/integrations/prometheus/resources/META-INF/cloudstack/prometheus/spring-prometheus-context.xml b/plugins/integrations/prometheus/resources/META-INF/cloudstack/prometheus/spring-prometheus-context.xml new file mode 100644 index 00000000000..06fb92c1617 --- /dev/null +++ b/plugins/integrations/prometheus/resources/META-INF/cloudstack/prometheus/spring-prometheus-context.xml @@ -0,0 +1,27 @@ + + + + + + + diff --git a/plugins/integrations/prometheus/src/org/apache/cloudstack/metrics/PrometheusExporter.java b/plugins/integrations/prometheus/src/org/apache/cloudstack/metrics/PrometheusExporter.java new file mode 100644 index 00000000000..6361f0edc6b --- /dev/null +++ b/plugins/integrations/prometheus/src/org/apache/cloudstack/metrics/PrometheusExporter.java @@ -0,0 +1,24 @@ +// 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.metrics; + +public interface PrometheusExporter { + + void updateMetrics(); + + String getMetrics(); +} diff --git a/plugins/integrations/prometheus/src/org/apache/cloudstack/metrics/PrometheusExporterImpl.java b/plugins/integrations/prometheus/src/org/apache/cloudstack/metrics/PrometheusExporterImpl.java new file mode 100644 index 00000000000..a51b2966631 --- /dev/null +++ b/plugins/integrations/prometheus/src/org/apache/cloudstack/metrics/PrometheusExporterImpl.java @@ -0,0 +1,612 @@ +// 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.metrics; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope; +import org.apache.cloudstack.storage.datastore.db.ImageStoreDao; +import org.apache.log4j.Logger; + +import com.cloud.alert.AlertManager; +import com.cloud.api.ApiDBUtils; +import com.cloud.api.query.dao.DomainJoinDao; +import com.cloud.api.query.dao.HostJoinDao; +import com.cloud.api.query.dao.StoragePoolJoinDao; +import com.cloud.api.query.vo.DomainJoinVO; +import com.cloud.api.query.vo.HostJoinVO; +import com.cloud.api.query.vo.StoragePoolJoinVO; +import com.cloud.capacity.Capacity; +import com.cloud.capacity.CapacityManager; +import com.cloud.capacity.CapacityVO; +import com.cloud.capacity.dao.CapacityDao; +import com.cloud.capacity.dao.CapacityDaoImpl; +import com.cloud.configuration.Resource; +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.Vlan; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.dc.dao.DataCenterIpAddressDao; +import com.cloud.host.Host; +import com.cloud.host.Status; +import com.cloud.network.dao.IPAddressDao; +import com.cloud.storage.ImageStore; +import com.cloud.storage.StorageStats; +import com.cloud.storage.Volume; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.utils.component.Manager; +import com.cloud.utils.component.ManagerBase; +import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.dao.VMInstanceDao; +import com.google.common.base.Strings; + +public class PrometheusExporterImpl extends ManagerBase implements PrometheusExporter, Manager { + private static final Logger LOG = Logger.getLogger(PrometheusExporterImpl.class); + + private static final String USED = "used"; + private static final String ALLOCATED = "allocated"; + private static final String UNALLOCATED = "unallocated"; + private static final String TOTAL = "total"; + private static final String ONLINE = "online"; + private static final String OFFLINE = "offline"; + + private static List metricsItems = new ArrayList<>(); + + @Inject + private DataCenterDao dcDao; + @Inject + private HostJoinDao hostJoinDao; + @Inject + private VMInstanceDao vmDao; + @Inject + private VolumeDao volumeDao; + @Inject + private IPAddressDao publicIpAddressDao; + @Inject + private DataCenterIpAddressDao privateIpAddressDao; + @Inject + private CapacityDao capacityDao; + @Inject + private StoragePoolJoinDao storagePoolJoinDao; + @Inject + private ImageStoreDao imageStoreDao; + @Inject + private DomainJoinDao domainDao; + @Inject + private AlertManager alertManager; + + public PrometheusExporterImpl() { + super(); + } + + private void addHostMetrics(final List metricsList, final long dcId, final String zoneName, final String zoneUuid) { + int total = 0; + int up = 0; + int down = 0; + for (final HostJoinVO host : hostJoinDao.listAll()) { + if (host == null || host.getType() != Host.Type.Routing || host.getZoneId() != dcId) { + continue; + } + total++; + if (host.getStatus() == Status.Up) { + up++; + } else if (host.getStatus() == Status.Disconnected || host.getStatus() == Status.Down) { + down++; + } + + final String cpuFactor = String.valueOf(CapacityManager.CpuOverprovisioningFactor.valueIn(host.getClusterId())); + final CapacityVO cpuCapacity = capacityDao.findByHostIdType(host.getId(), Capacity.CAPACITY_TYPE_CPU); + metricsList.add(new ItemHostCpu(zoneName, zoneUuid, host.getName(), host.getUuid(), host.getPrivateIpAddress(), cpuFactor, USED, cpuCapacity.getUsedCapacity())); + metricsList.add(new ItemHostCpu(zoneName, zoneUuid, host.getName(), host.getUuid(), host.getPrivateIpAddress(), cpuFactor, TOTAL, cpuCapacity.getTotalCapacity())); + + final String memoryFactor = String.valueOf(CapacityManager.MemOverprovisioningFactor.valueIn(host.getClusterId())); + final CapacityVO memCapacity = capacityDao.findByHostIdType(host.getId(), Capacity.CAPACITY_TYPE_MEMORY); + metricsList.add(new ItemHostMemory(zoneName, zoneUuid, host.getName(), host.getUuid(), host.getPrivateIpAddress(), memoryFactor, USED, memCapacity.getUsedCapacity())); + metricsList.add(new ItemHostMemory(zoneName, zoneUuid, host.getName(), host.getUuid(), host.getPrivateIpAddress(), memoryFactor, TOTAL, memCapacity.getTotalCapacity())); + + metricsList.add(new ItemHostVM(zoneName, zoneUuid, host.getName(), host.getUuid(), host.getPrivateIpAddress(), vmDao.listByHostId(host.getId()).size())); + + final CapacityVO coreCapacity = capacityDao.findByHostIdType(host.getId(), Capacity.CAPACITY_TYPE_CPU_CORE); + if (coreCapacity != null) { + metricsList.add(new ItemVMCore(zoneName, zoneUuid, host.getName(), host.getUuid(), host.getPrivateIpAddress(), USED, coreCapacity.getUsedCapacity())); + metricsList.add(new ItemVMCore(zoneName, zoneUuid, host.getName(), host.getUuid(), host.getPrivateIpAddress(), TOTAL, coreCapacity.getTotalCapacity())); + } + } + + final List cpuCapacity = capacityDao.findCapacityBy((int) Capacity.CAPACITY_TYPE_CPU, dcId, null, null); + if (cpuCapacity != null && cpuCapacity.size() > 0) { + metricsList.add(new ItemHostCpu(zoneName, zoneUuid, null, null, null, null, ALLOCATED, cpuCapacity.get(0).getAllocatedCapacity() != null ? cpuCapacity.get(0).getAllocatedCapacity() : 0)); + } + + final List memCapacity = capacityDao.findCapacityBy((int) Capacity.CAPACITY_TYPE_MEMORY, dcId, null, null); + if (memCapacity != null && memCapacity.size() > 0) { + metricsList.add(new ItemHostMemory(zoneName, zoneUuid, null, null, null, null, ALLOCATED, memCapacity.get(0).getAllocatedCapacity() != null ? memCapacity.get(0).getAllocatedCapacity() : 0)); + } + + final List coreCapacity = capacityDao.findCapacityBy((int) Capacity.CAPACITY_TYPE_CPU_CORE, dcId, null, null); + if (coreCapacity != null && coreCapacity.size() > 0) { + metricsList.add(new ItemVMCore(zoneName, zoneUuid, null, null, null, ALLOCATED, coreCapacity.get(0).getAllocatedCapacity() != null ? coreCapacity.get(0).getAllocatedCapacity() : 0)); + } + + metricsList.add(new ItemHost(zoneName, zoneUuid, ONLINE, up)); + metricsList.add(new ItemHost(zoneName, zoneUuid, OFFLINE, down)); + metricsList.add(new ItemHost(zoneName, zoneUuid, TOTAL, total)); + } + + private void addVMMetrics(final List metricsList, final long dcId, final String zoneName, final String zoneUuid) { + for (final State state : State.values()) { + final Long count = vmDao.countByZoneAndState(dcId, state); + if (count == null) { + continue; + } + metricsList.add(new ItemVM(zoneName, zoneUuid, state.name().toLowerCase(), count)); + } + } + + private void addVolumeMetrics(final List metricsList, final long dcId, final String zoneName, final String zoneUuid) { + int total = 0; + int ready = 0; + int destroyed = 0; + for (final VolumeVO volume : volumeDao.findByDc(dcId)) { + if (volume == null) { + continue; + } + total++; + if (volume.getState() == Volume.State.Ready) { + ready++; + } else if (volume.getState() == Volume.State.Destroy) { + destroyed++; + } + } + metricsList.add(new ItemVolume(zoneName, zoneUuid, Volume.State.Ready.name().toLowerCase(), ready)); + metricsList.add(new ItemVolume(zoneName, zoneUuid, Volume.State.Destroy.name().toLowerCase(), destroyed)); + metricsList.add(new ItemVolume(zoneName, zoneUuid, TOTAL, total)); + } + + private void addStorageMetrics(final List metricsList, final long dcId, final String zoneName, final String zoneUuid) { + for (final StoragePoolJoinVO pool: storagePoolJoinDao.listAll()) { + if (pool == null || pool.getZoneId() != dcId) { + continue; + } + final String poolName = pool.getName(); + final String poolPath = pool.getHostAddress() + ":" + pool.getPath(); + + long usedCapacity = 0L; + long allocatedCapacity = pool.getUsedCapacity() + pool.getReservedCapacity(); + final long totalCapacity = pool.getCapacityBytes(); + + final StorageStats stats = ApiDBUtils.getStoragePoolStatistics(pool.getId()); + if (stats != null) { + usedCapacity = stats.getByteUsed(); + } + + final BigDecimal poolOverProvisioningFactor = BigDecimal.valueOf(CapacityManager.StorageOverprovisioningFactor.valueIn(pool.getId())); + final String poolFactor = poolOverProvisioningFactor.toString(); + + metricsList.add(new ItemPool(zoneName, zoneUuid, poolName, poolPath, "primary", poolFactor, USED, usedCapacity)); + metricsList.add(new ItemPool(zoneName, zoneUuid, poolName, poolPath, "primary", poolFactor, ALLOCATED, allocatedCapacity)); + metricsList.add(new ItemPool(zoneName, zoneUuid, poolName, poolPath, "primary", poolFactor, UNALLOCATED, poolOverProvisioningFactor.multiply(BigDecimal.valueOf(totalCapacity)).longValue() - allocatedCapacity)); + metricsList.add(new ItemPool(zoneName, zoneUuid, poolName, poolPath, "primary", poolFactor, TOTAL, totalCapacity)); + } + + for (final ImageStore imageStore : imageStoreDao.findByScope(new ZoneScope(dcId))) { + final StorageStats stats = ApiDBUtils.getSecondaryStorageStatistics(imageStore.getId()); + metricsList.add(new ItemPool(zoneName, zoneUuid, imageStore.getName(), imageStore.getUrl(), "secondary", null, USED, stats != null ? stats.getByteUsed() : 0)); + metricsList.add(new ItemPool(zoneName, zoneUuid, imageStore.getName(), imageStore.getUrl(), "secondary", null, TOTAL, stats != null ? stats.getCapacityBytes() : 0)); + } + } + + private void addIpAddressMetrics(final List metricsList, final long dcId, final String zoneName, final String zoneUuid) { + metricsList.add(new ItemPrivateIp(zoneName, zoneUuid, ALLOCATED, privateIpAddressDao.countIPs(dcId, true))); + metricsList.add(new ItemPrivateIp(zoneName, zoneUuid, TOTAL, privateIpAddressDao.countIPs(dcId, false))); + metricsList.add(new ItemPublicIp(zoneName, zoneUuid, ALLOCATED, publicIpAddressDao.countIPsForNetwork(dcId, true, Vlan.VlanType.VirtualNetwork))); + metricsList.add(new ItemPublicIp(zoneName, zoneUuid, TOTAL, publicIpAddressDao.countIPsForNetwork(dcId, false, Vlan.VlanType.VirtualNetwork))); + metricsList.add(new ItemSharedNetworkIp(zoneName, zoneUuid, ALLOCATED, publicIpAddressDao.countIPsForNetwork(dcId, true, Vlan.VlanType.DirectAttached))); + metricsList.add(new ItemSharedNetworkIp(zoneName, zoneUuid, TOTAL, publicIpAddressDao.countIPsForNetwork(dcId, false, Vlan.VlanType.DirectAttached))); + } + + private void addVlanMetrics(final List metricsList, final long dcId, final String zoneName, final String zoneUuid) { + metricsList.add(new ItemVlan(zoneName, zoneUuid, ALLOCATED, dcDao.countZoneVlans(dcId, true))); + metricsList.add(new ItemVlan(zoneName, zoneUuid, TOTAL, dcDao.countZoneVlans(dcId, false))); + } + + private void addDomainLimits(final List metricsList) { + Long totalCpuLimit = 0L; + Long totalMemoryLimit = 0L; + + for (final DomainJoinVO domain: domainDao.listAll()) { + if (domain == null || domain.getLevel() != 1) { + continue; + } + long cpuLimit = ApiDBUtils.findCorrectResourceLimitForDomain(domain.getCpuLimit(), false, + Resource.ResourceType.cpu, domain.getId()); + if (cpuLimit > 0) { + totalCpuLimit += cpuLimit; + } + + long memoryLimit = ApiDBUtils.findCorrectResourceLimitForDomain(domain.getMemoryLimit(), false, + Resource.ResourceType.memory, domain.getId()); + if (memoryLimit > 0) { + totalMemoryLimit += memoryLimit; + } + } + metricsList.add(new ItemDomainLimitCpu(totalCpuLimit)); + metricsList.add(new ItemDomainLimitMemory(totalMemoryLimit)); + } + + @Override + public void updateMetrics() { + final List latestMetricsItems = new ArrayList(); + try { + for (final DataCenterVO dc : dcDao.listAll()) { + final String zoneName = dc.getName(); + final String zoneUuid = dc.getUuid(); + alertManager.recalculateCapacity(); + addHostMetrics(latestMetricsItems, dc.getId(), zoneName, zoneUuid); + addVMMetrics(latestMetricsItems, dc.getId(), zoneName, zoneUuid); + addVolumeMetrics(latestMetricsItems, dc.getId(), zoneName, zoneUuid); + addStorageMetrics(latestMetricsItems, dc.getId(), zoneName, zoneUuid); + addIpAddressMetrics(latestMetricsItems, dc.getId(), zoneName, zoneUuid); + addVlanMetrics(latestMetricsItems, dc.getId(), zoneName, zoneUuid); + } + addDomainLimits(latestMetricsItems); + } catch (Exception e) { + LOG.warn("Getting metrics failed ", e); + } + metricsItems = latestMetricsItems; + } + + @Override + public String getMetrics() { + final StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("# Cloudstack Prometheus Metrics\n"); + for (final Item item : metricsItems) { + stringBuilder.append(item.toMetricsString()).append("\n"); + } + return stringBuilder.toString(); + } + + private abstract class Item { + String name; + + public Item(final String nm) { + name = nm; + } + + public abstract String toMetricsString(); + } + + class ItemVM extends Item { + String zoneName; + String zoneUuid; + String filter; + long total; + + public ItemVM(final String zn, final String zu, final String st, long cnt) { + super("cloudstack_vms_total"); + zoneName = zn; + zoneUuid = zu; + filter = st; + total = cnt; + } + + @Override + public String toMetricsString() { + return String.format("%s{zone=\"%s\",filter=\"%s\"} %d", name, zoneName, filter, total); + } + } + + class ItemVolume extends Item { + String zoneName; + String zoneUuid; + String filter; + int total; + + public ItemVolume(final String zn, final String zu, final String st, int cnt) { + super("cloudstack_volumes_total"); + zoneName = zn; + zoneUuid = zu; + filter = st; + total = cnt; + } + + @Override + public String toMetricsString() { + return String.format("%s{zone=\"%s\",filter=\"%s\"} %d", name, zoneName, filter, total); + } + } + + class ItemHost extends Item { + String zoneName; + String zoneUuid; + String state; + int total; + + public ItemHost(final String zn, final String zu, final String st, int cnt) { + super("cloudstack_hosts_total"); + zoneName = zn; + zoneUuid = zu; + state = st; + total = cnt; + } + + @Override + public String toMetricsString() { + return String.format("%s{zone=\"%s\",filter=\"%s\"} %d", name, zoneName, state, total); + } + } + + class ItemVMCore extends Item { + String zoneName; + String zoneUuid; + String hostName; + String uuid; + String ip; + String filter; + long core = 0; + + public ItemVMCore(final String zn, final String zu, final String hn, final String hu, final String hip, final String fl, final Long cr) { + super("cloudstack_host_vms_cores_total"); + zoneName = zn; + zoneUuid = zu; + hostName = hn; + uuid = hu; + ip = hip; + filter = fl; + if (cr != null) { + core = cr; + } + } + + @Override + public String toMetricsString() { + if (Strings.isNullOrEmpty(hostName) && Strings.isNullOrEmpty(ip)) { + return String.format("%s{zone=\"%s\",filter=\"%s\"} %d", name, zoneName, filter, core); + } + return String.format("%s{zone=\"%s\",hostname=\"%s\",ip=\"%s\",filter=\"%s\"} %d", name, zoneName, hostName, ip, filter, core); + } + } + + class ItemHostCpu extends Item { + String zoneName; + String zoneUuid; + String hostName; + String uuid; + String ip; + String overProvisioningFactor; + String filter; + double mhertz; + + public ItemHostCpu(final String zn, final String zu, final String hn, final String hu, final String hip, final String of, final String fl, final double mh) { + super("cloudstack_host_cpu_usage_mhz_total"); + zoneName = zn; + zoneUuid = zu; + hostName = hn; + uuid = hu; + ip = hip; + overProvisioningFactor = of; + filter = fl; + mhertz = mh; + } + + @Override + public String toMetricsString() { + if (Strings.isNullOrEmpty(hostName) && Strings.isNullOrEmpty(ip)) { + return String.format("%s{zone=\"%s\",filter=\"%s\"} %.2f", name, zoneName, filter, mhertz); + } + return String.format("%s{zone=\"%s\",hostname=\"%s\",ip=\"%s\",overprovisioningfactor=\"%s\",filter=\"%s\"} %.2f", name, zoneName, hostName, ip, overProvisioningFactor, filter, mhertz); + } + } + + class ItemHostMemory extends Item { + String zoneName; + String zoneUuid; + String hostName; + String uuid; + String ip; + String overProvisioningFactor; + String filter; + double miBytes; + + public ItemHostMemory(final String zn, final String zu, final String hn, final String hu, final String hip, final String of, final String fl, final double membytes) { + super("cloudstack_host_memory_usage_mibs_total"); + zoneName = zn; + zoneUuid = zu; + hostName = hn; + uuid = hu; + ip = hip; + overProvisioningFactor = of; + filter = fl; + miBytes = membytes / (1024.0 * 1024.0); + } + + @Override + public String toMetricsString() { + if (Strings.isNullOrEmpty(hostName) && Strings.isNullOrEmpty(ip)) { + return String.format("%s{zone=\"%s\",filter=\"%s\"} %.2f", name, zoneName, filter, miBytes); + } + return String.format("%s{zone=\"%s\",hostname=\"%s\",ip=\"%s\",overprovisioningfactor=\"%s\",filter=\"%s\"} %.2f", name, zoneName, hostName, ip, overProvisioningFactor, filter, miBytes); + } + } + + class ItemHostVM extends Item { + String zoneName; + String zoneUuid; + String hostName; + String hostUuid; + String hostIp; + int total; + + public ItemHostVM(final String zoneName, final String zoneUuid, final String hostName, final String hostUuid, final String hostIp, final int total) { + super("cloudstack_host_vms_total"); + this.zoneName = zoneName; + this.zoneUuid = zoneUuid; + this.hostName = hostName; + this.hostUuid = hostUuid; + this.hostIp = hostIp; + this.total = total; + } + + @Override + public String toMetricsString() { + return String.format("%s{zone=\"%s\",hostname=\"%s\",address=\"%s\"} %d", name, zoneName, hostName, hostIp, total); + } + } + + class ItemPool extends Item { + String zoneName; + String zoneUuid; + String type; + String overProvisioningFactor; + String filter; + String pname; + String address; + double total; + + public ItemPool(final String zn, final String zu, final String pn, final String pa, final String typ, final String of, final String fl, double cnt) { + super("cloudstack_storage_pool_gibs_total"); + zoneName = zn; + zoneUuid = zu; + pname = pn; + address = pa; + type = typ; + overProvisioningFactor = of; + filter = fl; + total = cnt / (1024.0 * 1024.0 * 1024.0); + } + + @Override + public String toMetricsString() { + if (Strings.isNullOrEmpty(overProvisioningFactor)) { + return String.format("%s{zone=\"%s\",name=\"%s\",address=\"%s\",type=\"%s\",filter=\"%s\"} %.2f", name, zoneName, pname, address, type, filter, total); + } + return String.format("%s{zone=\"%s\",name=\"%s\",address=\"%s\",type=\"%s\",overprovisioningfactor=\"%s\",filter=\"%s\"} %.2f", name, zoneName, pname, address, type, overProvisioningFactor, filter, total); + } + } + + class ItemPrivateIp extends Item { + String zoneName; + String zoneUuid; + String filter; + int total; + + public ItemPrivateIp(final String zn, final String zu, final String fl, int cnt) { + super("cloudstack_private_ips_total"); + zoneName = zn; + zoneUuid = zu; + filter = fl; + total = cnt; + } + + @Override + public String toMetricsString() { + return String.format("%s{zone=\"%s\",filter=\"%s\"} %d", name, zoneName, filter, total); + } + } + + class ItemPublicIp extends Item { + String zoneName; + String zoneUuid; + String filter; + int total; + + public ItemPublicIp(final String zn, final String zu, final String fl, int cnt) { + super("cloudstack_public_ips_total"); + zoneName = zn; + zoneUuid = zu; + filter = fl; + total = cnt; + } + + @Override + public String toMetricsString() { + return String.format("%s{zone=\"%s\",filter=\"%s\"} %d", name, zoneName, filter, total); + } + } + + class ItemSharedNetworkIp extends Item { + String zoneName; + String zoneUuid; + String filter; + int total; + + public ItemSharedNetworkIp(final String zn, final String zu, final String fl, int cnt) { + super("cloudstack_shared_network_ips_total"); + zoneName = zn; + zoneUuid = zu; + filter = fl; + total = cnt; + } + + @Override + public String toMetricsString() { + return String.format("%s{zone=\"%s\",filter=\"%s\"} %d", name, zoneName, filter, total); + } + } + + class ItemVlan extends Item { + String zoneName; + String zoneUuid; + String filter; + int total; + + public ItemVlan(final String zn, final String zu, final String fl, int cnt) { + super("cloudstack_vlans_total"); + zoneName = zn; + zoneUuid = zu; + filter = fl; + total = cnt; + } + + @Override + public String toMetricsString() { + return String.format("%s{zone=\"%s\",filter=\"%s\"} %d", name, zoneName, filter, total); + } + } + + class ItemDomainLimitCpu extends Item { + long cores; + + public ItemDomainLimitCpu(final long c) { + super("cloudstack_domain_limit_cpu_cores_total"); + cores = c; + } + + @Override + public String toMetricsString() { + return String.format("%s %d", name, cores); + } + } + + class ItemDomainLimitMemory extends Item { + long miBytes; + + public ItemDomainLimitMemory(final long mb) { + super("cloudstack_domain_limit_memory_mibs_total"); + miBytes = mb; + } + + @Override + public String toMetricsString() { + return String.format("%s %d", name, miBytes); + } + } +} diff --git a/plugins/integrations/prometheus/src/org/apache/cloudstack/metrics/PrometheusExporterServer.java b/plugins/integrations/prometheus/src/org/apache/cloudstack/metrics/PrometheusExporterServer.java new file mode 100644 index 00000000000..e0303524235 --- /dev/null +++ b/plugins/integrations/prometheus/src/org/apache/cloudstack/metrics/PrometheusExporterServer.java @@ -0,0 +1,33 @@ +// 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.metrics; + +import org.apache.cloudstack.framework.config.ConfigKey; + +import com.cloud.utils.component.Manager; + +public interface PrometheusExporterServer extends Manager { + + ConfigKey EnablePrometheusExporter = new ConfigKey<>("Advanced", Boolean.class, "prometheus.exporter.enable", "false", + "Enable the prometheus exporter plugin, management server restart needed.", true); + + ConfigKey PrometheusExporterServerPort = new ConfigKey<>("Advanced", Integer.class, "prometheus.exporter.port", "9595", + "The prometheus exporter server port", true); + + ConfigKey PrometheusExporterAllowedAddresses = new ConfigKey<>("Advanced", String.class, "prometheus.exporter.allowed.ips", "127.0.0.1", + "List of comma separated prometheus server ips (with no spaces) that should be allowed to access the URLs", true); +} diff --git a/plugins/integrations/prometheus/src/org/apache/cloudstack/metrics/PrometheusExporterServerImpl.java b/plugins/integrations/prometheus/src/org/apache/cloudstack/metrics/PrometheusExporterServerImpl.java new file mode 100644 index 00000000000..a615c65766b --- /dev/null +++ b/plugins/integrations/prometheus/src/org/apache/cloudstack/metrics/PrometheusExporterServerImpl.java @@ -0,0 +1,118 @@ +// 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.metrics; + +import com.cloud.utils.component.ManagerBase; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; +import org.apache.log4j.Logger; + +import javax.inject.Inject; +import java.io.IOException; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.util.Arrays; + +public class PrometheusExporterServerImpl extends ManagerBase implements PrometheusExporterServer, Configurable { + private static final Logger LOG = Logger.getLogger(PrometheusExporterServerImpl.class); + + private static HttpServer httpServer; + + @Inject + private PrometheusExporter prometheusExporter; + + private final static class ExporterHandler implements HttpHandler { + private PrometheusExporter prometheusExporter; + + ExporterHandler(final PrometheusExporter prometheusExporter) { + super(); + this.prometheusExporter = prometheusExporter; + } + + @Override + public void handle(final HttpExchange httpExchange) throws IOException { + final String remoteClientAddress = httpExchange.getRemoteAddress().getAddress().toString().replace("/", ""); + LOG.debug("Prometheus exporter received client request from: " + remoteClientAddress); + String response = "Forbidden"; + int responseCode = 403; + if (Arrays.asList(PrometheusExporterAllowedAddresses.value().split(",")).contains(remoteClientAddress)) { + prometheusExporter.updateMetrics(); + response = prometheusExporter.getMetrics(); + responseCode = 200; + } + httpExchange.getResponseHeaders().set("Content-Type", "text/plain"); + httpExchange.sendResponseHeaders(responseCode, response.length()); + final OutputStream os = httpExchange.getResponseBody(); + os.write(response.getBytes()); + os.close(); + } + } + + @Override + public boolean start() { + if (EnablePrometheusExporter.value()) { + try { + httpServer = HttpServer.create(new InetSocketAddress(PrometheusExporterServerPort.value()), 0); + httpServer.createContext("/metrics", new ExporterHandler(prometheusExporter)); + httpServer.createContext("/", new HttpHandler() { + @Override + public void handle(HttpExchange httpExchange) throws IOException { + final String response = "CloudStack Exporter" + + "

CloudStack Exporter

" + + "

Metrics

" + + ""; + httpExchange.sendResponseHeaders(200, response.length()); + final OutputStream os = httpExchange.getResponseBody(); + os.write(response.getBytes()); + os.close(); + } + }); + httpServer.start(); + LOG.debug("Started prometheus exporter http server"); + } catch (final IOException e) { + LOG.info("Failed to start prometheus exporter http server due to: ", e); + } + } + return true; + } + + @Override + public boolean stop() { + if (httpServer != null) { + httpServer.stop(0); + LOG.debug("Stopped Prometheus exporter http server"); + } + return true; + } + + @Override + public String getConfigComponentName() { + return PrometheusExporter.class.getSimpleName(); + } + + @Override + public ConfigKey[] getConfigKeys() { + return new ConfigKey[] { + EnablePrometheusExporter, + PrometheusExporterServerPort, + PrometheusExporterAllowedAddresses + }; + } +} diff --git a/plugins/pom.xml b/plugins/pom.xml index 28104b4047c..1ee7af58c60 100755 --- a/plugins/pom.xml +++ b/plugins/pom.xml @@ -107,6 +107,7 @@ network-elements/vxlan network-elements/globodns database/quota + integrations/prometheus