Skip to content

Commit 4de8c2b

Browse files
authored
Add a Prometheus metric to track host certificate expiry (#12613)
1 parent b45726f commit 4de8c2b

File tree

2 files changed

+151
-0
lines changed

2 files changed

+151
-0
lines changed

plugins/integrations/prometheus/src/main/java/org/apache/cloudstack/metrics/PrometheusExporterImpl.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.apache.cloudstack.metrics;
1818

1919
import java.math.BigDecimal;
20+
import java.security.cert.X509Certificate;
2021
import java.util.ArrayList;
2122
import java.util.HashMap;
2223
import java.util.HashSet;
@@ -26,6 +27,7 @@
2627

2728
import javax.inject.Inject;
2829

30+
import org.apache.cloudstack.ca.CAManager;
2931
import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope;
3032
import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
3133
import org.apache.commons.lang3.StringUtils;
@@ -133,6 +135,8 @@ public String toString() {
133135
private ResourceCountDao _resourceCountDao;
134136
@Inject
135137
private HostTagsDao _hostTagsDao;
138+
@Inject
139+
private CAManager caManager;
136140

137141
public PrometheusExporterImpl() {
138142
super();
@@ -216,6 +220,9 @@ private void addHostMetrics(final List<Item> metricsList, final long dcId, final
216220
}
217221

218222
metricsList.add(new ItemHostVM(zoneName, zoneUuid, host.getName(), host.getUuid(), host.getPrivateIpAddress(), vmDao.listByHostId(host.getId()).size()));
223+
224+
addSSLCertificateExpirationMetrics(metricsList, zoneName, zoneUuid, host);
225+
219226
final CapacityVO coreCapacity = capacityDao.findByHostIdType(host.getId(), Capacity.CAPACITY_TYPE_CPU_CORE);
220227

221228
if (coreCapacity == null && !host.isInMaintenanceStates()){
@@ -253,6 +260,18 @@ private void addHostMetrics(final List<Item> metricsList, final long dcId, final
253260
addHostTagsMetrics(metricsList, dcId, zoneName, zoneUuid, totalHosts, upHosts, downHosts, total, up, down);
254261
}
255262

263+
private void addSSLCertificateExpirationMetrics(List<Item> metricsList, String zoneName, String zoneUuid, HostVO host) {
264+
if (caManager == null || caManager.getActiveCertificatesMap() == null) {
265+
return;
266+
}
267+
X509Certificate cert = caManager.getActiveCertificatesMap().getOrDefault(host.getPrivateIpAddress(), null);
268+
if (cert == null) {
269+
return;
270+
}
271+
long certExpiryEpoch = cert.getNotAfter().getTime() / 1000; // Convert to epoch seconds
272+
metricsList.add(new ItemHostCertExpiry(zoneName, zoneUuid, host.getName(), host.getUuid(), host.getPrivateIpAddress(), certExpiryEpoch));
273+
}
274+
256275
private String markTagMaps(HostVO host, Map<String, Integer> totalHosts, Map<String, Integer> upHosts, Map<String, Integer> downHosts) {
257276
List<HostTagVO> hostTagVOS = _hostTagsDao.getHostTags(host.getId());
258277
List<String> hostTags = new ArrayList<>();
@@ -1049,4 +1068,28 @@ public String toMetricsString() {
10491068
return String.format("%s{zone=\"%s\",cpu=\"%d\",memory=\"%d\"} %d", name, zoneName, cpu, memory, total);
10501069
}
10511070
}
1071+
1072+
class ItemHostCertExpiry extends Item {
1073+
String zoneName;
1074+
String zoneUuid;
1075+
String hostName;
1076+
String hostUuid;
1077+
String hostIp;
1078+
long expiryTimestamp;
1079+
1080+
public ItemHostCertExpiry(final String zoneName, final String zoneUuid, final String hostName, final String hostUuid, final String hostIp, final long expiry) {
1081+
super("cloudstack_host_cert_expiry_timestamp");
1082+
this.zoneName = zoneName;
1083+
this.zoneUuid = zoneUuid;
1084+
this.hostName = hostName;
1085+
this.hostUuid = hostUuid;
1086+
this.hostIp = hostIp;
1087+
this.expiryTimestamp = expiry;
1088+
}
1089+
1090+
@Override
1091+
public String toMetricsString() {
1092+
return String.format("%s{zone=\"%s\",hostname=\"%s\",ip=\"%s\"} %d", name, zoneName, hostName, hostIp, expiryTimestamp);
1093+
}
1094+
}
10521095
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
package org.apache.cloudstack.metrics;
18+
19+
import static org.junit.Assert.assertEquals;
20+
import static org.junit.Assert.assertTrue;
21+
22+
import org.junit.Test;
23+
24+
public class PrometheusExporterImplTest {
25+
26+
private static final String TEST_ZONE_NAME = "zone1";
27+
private static final String TEST_ZONE_UUID = "zone-uuid-1";
28+
private static final String TEST_HOST_NAME = "host1";
29+
private static final String TEST_HOST_UUID = "host-uuid-1";
30+
private static final String TEST_HOST_IP = "192.168.1.10";
31+
private static final long CERT_EXPIRY_TIME = 1735689600000L; // 2025-01-01 00:00:00 UTC
32+
private static final long CERT_EXPIRY_EPOCH = CERT_EXPIRY_TIME / 1000;
33+
34+
@Test
35+
public void testItemHostCertExpiryFormat() {
36+
PrometheusExporterImpl exporter = new PrometheusExporterImpl();
37+
PrometheusExporterImpl.ItemHostCertExpiry item = exporter.new ItemHostCertExpiry(
38+
TEST_ZONE_NAME,
39+
TEST_ZONE_UUID,
40+
TEST_HOST_NAME,
41+
TEST_HOST_UUID,
42+
TEST_HOST_IP,
43+
CERT_EXPIRY_EPOCH
44+
);
45+
46+
String metricsString = item.toMetricsString();
47+
String expected = String.format(
48+
"cloudstack_host_cert_expiry_timestamp{zone=\"%s\",hostname=\"%s\",ip=\"%s\"} %d",
49+
TEST_ZONE_NAME,
50+
TEST_HOST_NAME,
51+
TEST_HOST_IP,
52+
CERT_EXPIRY_EPOCH
53+
);
54+
assertEquals("Certificate expiry metric format should match expected format", expected, metricsString);
55+
}
56+
57+
@Test
58+
public void testItemHostCertExpiryContainsCorrectMetricName() {
59+
PrometheusExporterImpl exporter = new PrometheusExporterImpl();
60+
PrometheusExporterImpl.ItemHostCertExpiry item = exporter.new ItemHostCertExpiry(
61+
TEST_ZONE_NAME,
62+
TEST_ZONE_UUID,
63+
TEST_HOST_NAME,
64+
TEST_HOST_UUID,
65+
TEST_HOST_IP,
66+
CERT_EXPIRY_EPOCH
67+
);
68+
69+
String metricsString = item.toMetricsString();
70+
assertTrue("Metric should contain correct metric name",
71+
metricsString.contains("cloudstack_host_cert_expiry_timestamp"));
72+
}
73+
74+
@Test
75+
public void testItemHostCertExpiryContainsAllLabels() {
76+
PrometheusExporterImpl exporter = new PrometheusExporterImpl();
77+
PrometheusExporterImpl.ItemHostCertExpiry item = exporter.new ItemHostCertExpiry(
78+
TEST_ZONE_NAME,
79+
TEST_ZONE_UUID,
80+
TEST_HOST_NAME,
81+
TEST_HOST_UUID,
82+
TEST_HOST_IP,
83+
CERT_EXPIRY_EPOCH
84+
);
85+
86+
String metricsString = item.toMetricsString();
87+
assertTrue("Metric should contain zone label", metricsString.contains("zone=\"" + TEST_ZONE_NAME + "\""));
88+
assertTrue("Metric should contain hostname label", metricsString.contains("hostname=\"" + TEST_HOST_NAME + "\""));
89+
assertTrue("Metric should contain ip label", metricsString.contains("ip=\"" + TEST_HOST_IP + "\""));
90+
}
91+
92+
@Test
93+
public void testItemHostCertExpiryContainsTimestampValue() {
94+
PrometheusExporterImpl exporter = new PrometheusExporterImpl();
95+
PrometheusExporterImpl.ItemHostCertExpiry item = exporter.new ItemHostCertExpiry(
96+
TEST_ZONE_NAME,
97+
TEST_ZONE_UUID,
98+
TEST_HOST_NAME,
99+
TEST_HOST_UUID,
100+
TEST_HOST_IP,
101+
CERT_EXPIRY_EPOCH
102+
);
103+
104+
String metricsString = item.toMetricsString();
105+
assertTrue("Metric should contain correct timestamp value",
106+
metricsString.endsWith(" " + CERT_EXPIRY_EPOCH));
107+
}
108+
}

0 commit comments

Comments
 (0)