torrust_tracker_deployer_lib/domain/prometheus/
config.rs1use std::num::NonZeroU32;
6
7use serde::{Deserialize, Serialize};
8
9use crate::domain::topology::{
10 EnabledServices, Network, NetworkDerivation, PortBinding, PortDerivation, Service,
11};
12use crate::shared::docker_image::DockerImage;
13
14const DEFAULT_SCRAPE_INTERVAL_SECS: u32 = 15;
19
20pub const PROMETHEUS_DOCKER_IMAGE_REPOSITORY: &str = "prom/prometheus";
22
23pub const PROMETHEUS_DOCKER_IMAGE_TAG: &str = "v3.11.2";
25
26#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
47pub struct PrometheusConfig {
48 scrape_interval_in_secs: NonZeroU32,
54}
55
56impl PrometheusConfig {
57 #[must_use]
74 pub const fn new(scrape_interval_in_secs: NonZeroU32) -> Self {
75 Self {
76 scrape_interval_in_secs,
77 }
78 }
79
80 #[must_use]
84 pub fn scrape_interval_in_secs(&self) -> u32 {
85 self.scrape_interval_in_secs.get()
86 }
87
88 #[must_use]
101 pub fn docker_image() -> DockerImage {
102 DockerImage::new(
103 PROMETHEUS_DOCKER_IMAGE_REPOSITORY,
104 PROMETHEUS_DOCKER_IMAGE_TAG,
105 )
106 }
107}
108
109impl Default for PrometheusConfig {
110 fn default() -> Self {
111 Self {
112 scrape_interval_in_secs: NonZeroU32::new(DEFAULT_SCRAPE_INTERVAL_SECS)
114 .expect("DEFAULT_SCRAPE_INTERVAL_SECS is non-zero"),
115 }
116 }
117}
118
119impl PortDerivation for PrometheusConfig {
120 fn derive_ports(&self) -> Vec<PortBinding> {
127 vec![PortBinding::localhost_tcp(
128 9090,
129 "Prometheus metrics (localhost only)",
130 )]
131 }
132}
133
134impl NetworkDerivation for PrometheusConfig {
135 fn derive_networks(&self, enabled_services: &EnabledServices) -> Vec<Network> {
141 let mut networks = vec![Network::Metrics];
142
143 if enabled_services.has(Service::Grafana) {
145 networks.push(Network::Visualization);
146 }
147
148 networks
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use std::num::NonZeroU32;
155
156 use super::*;
157
158 #[test]
159 fn it_should_create_prometheus_config_with_default_values() {
160 let config = PrometheusConfig::default();
161 assert_eq!(
162 config.scrape_interval_in_secs(),
163 DEFAULT_SCRAPE_INTERVAL_SECS
164 );
165 }
166
167 #[test]
168 fn it_should_create_prometheus_config_with_custom_interval() {
169 let interval = NonZeroU32::new(30).expect("30 is non-zero");
170 let config = PrometheusConfig::new(interval);
171 assert_eq!(config.scrape_interval_in_secs(), 30);
172 }
173
174 #[test]
175 fn it_should_serialize_to_json() {
176 let interval = NonZeroU32::new(20).expect("20 is non-zero");
177 let config = PrometheusConfig::new(interval);
178
179 let json = serde_json::to_value(&config).unwrap();
180 assert_eq!(json["scrape_interval_in_secs"], 20);
181 }
182
183 #[test]
184 fn it_should_deserialize_from_json() {
185 let json = serde_json::json!({
186 "scrape_interval_in_secs": 25
187 });
188
189 let config: PrometheusConfig = serde_json::from_value(json).unwrap();
190 assert_eq!(config.scrape_interval_in_secs(), 25);
191 }
192
193 #[test]
194 fn it_should_support_different_scrape_intervals() {
195 let fast = PrometheusConfig::new(NonZeroU32::new(5).expect("5 is non-zero"));
196 let medium = PrometheusConfig::new(NonZeroU32::new(15).expect("15 is non-zero"));
197 let slow = PrometheusConfig::new(NonZeroU32::new(300).expect("300 is non-zero"));
198
199 assert_eq!(fast.scrape_interval_in_secs(), 5);
200 assert_eq!(medium.scrape_interval_in_secs(), 15);
201 assert_eq!(slow.scrape_interval_in_secs(), 300);
202 }
203
204 #[test]
205 fn it_should_reject_zero_interval_at_type_level() {
206 let result = NonZeroU32::new(0);
208 assert!(result.is_none());
209 }
210
211 mod port_derivation {
216 use std::net::{IpAddr, Ipv4Addr};
217
218 use super::*;
219 use crate::domain::tracker::Protocol;
220
221 #[test]
222 fn it_should_derive_prometheus_port_on_localhost_only() {
223 let config = PrometheusConfig::default();
225
226 let ports = config.derive_ports();
227
228 assert_eq!(ports.len(), 1);
229 let port = &ports[0];
230 assert_eq!(port.host_port(), 9090);
231 assert_eq!(port.container_port(), 9090);
232 assert_eq!(port.protocol(), Protocol::Tcp);
233 assert_eq!(port.host_ip(), Some(IpAddr::V4(Ipv4Addr::LOCALHOST)));
234 }
235
236 #[test]
237 fn it_should_include_description_for_prometheus_port() {
238 let config = PrometheusConfig::default();
239
240 let ports = config.derive_ports();
241
242 assert_eq!(
243 ports[0].description(),
244 "Prometheus metrics (localhost only)"
245 );
246 }
247 }
248}