Skip to main content

rocketmq_client_rust/nameserver_discovery/
snapshot.rs

1// Copyright 2023 The RocketMQ Rust Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::net::SocketAddr;
16use std::sync::Arc;
17use std::time::Duration;
18
19use tokio::time::Instant;
20
21use super::NameServerAuthority;
22
23/// Low-cardinality discovery source categories exposed by status snapshots.
24#[derive(Clone, Copy, Debug, Eq, PartialEq)]
25pub enum NameServerDiscoverySourceKind {
26    Dns,
27}
28
29/// Freshness of the last-known-good discovery snapshot.
30#[derive(Clone, Copy, Debug, Eq, PartialEq)]
31pub enum NameServerDiscoveryFreshness {
32    Fresh,
33    Stale,
34    Unavailable,
35}
36
37/// Sanitized DNS failure categories. Resolver messages and queried names are not exposed.
38#[derive(Clone, Copy, Debug, Eq, PartialEq)]
39pub enum NameServerDiscoveryErrorCategory {
40    Empty,
41    NxDomain,
42    ServFail,
43    Timeout,
44    Other,
45}
46
47/// Read-only, endpoint-free discovery status suitable for diagnostics and readiness policies.
48#[derive(Clone, Debug, Eq, PartialEq)]
49pub struct NameServerDiscoveryStatus {
50    source_kind: NameServerDiscoverySourceKind,
51    generation: u64,
52    freshness: NameServerDiscoveryFreshness,
53    last_successful_refresh_age: Duration,
54    snapshot_age: Duration,
55    ipv4_endpoint_count: usize,
56    ipv6_endpoint_count: usize,
57    healthy_endpoint_count: usize,
58    probing_endpoint_count: usize,
59    draining_endpoint_count: usize,
60    circuit_open_endpoint_count: usize,
61    next_refresh_delay: Duration,
62    last_error_category: Option<NameServerDiscoveryErrorCategory>,
63}
64
65impl NameServerDiscoveryStatus {
66    #[allow(
67        clippy::too_many_arguments,
68        reason = "the fields form one immutable diagnostic snapshot"
69    )]
70    pub(crate) fn new(
71        generation: u64,
72        freshness: NameServerDiscoveryFreshness,
73        last_successful_refresh_age: Duration,
74        snapshot_age: Duration,
75        ipv4_endpoint_count: usize,
76        ipv6_endpoint_count: usize,
77        next_refresh_delay: Duration,
78        last_error_category: Option<NameServerDiscoveryErrorCategory>,
79    ) -> Self {
80        Self {
81            source_kind: NameServerDiscoverySourceKind::Dns,
82            generation,
83            freshness,
84            last_successful_refresh_age,
85            snapshot_age,
86            ipv4_endpoint_count,
87            ipv6_endpoint_count,
88            healthy_endpoint_count: 0,
89            probing_endpoint_count: 0,
90            draining_endpoint_count: 0,
91            circuit_open_endpoint_count: 0,
92            next_refresh_delay,
93            last_error_category,
94        }
95    }
96
97    #[must_use]
98    pub fn source_kind(&self) -> NameServerDiscoverySourceKind {
99        self.source_kind
100    }
101
102    #[must_use]
103    pub fn generation(&self) -> u64 {
104        self.generation
105    }
106
107    #[must_use]
108    pub fn freshness(&self) -> NameServerDiscoveryFreshness {
109        self.freshness
110    }
111
112    #[must_use]
113    pub fn last_successful_refresh_age(&self) -> Duration {
114        self.last_successful_refresh_age
115    }
116
117    #[must_use]
118    pub fn snapshot_age(&self) -> Duration {
119        self.snapshot_age
120    }
121
122    #[must_use]
123    pub fn ipv4_endpoint_count(&self) -> usize {
124        self.ipv4_endpoint_count
125    }
126
127    #[must_use]
128    pub fn ipv6_endpoint_count(&self) -> usize {
129        self.ipv6_endpoint_count
130    }
131
132    #[must_use]
133    pub fn healthy_endpoint_count(&self) -> usize {
134        self.healthy_endpoint_count
135    }
136
137    #[must_use]
138    pub fn probing_endpoint_count(&self) -> usize {
139        self.probing_endpoint_count
140    }
141
142    #[must_use]
143    pub fn draining_endpoint_count(&self) -> usize {
144        self.draining_endpoint_count
145    }
146
147    #[must_use]
148    pub fn circuit_open_endpoint_count(&self) -> usize {
149        self.circuit_open_endpoint_count
150    }
151
152    pub(crate) fn with_transport_counts(
153        mut self,
154        healthy: usize,
155        probing: usize,
156        draining: usize,
157        circuit_open: usize,
158    ) -> Self {
159        self.healthy_endpoint_count = healthy;
160        self.probing_endpoint_count = probing;
161        self.draining_endpoint_count = draining;
162        self.circuit_open_endpoint_count = circuit_open;
163        self
164    }
165
166    #[must_use]
167    pub fn next_refresh_delay(&self) -> Duration {
168        self.next_refresh_delay
169    }
170
171    #[must_use]
172    pub fn last_error_category(&self) -> Option<NameServerDiscoveryErrorCategory> {
173        self.last_error_category
174    }
175}
176
177/// A physical endpoint resolved from a logical NameServer authority.
178#[derive(Clone, Debug, Eq, PartialEq)]
179pub struct ResolvedNameServerEndpoint {
180    authority: NameServerAuthority,
181    socket_addr: SocketAddr,
182}
183
184impl ResolvedNameServerEndpoint {
185    pub(crate) fn new(authority: NameServerAuthority, socket_addr: SocketAddr) -> Self {
186        Self { authority, socket_addr }
187    }
188
189    /// Returns the logical authority used for DNS and future TLS identity checks.
190    #[must_use]
191    pub fn authority(&self) -> &NameServerAuthority {
192        &self.authority
193    }
194
195    /// Returns the physical socket address used to establish a connection.
196    #[must_use]
197    pub fn socket_addr(&self) -> SocketAddr {
198        self.socket_addr
199    }
200}
201
202#[derive(Clone, Copy, Debug, Eq, PartialEq)]
203pub(crate) enum Freshness {
204    Fresh,
205    Stale,
206    Unavailable,
207}
208
209#[derive(Clone, Debug)]
210pub(crate) struct EndpointSnapshot {
211    pub(crate) generation: u64,
212    pub(crate) resolved_at: Instant,
213    pub(crate) valid_until: Instant,
214    pub(crate) freshness: Freshness,
215    pub(crate) endpoints: Arc<[ResolvedNameServerEndpoint]>,
216}
217
218impl EndpointSnapshot {
219    pub(crate) fn unavailable(now: Instant) -> Self {
220        Self {
221            generation: 0,
222            resolved_at: now,
223            valid_until: now,
224            freshness: Freshness::Unavailable,
225            endpoints: Arc::from([]),
226        }
227    }
228
229    pub(crate) fn same_endpoint_set(&self, other: &[ResolvedNameServerEndpoint]) -> bool {
230        if self.endpoints.len() != other.len() {
231            return false;
232        }
233        let mut current = self
234            .endpoints
235            .iter()
236            .map(ResolvedNameServerEndpoint::socket_addr)
237            .collect::<Vec<_>>();
238        let mut candidate = other
239            .iter()
240            .map(ResolvedNameServerEndpoint::socket_addr)
241            .collect::<Vec<_>>();
242        current.sort_unstable();
243        candidate.sort_unstable();
244        current == candidate
245    }
246}