1use std::{
2 collections::{
3 BTreeMap, BTreeSet, HashMap, HashSet, btree_map::Entry as BTreeMapEntry,
4 hash_map::DefaultHasher,
5 },
6 fs::File,
7 hash::{Hash, Hasher},
8 io::Write,
9 iter::repeat,
10 net::SocketAddr,
11};
12
13use prost::{Message, UnknownEnumValue};
14
15use crate::{
16 ObjectKind,
17 certificate::{CertificateError, Fingerprint, calculate_fingerprint},
18 config::validate_sni_pattern,
19 proto::{
20 command::{
21 ActivateListener, AddBackend, AddCertificate, CertificateAndKey, Cluster,
22 ClusterInformation, CustomHttpAnswers, DeactivateListener, FrontendFilters,
23 HealthChecksList, HttpListenerConfig, HttpsListenerConfig, InitialState,
24 ListedFrontends, ListenerType, ListenersList, PathRule, QueryCertificatesFilters,
25 RemoveBackend, RemoveCertificate, RemoveListener, ReplaceCertificate, Request,
26 RequestCounts, RequestHttpFrontend, RequestTcpFrontend, RequestUdpFrontend,
27 SetHealthCheck, SocketAddress, TcpListenerConfig, UdpListenerConfig,
28 UpdateHttpListenerConfig, UpdateHttpsListenerConfig, UpdateTcpListenerConfig,
29 UpdateUdpListenerConfig, WorkerRequest, request::RequestType,
30 },
31 display::format_request_type,
32 },
33 response::{Backend, HttpFrontend, TcpFrontend, UdpFrontend},
34};
35
36pub type ClusterId = String;
38
39#[derive(thiserror::Error, Debug)]
40pub enum StateError {
41 #[error("Request came in empty")]
42 EmptyRequest,
43 #[error("dispatching this request did not bring any change to the state")]
44 NoChange,
45 #[error("State can not handle this request")]
46 UndispatchableRequest,
47 #[error("Did not find {kind:?} with address or id '{id}'")]
48 NotFound { kind: ObjectKind, id: String },
49 #[error("{kind:?} '{id}' already exists")]
50 Exists { kind: ObjectKind, id: String },
51 #[error("Wrong field value: {0}")]
52 WrongFieldValue(UnknownEnumValue),
53 #[error("Could not add certificate: {0}")]
54 AddCertificate(CertificateError),
55 #[error("Could not remove certificate: {0}")]
56 RemoveCertificate(String),
57 #[error("Could not replace certificate: {0}")]
58 ReplaceCertificate(String),
59 #[error(
60 "Could not convert the frontend to an insertable one. Frontend: {frontend} error: {error}"
61 )]
62 FrontendConversion { frontend: String, error: String },
63 #[error("Could not write state to file: {0}")]
64 FileError(std::io::Error),
65 #[error("Invalid value for field '{field}': {reason}")]
66 InvalidValue {
67 field: &'static str,
68 reason: &'static str,
69 },
70 #[error("invalid TCP frontend for address {address}: {reason}")]
76 InvalidTcpFrontend { address: SocketAddr, reason: String },
77}
78
79#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
88pub struct ConfigState {
89 pub clusters: BTreeMap<ClusterId, Cluster>,
90 pub backends: BTreeMap<ClusterId, Vec<Backend>>,
91 pub http_listeners: BTreeMap<SocketAddr, HttpListenerConfig>,
93 pub https_listeners: BTreeMap<SocketAddr, HttpsListenerConfig>,
95 pub tcp_listeners: BTreeMap<SocketAddr, TcpListenerConfig>,
97 pub udp_listeners: BTreeMap<SocketAddr, UdpListenerConfig>,
99 pub http_fronts: BTreeMap<String, HttpFrontend>,
102 pub https_fronts: BTreeMap<String, HttpFrontend>,
104 pub tcp_fronts: HashMap<ClusterId, Vec<TcpFrontend>>,
105 pub udp_fronts: HashMap<ClusterId, Vec<UdpFrontend>>,
106 pub certificates: HashMap<SocketAddr, HashMap<Fingerprint, CertificateAndKey>>,
107 pub request_counts: BTreeMap<String, i32>,
109}
110
111impl ConfigState {
112 pub fn new() -> Self {
113 Self::default()
114 }
115
116 pub fn dispatch(&mut self, request: &Request) -> Result<(), StateError> {
117 let request_type = match &request.request_type {
118 Some(t) => t,
119 None => return Err(StateError::EmptyRequest),
120 };
121
122 self.increment_request_count(request);
123
124 let result = match request_type {
125 RequestType::AddCluster(cluster) => self.add_cluster(cluster),
126 RequestType::RemoveCluster(cluster_id) => self.remove_cluster(cluster_id),
127 RequestType::AddHttpListener(listener) => self.add_http_listener(listener),
128 RequestType::AddHttpsListener(listener) => self.add_https_listener(listener),
129 RequestType::AddTcpListener(listener) => self.add_tcp_listener(listener),
130 RequestType::AddUdpListener(listener) => self.add_udp_listener(listener),
131 RequestType::RemoveListener(remove) => self.remove_listener(remove),
132 RequestType::ActivateListener(activate) => self.activate_listener(activate),
133 RequestType::DeactivateListener(deactivate) => self.deactivate_listener(deactivate),
134 RequestType::AddHttpFrontend(front) => self.add_http_frontend(front),
135 RequestType::RemoveHttpFrontend(front) => self.remove_http_frontend(front),
136 RequestType::AddCertificate(add) => self.add_certificate(add),
137 RequestType::RemoveCertificate(remove) => self.remove_certificate(remove),
138 RequestType::ReplaceCertificate(replace) => self.replace_certificate(replace),
139 RequestType::AddHttpsFrontend(front) => self.add_https_frontend(front),
140 RequestType::RemoveHttpsFrontend(front) => self.remove_https_frontend(front),
141 RequestType::AddTcpFrontend(front) => self.add_tcp_frontend(front),
142 RequestType::RemoveTcpFrontend(front) => self.remove_tcp_frontend(front),
143 RequestType::AddUdpFrontend(front) => self.add_udp_frontend(front),
144 RequestType::RemoveUdpFrontend(front) => self.remove_udp_frontend(front),
145 RequestType::AddBackend(add_backend) => self.add_backend(add_backend),
146 RequestType::RemoveBackend(backend) => self.remove_backend(backend),
147 RequestType::UpdateHttpListener(patch) => self.update_http_listener(patch),
148 RequestType::UpdateHttpsListener(patch) => self.update_https_listener(patch),
149 RequestType::UpdateTcpListener(patch) => self.update_tcp_listener(patch),
150 RequestType::UpdateUdpListener(patch) => self.update_udp_listener(patch),
151 RequestType::SetHealthCheck(set) => self.set_health_check(set),
152 RequestType::RemoveHealthCheck(cluster_id) => self.remove_health_check(cluster_id),
153
154 RequestType::Logging(_)
161 | RequestType::CountRequests(_)
162 | RequestType::Status(_)
163 | RequestType::SoftStop(_)
164 | RequestType::QueryCertificatesFromWorkers(_)
165 | RequestType::QueryClusterById(_)
166 | RequestType::QueryClustersByDomain(_)
167 | RequestType::QueryMetrics(_)
168 | RequestType::QueryClustersHashes(_)
169 | RequestType::ConfigureMetrics(_)
170 | RequestType::SetMetricDetail(_)
171 | RequestType::ReturnListenSockets(_)
172 | RequestType::SetMaxConnectionsPerIp(_)
173 | RequestType::QueryMaxConnectionsPerIp(_)
174 | RequestType::HardStop(_) => Ok(()),
175
176 _other_request => Err(StateError::UndispatchableRequest),
177 };
178
179 #[cfg(debug_assertions)]
185 self.check_invariants();
186
187 result
188 }
189
190 #[cfg(debug_assertions)]
201 fn check_invariants(&self) {
202 for (addr, listener) in &self.http_listeners {
206 debug_assert_eq!(
207 SocketAddr::from(listener.address),
208 *addr,
209 "http_listener value address must match its map key"
210 );
211 }
212 for (addr, listener) in &self.https_listeners {
213 debug_assert_eq!(
214 SocketAddr::from(listener.address),
215 *addr,
216 "https_listener value address must match its map key"
217 );
218 }
219 for (addr, listener) in &self.tcp_listeners {
220 debug_assert_eq!(
221 SocketAddr::from(listener.address),
222 *addr,
223 "tcp_listener value address must match its map key"
224 );
225 }
226
227 for (cluster_id, cluster) in &self.clusters {
230 debug_assert_eq!(
231 &cluster.cluster_id, cluster_id,
232 "cluster value cluster_id must match its map key"
233 );
234 }
235
236 for (cluster_id, backends) in &self.backends {
241 for backend in backends {
242 debug_assert_eq!(
243 &backend.cluster_id, cluster_id,
244 "backend cluster_id must match its bucket key"
245 );
246 }
247 let unique: HashSet<(&String, &SocketAddr)> = backends
248 .iter()
249 .map(|b| (&b.backend_id, &b.address))
250 .collect();
251 debug_assert_eq!(
252 unique.len(),
253 backends.len(),
254 "backends within a cluster must be unique on (backend_id, address)"
255 );
256 }
257
258 for (cluster_id, fronts) in &self.tcp_fronts {
262 for front in fronts {
263 debug_assert_eq!(
264 &front.cluster_id, cluster_id,
265 "tcp_frontend cluster_id must match its bucket key"
266 );
267 }
268 let unique: HashSet<&TcpFrontend> = fronts.iter().collect();
269 debug_assert_eq!(
270 unique.len(),
271 fronts.len(),
272 "tcp frontends within a cluster must be unique"
273 );
274 }
275
276 let raw_frontends = self.http_fronts.len()
287 + self.https_fronts.len()
288 + self.count_tcp_frontends_raw()
289 + self.udp_fronts.values().map(|v| v.len()).sum::<usize>();
290 debug_assert_eq!(
291 self.count_frontends(),
292 raw_frontends,
293 "count_frontends must equal the sum of all frontend map entries"
294 );
295 let raw_backends: usize = self.backends.values().map(|v| v.len()).sum();
296 debug_assert_eq!(
297 self.count_backends(),
298 raw_backends,
299 "count_backends must equal the sum of all backend Vec lengths"
300 );
301 }
302
303 fn count_tcp_frontends_raw(&self) -> usize {
311 self.tcp_fronts.values().map(|v| v.len()).sum()
312 }
313
314 fn increment_request_count(&mut self, request: &Request) {
316 if let Some(request_type) = &request.request_type {
317 let count = self
318 .request_counts
319 .entry(format_request_type(request_type).to_owned())
320 .or_insert(1);
321 *count += 1;
322 }
323 }
324
325 pub fn get_request_counts(&self) -> RequestCounts {
326 RequestCounts {
327 map: self.request_counts.clone(),
328 }
329 }
330
331 fn add_cluster(&mut self, cluster: &Cluster) -> Result<(), StateError> {
332 if let Some(hc) = cluster.health_check.as_ref()
339 && let Err(reason) = crate::config::validate_health_check_config(hc)
340 {
341 return Err(StateError::InvalidValue {
342 field: "health_check",
343 reason,
344 });
345 }
346 let cluster = cluster.clone();
347 let cluster_id = cluster.cluster_id.clone();
351 self.clusters.insert(cluster_id.clone(), cluster);
352 debug_assert!(
353 self.clusters.contains_key(&cluster_id),
354 "add_cluster must leave the cluster present in the map"
355 );
356 debug_assert_eq!(
357 self.clusters.get(&cluster_id).map(|c| &c.cluster_id),
358 Some(&cluster_id),
359 "stored cluster must be keyed by its own cluster_id"
360 );
361 Ok(())
362 }
363
364 fn remove_cluster(&mut self, cluster_id: &str) -> Result<(), StateError> {
365 let before = self.clusters.len();
366 match self.clusters.remove(cluster_id) {
367 Some(_) => {
368 debug_assert!(
369 !self.clusters.contains_key(cluster_id),
370 "remove_cluster must evict the cluster"
371 );
372 debug_assert_eq!(
373 self.clusters.len(),
374 before - 1,
375 "remove_cluster must drop exactly one entry"
376 );
377 Ok(())
378 }
379 None => {
380 debug_assert_eq!(
381 self.clusters.len(),
382 before,
383 "a failed remove_cluster must not mutate the map"
384 );
385 Err(StateError::NotFound {
386 kind: ObjectKind::Cluster,
387 id: cluster_id.to_owned(),
388 })
389 }
390 }
391 }
392
393 fn set_health_check(&mut self, set: &SetHealthCheck) -> Result<(), StateError> {
394 if let Err(reason) = crate::config::validate_health_check_config(&set.config) {
401 return Err(StateError::InvalidValue {
402 field: "health_check",
403 reason,
404 });
405 }
406 match self.clusters.get_mut(&set.cluster_id) {
407 Some(cluster) => {
408 cluster.health_check = Some(set.config.to_owned());
409 Ok(())
410 }
411 None => Err(StateError::NotFound {
412 kind: ObjectKind::Cluster,
413 id: set.cluster_id.to_owned(),
414 }),
415 }
416 }
417
418 fn remove_health_check(&mut self, cluster_id: &str) -> Result<(), StateError> {
419 match self.clusters.get_mut(cluster_id) {
420 Some(cluster) => {
421 cluster.health_check = None;
422 Ok(())
423 }
424 None => Err(StateError::NotFound {
425 kind: ObjectKind::Cluster,
426 id: cluster_id.to_owned(),
427 }),
428 }
429 }
430
431 pub fn list_health_checks(&self, cluster_id: Option<&str>) -> HealthChecksList {
432 let map = self
433 .clusters
434 .iter()
435 .filter(|(id, _)| cluster_id.is_none_or(|filter| filter == id.as_str()))
436 .filter_map(|(id, cluster)| {
437 cluster
438 .health_check
439 .as_ref()
440 .map(|hc| (id.to_owned(), hc.to_owned()))
441 })
442 .collect();
443 HealthChecksList { map }
444 }
445
446 fn add_http_listener(&mut self, listener: &HttpListenerConfig) -> Result<(), StateError> {
447 let address: SocketAddr = listener.address.into();
448 let before = self.http_listeners.len();
449 match self.http_listeners.entry(address) {
450 BTreeMapEntry::Vacant(vacant_entry) => vacant_entry.insert(listener.clone()),
451 BTreeMapEntry::Occupied(_) => {
452 debug_assert_eq!(
453 self.http_listeners.len(),
454 before,
455 "a rejected duplicate add_http_listener must not mutate the map"
456 );
457 return Err(StateError::Exists {
458 kind: ObjectKind::HttpListener,
459 id: address.to_string(),
460 });
461 }
462 };
463 debug_assert!(
464 self.http_listeners.contains_key(&address),
465 "add_http_listener must insert the listener under its address"
466 );
467 debug_assert_eq!(
468 self.http_listeners.len(),
469 before + 1,
470 "add_http_listener inserts exactly one entry on the vacant path"
471 );
472 Ok(())
473 }
474
475 fn add_https_listener(&mut self, listener: &HttpsListenerConfig) -> Result<(), StateError> {
476 let address: SocketAddr = listener.address.into();
477 let before = self.https_listeners.len();
478 match self.https_listeners.entry(address) {
479 BTreeMapEntry::Vacant(vacant_entry) => vacant_entry.insert(listener.clone()),
480 BTreeMapEntry::Occupied(_) => {
481 debug_assert_eq!(
482 self.https_listeners.len(),
483 before,
484 "a rejected duplicate add_https_listener must not mutate the map"
485 );
486 return Err(StateError::Exists {
487 kind: ObjectKind::HttpsListener,
488 id: address.to_string(),
489 });
490 }
491 };
492 debug_assert!(
493 self.https_listeners.contains_key(&address),
494 "add_https_listener must insert the listener under its address"
495 );
496 debug_assert_eq!(
497 self.https_listeners.len(),
498 before + 1,
499 "add_https_listener inserts exactly one entry on the vacant path"
500 );
501 Ok(())
502 }
503
504 fn add_tcp_listener(&mut self, listener: &TcpListenerConfig) -> Result<(), StateError> {
505 let address: SocketAddr = listener.address.into();
506 let before = self.tcp_listeners.len();
507 match self.tcp_listeners.entry(address) {
508 BTreeMapEntry::Vacant(vacant_entry) => vacant_entry.insert(*listener),
509 BTreeMapEntry::Occupied(_) => {
510 debug_assert_eq!(
511 self.tcp_listeners.len(),
512 before,
513 "a rejected duplicate add_tcp_listener must not mutate the map"
514 );
515 return Err(StateError::Exists {
516 kind: ObjectKind::TcpListener,
517 id: address.to_string(),
518 });
519 }
520 };
521 debug_assert!(
522 self.tcp_listeners.contains_key(&address),
523 "add_tcp_listener must insert the listener under its address"
524 );
525 debug_assert_eq!(
526 self.tcp_listeners.len(),
527 before + 1,
528 "add_tcp_listener inserts exactly one entry on the vacant path"
529 );
530 Ok(())
531 }
532
533 fn add_udp_listener(&mut self, listener: &UdpListenerConfig) -> Result<(), StateError> {
534 let address: SocketAddr = listener.address.into();
535 match self.udp_listeners.entry(address) {
536 BTreeMapEntry::Vacant(vacant_entry) => vacant_entry.insert(*listener),
537 BTreeMapEntry::Occupied(_) => {
538 return Err(StateError::Exists {
539 kind: ObjectKind::UdpListener,
540 id: address.to_string(),
541 });
542 }
543 };
544 Ok(())
545 }
546
547 fn remove_listener(&mut self, remove: &RemoveListener) -> Result<(), StateError> {
548 match ListenerType::try_from(remove.proxy).map_err(StateError::WrongFieldValue)? {
549 ListenerType::Http => self.remove_http_listener(&remove.address.into()),
550 ListenerType::Https => self.remove_https_listener(&remove.address.into()),
551 ListenerType::Tcp => self.remove_tcp_listener(&remove.address.into()),
552 ListenerType::Udp => self.remove_udp_listener(&remove.address.into()),
553 }
554 }
555
556 fn remove_http_listener(&mut self, address: &SocketAddr) -> Result<(), StateError> {
557 let before = self.http_listeners.len();
558 if self.http_listeners.remove(address).is_none() {
559 debug_assert_eq!(
560 self.http_listeners.len(),
561 before,
562 "a failed remove_http_listener must not mutate the map"
563 );
564 return Err(StateError::NoChange);
565 }
566 debug_assert!(
567 !self.http_listeners.contains_key(address),
568 "remove_http_listener must evict the address"
569 );
570 debug_assert_eq!(
571 self.http_listeners.len(),
572 before - 1,
573 "remove_http_listener drops exactly one entry"
574 );
575 Ok(())
576 }
577
578 fn remove_https_listener(&mut self, address: &SocketAddr) -> Result<(), StateError> {
579 let before = self.https_listeners.len();
580 if self.https_listeners.remove(address).is_none() {
581 debug_assert_eq!(
582 self.https_listeners.len(),
583 before,
584 "a failed remove_https_listener must not mutate the map"
585 );
586 return Err(StateError::NoChange);
587 }
588 debug_assert!(
589 !self.https_listeners.contains_key(address),
590 "remove_https_listener must evict the address"
591 );
592 debug_assert_eq!(
593 self.https_listeners.len(),
594 before - 1,
595 "remove_https_listener drops exactly one entry"
596 );
597 Ok(())
598 }
599
600 fn remove_tcp_listener(&mut self, address: &SocketAddr) -> Result<(), StateError> {
601 let before = self.tcp_listeners.len();
602 if self.tcp_listeners.remove(address).is_none() {
603 debug_assert_eq!(
604 self.tcp_listeners.len(),
605 before,
606 "a failed remove_tcp_listener must not mutate the map"
607 );
608 return Err(StateError::NoChange);
609 }
610 debug_assert!(
611 !self.tcp_listeners.contains_key(address),
612 "remove_tcp_listener must evict the address"
613 );
614 debug_assert_eq!(
615 self.tcp_listeners.len(),
616 before - 1,
617 "remove_tcp_listener drops exactly one entry"
618 );
619 Ok(())
620 }
621
622 fn remove_udp_listener(&mut self, address: &SocketAddr) -> Result<(), StateError> {
623 if self.udp_listeners.remove(address).is_none() {
624 return Err(StateError::NoChange);
625 }
626 Ok(())
627 }
628
629 fn update_http_listener(&mut self, patch: &UpdateHttpListenerConfig) -> Result<(), StateError> {
636 validate_h2_flood_knobs_http(patch)?;
637
638 let address: SocketAddr = patch.address.into();
639 let listener =
640 self.http_listeners
641 .get_mut(&address)
642 .ok_or_else(|| StateError::NotFound {
643 kind: ObjectKind::HttpListener,
644 id: address.to_string(),
645 })?;
646
647 if let Some(v) = patch.public_address {
649 listener.public_address = Some(v);
650 }
651 if let Some(v) = patch.expect_proxy {
652 listener.expect_proxy = v;
653 }
654 if let Some(ref v) = patch.sticky_name {
655 listener.sticky_name = v.to_owned();
656 }
657 if let Some(v) = patch.front_timeout {
658 listener.front_timeout = v;
659 }
660 if let Some(v) = patch.back_timeout {
661 listener.back_timeout = v;
662 }
663 if let Some(v) = patch.connect_timeout {
664 listener.connect_timeout = v;
665 }
666 if let Some(v) = patch.request_timeout {
667 listener.request_timeout = v;
668 }
669 if let Some(patch_answers) = patch.http_answers.as_ref() {
670 merge_custom_http_answers(&mut listener.http_answers, patch_answers);
671 }
672 if let Some(v) = patch.h2_max_rst_stream_per_window {
674 listener.h2_max_rst_stream_per_window = Some(v);
675 }
676 if let Some(v) = patch.h2_max_ping_per_window {
677 listener.h2_max_ping_per_window = Some(v);
678 }
679 if let Some(v) = patch.h2_max_settings_per_window {
680 listener.h2_max_settings_per_window = Some(v);
681 }
682 if let Some(v) = patch.h2_max_empty_data_per_window {
683 listener.h2_max_empty_data_per_window = Some(v);
684 }
685 if let Some(v) = patch.h2_max_continuation_frames {
686 listener.h2_max_continuation_frames = Some(v);
687 }
688 if let Some(v) = patch.h2_max_glitch_count {
689 listener.h2_max_glitch_count = Some(v);
690 }
691 if let Some(v) = patch.h2_initial_connection_window {
692 listener.h2_initial_connection_window = Some(v);
693 }
694 if let Some(v) = patch.h2_max_concurrent_streams {
695 listener.h2_max_concurrent_streams = Some(v);
696 }
697 if let Some(v) = patch.h2_stream_shrink_ratio {
698 listener.h2_stream_shrink_ratio = Some(v);
699 }
700 if let Some(v) = patch.h2_max_rst_stream_lifetime {
701 listener.h2_max_rst_stream_lifetime = Some(v);
702 }
703 if let Some(v) = patch.h2_max_rst_stream_abusive_lifetime {
704 listener.h2_max_rst_stream_abusive_lifetime = Some(v);
705 }
706 if let Some(v) = patch.h2_max_rst_stream_emitted_lifetime {
707 listener.h2_max_rst_stream_emitted_lifetime = Some(v);
708 }
709 if let Some(v) = patch.h2_max_header_list_size {
710 listener.h2_max_header_list_size = Some(v);
711 }
712 if let Some(v) = patch.h2_max_header_table_size {
713 listener.h2_max_header_table_size = Some(v);
714 }
715 if let Some(v) = patch.h2_max_header_fields {
716 listener.h2_max_header_fields = Some(v);
717 }
718 if let Some(v) = patch.h2_stream_idle_timeout_seconds {
719 listener.h2_stream_idle_timeout_seconds = Some(v);
720 }
721 if let Some(v) = patch.h2_graceful_shutdown_deadline_seconds {
723 listener.h2_graceful_shutdown_deadline_seconds = Some(v);
724 }
725 if let Some(v) = patch.h2_max_window_update_stream0_per_window {
726 listener.h2_max_window_update_stream0_per_window = Some(v);
727 }
728 if let Some(ref v) = patch.sozu_id_header {
729 validate_sozu_id_header(v)?;
730 listener.sozu_id_header = Some(v.to_owned());
731 }
732 Ok(())
733 }
734
735 fn update_https_listener(
742 &mut self,
743 patch: &UpdateHttpsListenerConfig,
744 ) -> Result<(), StateError> {
745 validate_h2_flood_knobs_https(patch)?;
746
747 let address: SocketAddr = patch.address.into();
748 let listener =
749 self.https_listeners
750 .get_mut(&address)
751 .ok_or_else(|| StateError::NotFound {
752 kind: ObjectKind::HttpsListener,
753 id: address.to_string(),
754 })?;
755
756 if let Some(v) = patch.public_address {
758 listener.public_address = Some(v);
759 }
760 if let Some(v) = patch.expect_proxy {
761 listener.expect_proxy = v;
762 }
763 if let Some(ref v) = patch.sticky_name {
764 listener.sticky_name = v.to_owned();
765 }
766 if let Some(v) = patch.front_timeout {
767 listener.front_timeout = v;
768 }
769 if let Some(v) = patch.back_timeout {
770 listener.back_timeout = v;
771 }
772 if let Some(v) = patch.connect_timeout {
773 listener.connect_timeout = v;
774 }
775 if let Some(v) = patch.request_timeout {
776 listener.request_timeout = v;
777 }
778 if let Some(patch_answers) = patch.http_answers.as_ref() {
779 merge_custom_http_answers(&mut listener.http_answers, patch_answers);
780 }
781 if let Some(ref alpn_wrapper) = patch.alpn_protocols {
783 validate_alpn_protocols(&alpn_wrapper.values)?;
784 listener.alpn_protocols = alpn_wrapper.values.clone();
786 }
787 if let Some(v) = patch.strict_sni_binding {
788 listener.strict_sni_binding = Some(v);
789 }
790 if let Some(v) = patch.disable_http11 {
791 listener.disable_http11 = Some(v);
792 }
793 if let Some(v) = patch.h2_max_rst_stream_per_window {
795 listener.h2_max_rst_stream_per_window = Some(v);
796 }
797 if let Some(v) = patch.h2_max_ping_per_window {
798 listener.h2_max_ping_per_window = Some(v);
799 }
800 if let Some(v) = patch.h2_max_settings_per_window {
801 listener.h2_max_settings_per_window = Some(v);
802 }
803 if let Some(v) = patch.h2_max_empty_data_per_window {
804 listener.h2_max_empty_data_per_window = Some(v);
805 }
806 if let Some(v) = patch.h2_max_continuation_frames {
807 listener.h2_max_continuation_frames = Some(v);
808 }
809 if let Some(v) = patch.h2_max_glitch_count {
810 listener.h2_max_glitch_count = Some(v);
811 }
812 if let Some(v) = patch.h2_initial_connection_window {
813 listener.h2_initial_connection_window = Some(v);
814 }
815 if let Some(v) = patch.h2_max_concurrent_streams {
816 listener.h2_max_concurrent_streams = Some(v);
817 }
818 if let Some(v) = patch.h2_stream_shrink_ratio {
819 listener.h2_stream_shrink_ratio = Some(v);
820 }
821 if let Some(v) = patch.h2_max_rst_stream_lifetime {
822 listener.h2_max_rst_stream_lifetime = Some(v);
823 }
824 if let Some(v) = patch.h2_max_rst_stream_abusive_lifetime {
825 listener.h2_max_rst_stream_abusive_lifetime = Some(v);
826 }
827 if let Some(v) = patch.h2_max_rst_stream_emitted_lifetime {
828 listener.h2_max_rst_stream_emitted_lifetime = Some(v);
829 }
830 if let Some(v) = patch.h2_max_header_list_size {
831 listener.h2_max_header_list_size = Some(v);
832 }
833 if let Some(v) = patch.h2_max_header_table_size {
834 listener.h2_max_header_table_size = Some(v);
835 }
836 if let Some(v) = patch.h2_max_header_fields {
837 listener.h2_max_header_fields = Some(v);
838 }
839 if let Some(v) = patch.h2_stream_idle_timeout_seconds {
840 listener.h2_stream_idle_timeout_seconds = Some(v);
841 }
842 if let Some(v) = patch.h2_graceful_shutdown_deadline_seconds {
844 listener.h2_graceful_shutdown_deadline_seconds = Some(v);
845 }
846 if let Some(v) = patch.h2_max_window_update_stream0_per_window {
847 listener.h2_max_window_update_stream0_per_window = Some(v);
848 }
849 if let Some(ref v) = patch.sozu_id_header {
850 validate_sozu_id_header(v)?;
851 listener.sozu_id_header = Some(v.to_owned());
852 }
853 Ok(())
854 }
855
856 fn update_tcp_listener(&mut self, patch: &UpdateTcpListenerConfig) -> Result<(), StateError> {
861 let address: SocketAddr = patch.address.into();
862 let listener =
863 self.tcp_listeners
864 .get_mut(&address)
865 .ok_or_else(|| StateError::NotFound {
866 kind: ObjectKind::TcpListener,
867 id: address.to_string(),
868 })?;
869
870 if let Some(v) = patch.public_address {
871 listener.public_address = Some(v);
872 }
873 if let Some(v) = patch.expect_proxy {
874 listener.expect_proxy = v;
875 }
876 if let Some(v) = patch.front_timeout {
877 listener.front_timeout = v;
878 }
879 if let Some(v) = patch.back_timeout {
880 listener.back_timeout = v;
881 }
882 if let Some(v) = patch.connect_timeout {
883 listener.connect_timeout = v;
884 }
885 Ok(())
886 }
887
888 fn update_udp_listener(&mut self, patch: &UpdateUdpListenerConfig) -> Result<(), StateError> {
893 let address: SocketAddr = patch.address.into();
894 let listener =
895 self.udp_listeners
896 .get_mut(&address)
897 .ok_or_else(|| StateError::NotFound {
898 kind: ObjectKind::UdpListener,
899 id: address.to_string(),
900 })?;
901
902 if let Some(v) = patch.public_address {
903 listener.public_address = Some(v);
904 }
905 if let Some(v) = patch.front_timeout {
906 listener.front_timeout = v;
907 }
908 if let Some(v) = patch.back_timeout {
909 listener.back_timeout = v;
910 }
911 if let Some(v) = patch.max_rx_datagram_size {
912 listener.max_rx_datagram_size = v;
913 }
914 if let Some(v) = patch.max_flows {
915 listener.max_flows = v;
916 }
917 Ok(())
918 }
919
920 fn activate_listener(&mut self, activate: &ActivateListener) -> Result<(), StateError> {
921 match ListenerType::try_from(activate.proxy).map_err(StateError::WrongFieldValue)? {
922 ListenerType::Http => self
923 .http_listeners
924 .get_mut(&activate.address.into())
925 .map(|listener| listener.active = true)
926 .ok_or(StateError::NotFound {
927 kind: ObjectKind::HttpListener,
928 id: activate.address.to_string(),
929 }),
930 ListenerType::Https => self
931 .https_listeners
932 .get_mut(&activate.address.into())
933 .map(|listener| listener.active = true)
934 .ok_or(StateError::NotFound {
935 kind: ObjectKind::HttpsListener,
936 id: activate.address.to_string(),
937 }),
938 ListenerType::Tcp => self
939 .tcp_listeners
940 .get_mut(&activate.address.into())
941 .map(|listener| listener.active = true)
942 .ok_or(StateError::NotFound {
943 kind: ObjectKind::TcpListener,
944 id: activate.address.to_string(),
945 }),
946 ListenerType::Udp => self
947 .udp_listeners
948 .get_mut(&activate.address.into())
949 .map(|listener| listener.active = true)
950 .ok_or(StateError::NotFound {
951 kind: ObjectKind::UdpListener,
952 id: activate.address.to_string(),
953 }),
954 }
955 }
956
957 fn deactivate_listener(&mut self, deactivate: &DeactivateListener) -> Result<(), StateError> {
958 match ListenerType::try_from(deactivate.proxy).map_err(StateError::WrongFieldValue)? {
959 ListenerType::Http => self
960 .http_listeners
961 .get_mut(&deactivate.address.into())
962 .map(|listener| listener.active = false)
963 .ok_or(StateError::NotFound {
964 kind: ObjectKind::HttpListener,
965 id: deactivate.address.to_string(),
966 }),
967 ListenerType::Https => self
968 .https_listeners
969 .get_mut(&deactivate.address.into())
970 .map(|listener| listener.active = false)
971 .ok_or(StateError::NotFound {
972 kind: ObjectKind::HttpsListener,
973 id: deactivate.address.to_string(),
974 }),
975 ListenerType::Tcp => self
976 .tcp_listeners
977 .get_mut(&deactivate.address.into())
978 .map(|listener| listener.active = false)
979 .ok_or(StateError::NotFound {
980 kind: ObjectKind::TcpListener,
981 id: deactivate.address.to_string(),
982 }),
983 ListenerType::Udp => self
984 .udp_listeners
985 .get_mut(&deactivate.address.into())
986 .map(|listener| listener.active = false)
987 .ok_or(StateError::NotFound {
988 kind: ObjectKind::UdpListener,
989 id: deactivate.address.to_string(),
990 }),
991 }
992 }
993
994 fn add_http_frontend(&mut self, front: &RequestHttpFrontend) -> Result<(), StateError> {
995 let front_as_key = front.to_string();
996 let before = self.http_fronts.len();
997
998 match self.http_fronts.entry(front.to_string()) {
999 BTreeMapEntry::Vacant(e) => {
1000 e.insert(front.clone().to_frontend().map_err(|into_error| {
1001 StateError::FrontendConversion {
1002 frontend: front_as_key,
1003 error: into_error.to_string(),
1004 }
1005 })?)
1006 }
1007 BTreeMapEntry::Occupied(_) => {
1008 debug_assert_eq!(
1009 self.http_fronts.len(),
1010 before,
1011 "a rejected duplicate add_http_frontend must not mutate the map"
1012 );
1013 return Err(StateError::Exists {
1014 kind: ObjectKind::HttpFrontend,
1015 id: front.to_string(),
1016 });
1017 }
1018 };
1019 debug_assert!(
1022 self.http_fronts.contains_key(&front.to_string()),
1023 "add_http_frontend must insert the route key on success"
1024 );
1025 debug_assert_eq!(
1026 self.http_fronts.len(),
1027 before + 1,
1028 "add_http_frontend inserts exactly one entry on success"
1029 );
1030 Ok(())
1031 }
1032
1033 fn add_https_frontend(&mut self, front: &RequestHttpFrontend) -> Result<(), StateError> {
1034 let front_as_key = front.to_string();
1035 let before = self.https_fronts.len();
1036
1037 match self.https_fronts.entry(front.to_string()) {
1038 BTreeMapEntry::Vacant(e) => {
1039 e.insert(front.clone().to_frontend().map_err(|into_error| {
1040 StateError::FrontendConversion {
1041 frontend: front_as_key,
1042 error: into_error.to_string(),
1043 }
1044 })?)
1045 }
1046 BTreeMapEntry::Occupied(_) => {
1047 debug_assert_eq!(
1048 self.https_fronts.len(),
1049 before,
1050 "a rejected duplicate add_https_frontend must not mutate the map"
1051 );
1052 return Err(StateError::Exists {
1053 kind: ObjectKind::HttpsFrontend,
1054 id: front.to_string(),
1055 });
1056 }
1057 };
1058 debug_assert!(
1059 self.https_fronts.contains_key(&front.to_string()),
1060 "add_https_frontend must insert the route key on success"
1061 );
1062 debug_assert_eq!(
1063 self.https_fronts.len(),
1064 before + 1,
1065 "add_https_frontend inserts exactly one entry on success"
1066 );
1067 Ok(())
1068 }
1069
1070 fn remove_http_frontend(&mut self, front: &RequestHttpFrontend) -> Result<(), StateError> {
1071 let key = front.to_string();
1072 let before = self.http_fronts.len();
1073 self.http_fronts.remove(&key).ok_or(StateError::NotFound {
1074 kind: ObjectKind::HttpFrontend,
1075 id: front.to_string(),
1076 })?;
1077 debug_assert!(
1078 !self.http_fronts.contains_key(&key),
1079 "remove_http_frontend must evict the route key"
1080 );
1081 debug_assert_eq!(
1082 self.http_fronts.len(),
1083 before - 1,
1084 "remove_http_frontend drops exactly one entry"
1085 );
1086 Ok(())
1087 }
1088
1089 fn remove_https_frontend(&mut self, front: &RequestHttpFrontend) -> Result<(), StateError> {
1090 let key = front.to_string();
1091 let before = self.https_fronts.len();
1092 self.https_fronts.remove(&key).ok_or(StateError::NotFound {
1093 kind: ObjectKind::HttpsFrontend,
1094 id: front.to_string(),
1095 })?;
1096 debug_assert!(
1097 !self.https_fronts.contains_key(&key),
1098 "remove_https_frontend must evict the route key"
1099 );
1100 debug_assert_eq!(
1101 self.https_fronts.len(),
1102 before - 1,
1103 "remove_https_frontend drops exactly one entry"
1104 );
1105 Ok(())
1106 }
1107
1108 fn add_certificate(&mut self, add: &AddCertificate) -> Result<(), StateError> {
1109 let fingerprint = add
1110 .certificate
1111 .fingerprint()
1112 .map_err(StateError::AddCertificate)?;
1113
1114 let entry = self.certificates.entry(add.address.into()).or_default();
1115
1116 let mut add = add.clone();
1117 add.certificate
1118 .apply_overriding_names()
1119 .map_err(StateError::AddCertificate)?;
1120
1121 if entry.contains_key(&fingerprint) {
1122 info!(
1123 "Skip loading of certificate '{}' for domain '{}' on listener '{}', the certificate is already present.",
1124 fingerprint,
1125 add.certificate.names.join(", "),
1126 add.address
1127 );
1128 return Ok(());
1129 }
1130
1131 let before = entry.len();
1132 entry.insert(fingerprint.clone(), add.certificate);
1133 debug_assert!(
1134 entry.contains_key(&fingerprint),
1135 "add_certificate must insert the fingerprint under its address"
1136 );
1137 debug_assert_eq!(
1138 entry.len(),
1139 before + 1,
1140 "add_certificate inserts exactly one fingerprint on the new path"
1141 );
1142 Ok(())
1143 }
1144
1145 fn remove_certificate(&mut self, remove: &RemoveCertificate) -> Result<(), StateError> {
1146 let fingerprint = Fingerprint(
1147 hex::decode(&remove.fingerprint)
1148 .map_err(|decode_error| StateError::RemoveCertificate(decode_error.to_string()))?,
1149 );
1150
1151 if let Some(index) = self.certificates.get_mut(&remove.address.into()) {
1152 index.remove(&fingerprint);
1153 debug_assert!(
1154 !index.contains_key(&fingerprint),
1155 "remove_certificate must evict the fingerprint when the address is known"
1156 );
1157 }
1158
1159 Ok(())
1160 }
1161
1162 fn replace_certificate(&mut self, replace: &ReplaceCertificate) -> Result<(), StateError> {
1167 let replace_address = replace.address.into();
1168 let old_fingerprint = Fingerprint(
1169 hex::decode(&replace.old_fingerprint)
1170 .map_err(|decode_error| StateError::RemoveCertificate(decode_error.to_string()))?,
1171 );
1172
1173 self.certificates
1174 .get_mut(&replace_address)
1175 .ok_or(StateError::NotFound {
1176 kind: ObjectKind::Certificate,
1177 id: replace.address.to_string(),
1178 })?
1179 .remove(&old_fingerprint);
1180
1181 let new_fingerprint = Fingerprint(
1182 calculate_fingerprint(replace.new_certificate.certificate.as_bytes()).map_err(
1183 |fingerprint_err| StateError::ReplaceCertificate(fingerprint_err.to_string()),
1184 )?,
1185 );
1186
1187 self.certificates
1188 .get_mut(&replace_address)
1189 .map(|certs| certs.insert(new_fingerprint.clone(), replace.new_certificate.clone()));
1190
1191 if !self
1192 .certificates
1193 .get(&replace_address)
1194 .ok_or(StateError::ReplaceCertificate(
1195 "Unlikely error. This entry in the certificate hashmap should be present"
1196 .to_string(),
1197 ))?
1198 .contains_key(&new_fingerprint)
1199 {
1200 return Err(StateError::ReplaceCertificate(format!(
1201 "Failed to insert the new certificate for address {}",
1202 replace.address
1203 )));
1204 }
1205 debug_assert!(
1208 self.certificates
1209 .get(&replace_address)
1210 .is_some_and(|certs| certs.contains_key(&new_fingerprint)),
1211 "replace_certificate must leave the new fingerprint present"
1212 );
1213 debug_assert!(
1214 new_fingerprint == old_fingerprint
1215 || self
1216 .certificates
1217 .get(&replace_address)
1218 .is_none_or(|certs| !certs.contains_key(&old_fingerprint)),
1219 "replace_certificate must evict the old fingerprint unless it equals the new one"
1220 );
1221 Ok(())
1222 }
1223
1224 fn add_tcp_frontend(&mut self, front: &RequestTcpFrontend) -> Result<(), StateError> {
1232 let address: SocketAddr = front.address.into();
1233
1234 let normalized_sni = match &front.sni {
1242 Some(sni) => Some(validate_sni_pattern(sni).map_err(|config_error| {
1243 StateError::InvalidTcpFrontend {
1244 address,
1245 reason: format!("sni {sni:?} failed SNI shape validation: {config_error}"),
1246 }
1247 })?),
1248 None => None,
1249 };
1250 let alpn = canonical_tcp_alpn(&front.alpn);
1251
1252 if normalized_sni.is_none() && !alpn.is_empty() {
1258 return Err(StateError::InvalidTcpFrontend {
1259 address,
1260 reason: format!(
1261 "alpn = {alpn:?} set without sni: alpn only matches within an SNI-scoped \
1262 preread, so a frontend without sni would silently ignore its alpn list"
1263 ),
1264 });
1265 }
1266
1267 let total_before = self.count_tcp_frontends_raw();
1268
1269 let mut listener_has_no_sni_front = false;
1275 let mut listener_has_sni_front = false;
1276 for existing in self
1277 .tcp_fronts
1278 .values()
1279 .flatten()
1280 .filter(|existing| existing.address == address)
1281 {
1282 if tcp_frontend_matches(existing, address, &normalized_sni, &alpn) {
1283 return Err(StateError::Exists {
1284 kind: ObjectKind::TcpFrontend,
1285 id: format!(
1286 "TcpFrontend {{ address: {address}, sni: {normalized_sni:?}, \
1287 alpn: {alpn:?} }}"
1288 ),
1289 });
1290 }
1291 match &existing.sni {
1292 None => listener_has_no_sni_front = true,
1293 Some(existing_sni) => {
1294 listener_has_sni_front = true;
1295 if normalized_sni.as_deref() != Some(existing_sni.as_str()) {
1302 continue;
1303 }
1304 let existing_is_catch_all = existing.alpn.is_empty();
1305 let new_is_catch_all = alpn.is_empty();
1306 if existing_is_catch_all && new_is_catch_all {
1307 return Err(StateError::InvalidTcpFrontend {
1308 address,
1309 reason: format!(
1310 "sni {existing_sni:?} already has a catch-all (empty alpn) \
1311 frontend: at most one frontend per (address, sni) may omit \
1312 alpn"
1313 ),
1314 });
1315 }
1316 if !existing_is_catch_all
1321 && !new_is_catch_all
1322 && let Some(overlap) = alpn
1323 .iter()
1324 .find(|protocol| existing.alpn.contains(protocol))
1325 {
1326 return Err(StateError::InvalidTcpFrontend {
1327 address,
1328 reason: format!(
1329 "sni {existing_sni:?} already has a frontend matching ALPN \
1330 protocol {overlap:?}: ALPN matchers for the same (address, \
1331 sni) must not overlap"
1332 ),
1333 });
1334 }
1335 }
1336 }
1337 }
1338 match &normalized_sni {
1339 None if listener_has_sni_front => {
1340 return Err(StateError::InvalidTcpFrontend {
1341 address,
1342 reason: "a no-SNI frontend cannot be added to a listener that already has \
1343 SNI-scoped routes"
1344 .to_string(),
1345 });
1346 }
1347 Some(_) if listener_has_no_sni_front => {
1348 return Err(StateError::InvalidTcpFrontend {
1349 address,
1350 reason: "an SNI-scoped frontend cannot be added to a listener that already \
1351 has a no-SNI catch-all cluster"
1352 .to_string(),
1353 });
1354 }
1355 _ => {}
1356 }
1357 debug_assert_eq!(
1361 self.count_tcp_frontends_raw(),
1362 total_before,
1363 "add_tcp_frontend must not mutate tcp_fronts before every admission check has \
1364 passed"
1365 );
1366
1367 let tcp_frontend = TcpFrontend {
1368 cluster_id: front.cluster_id.clone(),
1369 address,
1370 tags: front.tags.clone(),
1371 sni: normalized_sni,
1372 alpn,
1373 };
1374 let tcp_frontends = self.tcp_fronts.entry(front.cluster_id.clone()).or_default();
1375 let before = tcp_frontends.len();
1376 debug_assert_eq!(
1377 tcp_frontend.cluster_id, front.cluster_id,
1378 "the built frontend must carry its bucket's cluster_id"
1379 );
1380 tcp_frontends.push(tcp_frontend);
1381 debug_assert_eq!(
1382 tcp_frontends.len(),
1383 before + 1,
1384 "add_tcp_frontend appends exactly one entry"
1385 );
1386 Ok(())
1387 }
1388
1389 fn remove_tcp_frontend(
1390 &mut self,
1391 front_to_remove: &RequestTcpFrontend,
1392 ) -> Result<(), StateError> {
1393 let tcp_frontends =
1394 self.tcp_fronts
1395 .get_mut(&front_to_remove.cluster_id)
1396 .ok_or(StateError::NotFound {
1397 kind: ObjectKind::TcpFrontend,
1398 id: format!("{front_to_remove:?}"),
1399 })?;
1400
1401 let len = tcp_frontends.len();
1402 let remove_address: SocketAddr = front_to_remove.address.into();
1403 let remove_sni = front_to_remove.sni.as_deref().map(str::to_ascii_lowercase);
1413 let remove_alpn = canonical_tcp_alpn(&front_to_remove.alpn);
1414 tcp_frontends.retain(|front| {
1419 !tcp_frontend_matches(front, remove_address, &remove_sni, &remove_alpn)
1420 });
1421 let after = tcp_frontends.len();
1422 if after == len {
1423 return Err(StateError::NoChange);
1424 }
1425 debug_assert_eq!(
1430 after,
1431 len - 1,
1432 "remove_tcp_frontend drops exactly one entry"
1433 );
1434 debug_assert!(
1435 !tcp_frontends.iter().any(|f| tcp_frontend_matches(
1436 f,
1437 remove_address,
1438 &remove_sni,
1439 &remove_alpn
1440 )),
1441 "remove_tcp_frontend must leave no frontend matching the removed (address, sni, alpn)"
1442 );
1443 Ok(())
1444 }
1445
1446 fn add_udp_frontend(&mut self, front: &RequestUdpFrontend) -> Result<(), StateError> {
1447 let udp_frontends = self.udp_fronts.entry(front.cluster_id.clone()).or_default();
1448
1449 let udp_frontend = UdpFrontend {
1450 cluster_id: front.cluster_id.clone(),
1451 address: front.address.into(),
1452 tags: front.tags.clone(),
1453 };
1454 if udp_frontends.contains(&udp_frontend) {
1455 return Err(StateError::Exists {
1456 kind: ObjectKind::UdpFrontend,
1457 id: format!("{udp_frontend:?}"),
1458 });
1459 }
1460
1461 udp_frontends.push(udp_frontend);
1462 Ok(())
1463 }
1464
1465 fn remove_udp_frontend(
1466 &mut self,
1467 front_to_remove: &RequestUdpFrontend,
1468 ) -> Result<(), StateError> {
1469 let udp_frontends =
1470 self.udp_fronts
1471 .get_mut(&front_to_remove.cluster_id)
1472 .ok_or(StateError::NotFound {
1473 kind: ObjectKind::UdpFrontend,
1474 id: format!("{front_to_remove:?}"),
1475 })?;
1476
1477 let len = udp_frontends.len();
1478 udp_frontends.retain(|front| front.address != front_to_remove.address.into());
1479 if udp_frontends.len() == len {
1480 return Err(StateError::NoChange);
1481 }
1482 Ok(())
1483 }
1484
1485 fn add_backend(&mut self, add_backend: &AddBackend) -> Result<(), StateError> {
1486 let backend = Backend {
1487 address: add_backend.address.into(),
1488 cluster_id: add_backend.cluster_id.clone(),
1489 backend_id: add_backend.backend_id.clone(),
1490 sticky_id: add_backend.sticky_id.clone(),
1491 load_balancing_parameters: add_backend.load_balancing_parameters,
1492 backup: add_backend.backup,
1493 };
1494 let backends = self.backends.entry(backend.cluster_id.clone()).or_default();
1495 let backend_id = backend.backend_id.clone();
1496 let backend_address = backend.address;
1497 let before = backends.len();
1498
1499 let was_present = backends
1504 .iter()
1505 .any(|b| b.backend_id == backend_id && b.address == backend_address);
1506 backends.retain(|b| b.backend_id != backend.backend_id || b.address != backend.address);
1507 debug_assert_eq!(
1508 backends.len(),
1509 before - was_present as usize,
1510 "the upsert retain must drop exactly the prior copy iff it existed"
1511 );
1512 backends.push(backend);
1513 backends.sort();
1514
1515 debug_assert_eq!(
1516 backends.len(),
1517 before + (!was_present) as usize,
1518 "add_backend grows the bucket by one iff the backend was new"
1519 );
1520 debug_assert_eq!(
1521 backends
1522 .iter()
1523 .filter(|b| b.backend_id == backend_id && b.address == backend_address)
1524 .count(),
1525 1,
1526 "exactly one copy of the upserted backend must remain"
1527 );
1528 Ok(())
1529 }
1530
1531 fn remove_backend(&mut self, backend: &RemoveBackend) -> Result<(), StateError> {
1532 let backend_list =
1533 self.backends
1534 .get_mut(&backend.cluster_id)
1535 .ok_or(StateError::NotFound {
1536 kind: ObjectKind::Backend,
1537 id: backend.backend_id.to_owned(),
1538 })?;
1539
1540 let len = backend_list.len();
1541 let remove_address: SocketAddr = backend.address.into();
1542 backend_list.retain(|b| b.backend_id != backend.backend_id || b.address != remove_address);
1543 backend_list.sort();
1544 let after = backend_list.len();
1545 if after == len {
1546 return Err(StateError::NoChange);
1547 }
1548 debug_assert_eq!(after, len - 1, "remove_backend drops exactly one entry");
1551 debug_assert!(
1552 !backend_list
1553 .iter()
1554 .any(|b| b.backend_id == backend.backend_id && b.address == remove_address),
1555 "remove_backend must leave no backend matching (backend_id, address)"
1556 );
1557 Ok(())
1558 }
1559
1560 fn generate_requests(&self) -> Vec<Request> {
1562 let mut v: Vec<Request> = Vec::new();
1563
1564 for listener in self.http_listeners.values() {
1565 v.push(RequestType::AddHttpListener(listener.clone()).into());
1566 if listener.active {
1567 v.push(
1568 RequestType::ActivateListener(ActivateListener {
1569 address: listener.address,
1570 proxy: ListenerType::Http.into(),
1571 from_scm: false,
1572 })
1573 .into(),
1574 );
1575 }
1576 }
1577
1578 for listener in self.https_listeners.values() {
1579 v.push(RequestType::AddHttpsListener(listener.clone()).into());
1580 if listener.active {
1581 v.push(
1582 RequestType::ActivateListener(ActivateListener {
1583 address: listener.address,
1584 proxy: ListenerType::Https.into(),
1585 from_scm: false,
1586 })
1587 .into(),
1588 );
1589 }
1590 }
1591
1592 for listener in self.tcp_listeners.values() {
1593 v.push(RequestType::AddTcpListener(*listener).into());
1594 if listener.active {
1595 v.push(
1596 RequestType::ActivateListener(ActivateListener {
1597 address: listener.address,
1598 proxy: ListenerType::Tcp.into(),
1599 from_scm: false,
1600 })
1601 .into(),
1602 );
1603 }
1604 }
1605
1606 for listener in self.udp_listeners.values() {
1607 v.push(RequestType::AddUdpListener(*listener).into());
1608 if listener.active {
1609 v.push(
1610 RequestType::ActivateListener(ActivateListener {
1611 address: listener.address,
1612 proxy: ListenerType::Udp.into(),
1613 from_scm: false,
1614 })
1615 .into(),
1616 );
1617 }
1618 }
1619
1620 for cluster in self.clusters.values() {
1621 v.push(RequestType::AddCluster(cluster.clone()).into());
1622 }
1623
1624 for front in self.http_fronts.values() {
1625 v.push(RequestType::AddHttpFrontend(front.clone().into()).into());
1626 }
1627
1628 for (front, certs) in self.certificates.iter() {
1629 for certificate_and_key in certs.values() {
1630 v.push(
1631 RequestType::AddCertificate(AddCertificate {
1632 address: SocketAddress::from(*front),
1633 certificate: certificate_and_key.clone(),
1634 expired_at: None,
1635 })
1636 .into(),
1637 );
1638 }
1639 }
1640
1641 for front in self.https_fronts.values() {
1642 v.push(RequestType::AddHttpsFrontend(front.clone().into()).into());
1643 }
1644
1645 for front_list in self.tcp_fronts.values() {
1646 for front in front_list {
1647 v.push(RequestType::AddTcpFrontend(front.clone().into()).into());
1648 }
1649 }
1650
1651 for front_list in self.udp_fronts.values() {
1652 for front in front_list {
1653 v.push(RequestType::AddUdpFrontend(front.clone().into()).into());
1654 }
1655 }
1656
1657 for backend_list in self.backends.values() {
1658 for backend in backend_list {
1659 v.push(RequestType::AddBackend(backend.clone().to_add_backend()).into());
1660 }
1661 }
1662
1663 #[cfg(debug_assertions)]
1669 {
1670 let mut replayed = ConfigState::new();
1671 for request in &v {
1672 debug_assert!(
1673 replayed.dispatch(request).is_ok(),
1674 "every request from generate_requests must replay cleanly"
1675 );
1676 }
1677 debug_assert!(
1678 replayed.clusters == self.clusters
1679 && replayed.backends == self.backends
1680 && replayed.http_listeners == self.http_listeners
1681 && replayed.https_listeners == self.https_listeners
1682 && replayed.tcp_listeners == self.tcp_listeners
1683 && replayed.http_fronts == self.http_fronts
1684 && replayed.https_fronts == self.https_fronts
1685 && replayed.tcp_fronts == self.tcp_fronts
1686 && replayed.certificates == self.certificates,
1687 "replaying generate_requests into a fresh state must reproduce self"
1688 );
1689 }
1690
1691 v
1692 }
1693
1694 pub fn generate_activate_requests(&self) -> Vec<Request> {
1695 let mut v: Vec<Request> = Vec::new();
1696 for front in self
1697 .http_listeners
1698 .iter()
1699 .filter(|(_, listener)| listener.active)
1700 .map(|(k, _)| k)
1701 {
1702 v.push(
1703 RequestType::ActivateListener(ActivateListener {
1704 address: SocketAddress::from(*front),
1705 proxy: ListenerType::Http.into(),
1706 from_scm: false,
1707 })
1708 .into(),
1709 );
1710 }
1711
1712 for front in self
1713 .https_listeners
1714 .iter()
1715 .filter(|(_, listener)| listener.active)
1716 .map(|(k, _)| k)
1717 {
1718 v.push(
1719 RequestType::ActivateListener(ActivateListener {
1720 address: SocketAddress::from(*front),
1721 proxy: ListenerType::Https.into(),
1722 from_scm: false,
1723 })
1724 .into(),
1725 );
1726 }
1727 for front in self
1728 .tcp_listeners
1729 .iter()
1730 .filter(|(_, listener)| listener.active)
1731 .map(|(k, _)| k)
1732 {
1733 v.push(
1734 RequestType::ActivateListener(ActivateListener {
1735 address: SocketAddress::from(*front),
1736 proxy: ListenerType::Tcp.into(),
1737 from_scm: false,
1738 })
1739 .into(),
1740 );
1741 }
1742 for front in self
1743 .udp_listeners
1744 .iter()
1745 .filter(|(_, listener)| listener.active)
1746 .map(|(k, _)| k)
1747 {
1748 v.push(
1749 RequestType::ActivateListener(ActivateListener {
1750 address: SocketAddress::from(*front),
1751 proxy: ListenerType::Udp.into(),
1752 from_scm: false,
1753 })
1754 .into(),
1755 );
1756 }
1757
1758 #[cfg(debug_assertions)]
1761 {
1762 let active_listeners = self.http_listeners.values().filter(|l| l.active).count()
1763 + self.https_listeners.values().filter(|l| l.active).count()
1764 + self.tcp_listeners.values().filter(|l| l.active).count()
1765 + self.udp_listeners.values().filter(|l| l.active).count();
1766 debug_assert_eq!(
1767 v.len(),
1768 active_listeners,
1769 "generate_activate_requests emits one request per active listener"
1770 );
1771 debug_assert!(
1772 v.iter()
1773 .all(|r| matches!(r.request_type, Some(RequestType::ActivateListener(_)))),
1774 "generate_activate_requests must emit only ActivateListener requests"
1775 );
1776 }
1777
1778 v
1779 }
1780
1781 pub fn diff(&self, other: &ConfigState) -> Vec<Request> {
1782 let my_tcp_listeners: HashSet<&SocketAddr> = self.tcp_listeners.keys().collect();
1784 let their_tcp_listeners: HashSet<&SocketAddr> = other.tcp_listeners.keys().collect();
1785 let removed_tcp_listeners = my_tcp_listeners.difference(&their_tcp_listeners);
1786 let added_tcp_listeners = their_tcp_listeners.difference(&my_tcp_listeners);
1787
1788 let my_udp_listeners: HashSet<&SocketAddr> = self.udp_listeners.keys().collect();
1789 let their_udp_listeners: HashSet<&SocketAddr> = other.udp_listeners.keys().collect();
1790 let removed_udp_listeners = my_udp_listeners.difference(&their_udp_listeners);
1791 let added_udp_listeners = their_udp_listeners.difference(&my_udp_listeners);
1792
1793 let my_http_listeners: HashSet<&SocketAddr> = self.http_listeners.keys().collect();
1794 let their_http_listeners: HashSet<&SocketAddr> = other.http_listeners.keys().collect();
1795 let removed_http_listeners = my_http_listeners.difference(&their_http_listeners);
1796 let added_http_listeners = their_http_listeners.difference(&my_http_listeners);
1797
1798 let my_https_listeners: HashSet<&SocketAddr> = self.https_listeners.keys().collect();
1799 let their_https_listeners: HashSet<&SocketAddr> = other.https_listeners.keys().collect();
1800 let removed_https_listeners = my_https_listeners.difference(&their_https_listeners);
1801 let added_https_listeners = their_https_listeners.difference(&my_https_listeners);
1802
1803 let mut v: Vec<Request> = vec![];
1804
1805 for address in removed_tcp_listeners {
1806 if self.tcp_listeners[*address].active {
1807 v.push(
1808 RequestType::DeactivateListener(DeactivateListener {
1809 address: SocketAddress::from(**address),
1810 proxy: ListenerType::Tcp.into(),
1811 to_scm: false,
1812 })
1813 .into(),
1814 );
1815 }
1816
1817 v.push(
1818 RequestType::RemoveListener(RemoveListener {
1819 address: SocketAddress::from(**address),
1820 proxy: ListenerType::Tcp.into(),
1821 })
1822 .into(),
1823 );
1824 }
1825
1826 for address in added_tcp_listeners.clone() {
1827 v.push(RequestType::AddTcpListener(other.tcp_listeners[*address]).into());
1828
1829 if other.tcp_listeners[*address].active {
1830 v.push(
1831 RequestType::ActivateListener(ActivateListener {
1832 address: SocketAddress::from(**address),
1833 proxy: ListenerType::Tcp.into(),
1834 from_scm: false,
1835 })
1836 .into(),
1837 );
1838 }
1839 }
1840
1841 for address in removed_udp_listeners {
1842 if self.udp_listeners[*address].active {
1843 v.push(
1844 RequestType::DeactivateListener(DeactivateListener {
1845 address: SocketAddress::from(**address),
1846 proxy: ListenerType::Udp.into(),
1847 to_scm: false,
1848 })
1849 .into(),
1850 );
1851 }
1852
1853 v.push(
1854 RequestType::RemoveListener(RemoveListener {
1855 address: SocketAddress::from(**address),
1856 proxy: ListenerType::Udp.into(),
1857 })
1858 .into(),
1859 );
1860 }
1861
1862 for address in added_udp_listeners.clone() {
1863 v.push(RequestType::AddUdpListener(other.udp_listeners[*address]).into());
1864
1865 if other.udp_listeners[*address].active {
1866 v.push(
1867 RequestType::ActivateListener(ActivateListener {
1868 address: SocketAddress::from(**address),
1869 proxy: ListenerType::Udp.into(),
1870 from_scm: false,
1871 })
1872 .into(),
1873 );
1874 }
1875 }
1876
1877 for address in removed_http_listeners {
1878 if self.http_listeners[*address].active {
1879 v.push(
1880 RequestType::DeactivateListener(DeactivateListener {
1881 address: SocketAddress::from(**address),
1882 proxy: ListenerType::Http.into(),
1883 to_scm: false,
1884 })
1885 .into(),
1886 );
1887 }
1888
1889 v.push(
1890 RequestType::RemoveListener(RemoveListener {
1891 address: SocketAddress::from(**address),
1892 proxy: ListenerType::Http.into(),
1893 })
1894 .into(),
1895 );
1896 }
1897
1898 for address in added_http_listeners.clone() {
1899 v.push(RequestType::AddHttpListener(other.http_listeners[*address].clone()).into());
1900
1901 if other.http_listeners[*address].active {
1902 v.push(
1903 RequestType::ActivateListener(ActivateListener {
1904 address: SocketAddress::from(**address),
1905 proxy: ListenerType::Http.into(),
1906 from_scm: false,
1907 })
1908 .into(),
1909 );
1910 }
1911 }
1912
1913 for address in removed_https_listeners {
1914 if self.https_listeners[*address].active {
1915 v.push(
1916 RequestType::DeactivateListener(DeactivateListener {
1917 address: SocketAddress::from(**address),
1918 proxy: ListenerType::Https.into(),
1919 to_scm: false,
1920 })
1921 .into(),
1922 );
1923 }
1924
1925 v.push(
1926 RequestType::RemoveListener(RemoveListener {
1927 address: SocketAddress::from(**address),
1928 proxy: ListenerType::Https.into(),
1929 })
1930 .into(),
1931 );
1932 }
1933
1934 for address in added_https_listeners.clone() {
1935 v.push(RequestType::AddHttpsListener(other.https_listeners[*address].clone()).into());
1936
1937 if other.https_listeners[*address].active {
1938 v.push(
1939 RequestType::ActivateListener(ActivateListener {
1940 address: SocketAddress::from(**address),
1941 proxy: ListenerType::Https.into(),
1942 from_scm: false,
1943 })
1944 .into(),
1945 );
1946 }
1947 }
1948
1949 for addr in my_tcp_listeners.intersection(&their_tcp_listeners) {
1950 let my_listener = &self.tcp_listeners[*addr];
1951 let their_listener = &other.tcp_listeners[*addr];
1952
1953 if my_listener != their_listener {
1954 v.push(
1955 RequestType::RemoveListener(RemoveListener {
1956 address: SocketAddress::from(**addr),
1957 proxy: ListenerType::Tcp.into(),
1958 })
1959 .into(),
1960 );
1961 let mut listener_to_add = *their_listener;
1963 listener_to_add.active = false;
1964 v.push(RequestType::AddTcpListener(listener_to_add).into());
1965
1966 if their_listener.active {
1973 v.push(
1974 RequestType::ActivateListener(ActivateListener {
1975 address: SocketAddress::from(**addr),
1976 proxy: ListenerType::Tcp.into(),
1977 from_scm: false,
1978 })
1979 .into(),
1980 );
1981 }
1982 }
1983
1984 if my_listener.active && !their_listener.active {
1985 v.push(
1986 RequestType::DeactivateListener(DeactivateListener {
1987 address: SocketAddress::from(**addr),
1988 proxy: ListenerType::Tcp.into(),
1989 to_scm: false,
1990 })
1991 .into(),
1992 );
1993 }
1994 }
1995
1996 for addr in my_udp_listeners.intersection(&their_udp_listeners) {
1997 let my_listener = &self.udp_listeners[*addr];
1998 let their_listener = &other.udp_listeners[*addr];
1999
2000 if my_listener != their_listener {
2001 v.push(
2002 RequestType::RemoveListener(RemoveListener {
2003 address: SocketAddress::from(**addr),
2004 proxy: ListenerType::Udp.into(),
2005 })
2006 .into(),
2007 );
2008 let mut listener_to_add = *their_listener;
2010 listener_to_add.active = false;
2011 v.push(RequestType::AddUdpListener(listener_to_add).into());
2012
2013 if their_listener.active {
2020 v.push(
2021 RequestType::ActivateListener(ActivateListener {
2022 address: SocketAddress::from(**addr),
2023 proxy: ListenerType::Udp.into(),
2024 from_scm: false,
2025 })
2026 .into(),
2027 );
2028 }
2029 }
2030
2031 if my_listener.active && !their_listener.active {
2032 v.push(
2033 RequestType::DeactivateListener(DeactivateListener {
2034 address: SocketAddress::from(**addr),
2035 proxy: ListenerType::Udp.into(),
2036 to_scm: false,
2037 })
2038 .into(),
2039 );
2040 }
2041 }
2042
2043 for addr in my_http_listeners.intersection(&their_http_listeners) {
2044 let my_listener = &self.http_listeners[*addr];
2045 let their_listener = &other.http_listeners[*addr];
2046
2047 if my_listener != their_listener {
2048 v.push(
2049 RequestType::RemoveListener(RemoveListener {
2050 address: SocketAddress::from(**addr),
2051 proxy: ListenerType::Http.into(),
2052 })
2053 .into(),
2054 );
2055 let mut listener_to_add = their_listener.clone();
2057 listener_to_add.active = false;
2058 v.push(RequestType::AddHttpListener(listener_to_add).into());
2059
2060 if their_listener.active {
2067 v.push(
2068 RequestType::ActivateListener(ActivateListener {
2069 address: SocketAddress::from(**addr),
2070 proxy: ListenerType::Http.into(),
2071 from_scm: false,
2072 })
2073 .into(),
2074 );
2075 }
2076 }
2077
2078 if my_listener.active && !their_listener.active {
2079 v.push(
2080 RequestType::DeactivateListener(DeactivateListener {
2081 address: SocketAddress::from(**addr),
2082 proxy: ListenerType::Http.into(),
2083 to_scm: false,
2084 })
2085 .into(),
2086 );
2087 }
2088 }
2089
2090 for addr in my_https_listeners.intersection(&their_https_listeners) {
2091 let my_listener = &self.https_listeners[*addr];
2092 let their_listener = &other.https_listeners[*addr];
2093
2094 if my_listener != their_listener {
2095 v.push(
2096 RequestType::RemoveListener(RemoveListener {
2097 address: SocketAddress::from(**addr),
2098 proxy: ListenerType::Https.into(),
2099 })
2100 .into(),
2101 );
2102 let mut listener_to_add = their_listener.clone();
2104 listener_to_add.active = false;
2105 v.push(RequestType::AddHttpsListener(listener_to_add).into());
2106
2107 if their_listener.active {
2114 v.push(
2115 RequestType::ActivateListener(ActivateListener {
2116 address: SocketAddress::from(**addr),
2117 proxy: ListenerType::Https.into(),
2118 from_scm: false,
2119 })
2120 .into(),
2121 );
2122 }
2123 }
2124
2125 if my_listener.active && !their_listener.active {
2126 v.push(
2127 RequestType::DeactivateListener(DeactivateListener {
2128 address: SocketAddress::from(**addr),
2129 proxy: ListenerType::Https.into(),
2130 to_scm: false,
2131 })
2132 .into(),
2133 );
2134 }
2135 }
2136
2137 for (cluster_id, res) in diff_map(self.clusters.iter(), other.clusters.iter()) {
2138 match res {
2139 DiffResult::Added | DiffResult::Changed => v.push(
2140 RequestType::AddCluster(other.clusters.get(cluster_id).unwrap().clone()).into(),
2141 ),
2142 DiffResult::Removed => {
2143 v.push(RequestType::RemoveCluster(cluster_id.to_string()).into())
2144 }
2145 }
2146 }
2147
2148 for ((cluster_id, backend_id), res) in diff_map(
2149 self.backends.iter().flat_map(|(cluster_id, v)| {
2150 v.iter()
2151 .map(move |backend| ((cluster_id, &backend.backend_id), backend))
2152 }),
2153 other.backends.iter().flat_map(|(cluster_id, v)| {
2154 v.iter()
2155 .map(move |backend| ((cluster_id, &backend.backend_id), backend))
2156 }),
2157 ) {
2158 match res {
2159 DiffResult::Added => {
2160 let backend = other
2161 .backends
2162 .get(cluster_id)
2163 .and_then(|v| v.iter().find(|b| &b.backend_id == backend_id))
2164 .unwrap();
2165 v.push(RequestType::AddBackend(backend.clone().to_add_backend()).into());
2166 }
2167 DiffResult::Removed => {
2168 let backend = self
2169 .backends
2170 .get(cluster_id)
2171 .and_then(|v| v.iter().find(|b| &b.backend_id == backend_id))
2172 .unwrap();
2173
2174 v.push(
2175 RequestType::RemoveBackend(RemoveBackend {
2176 cluster_id: backend.cluster_id.clone(),
2177 backend_id: backend.backend_id.clone(),
2178 address: SocketAddress::from(backend.address),
2179 })
2180 .into(),
2181 );
2182 }
2183 DiffResult::Changed => {
2184 let backend = self
2185 .backends
2186 .get(cluster_id)
2187 .and_then(|v| v.iter().find(|b| &b.backend_id == backend_id))
2188 .unwrap();
2189
2190 v.push(
2191 RequestType::RemoveBackend(RemoveBackend {
2192 cluster_id: backend.cluster_id.clone(),
2193 backend_id: backend.backend_id.clone(),
2194 address: SocketAddress::from(backend.address),
2195 })
2196 .into(),
2197 );
2198
2199 let backend = other
2200 .backends
2201 .get(cluster_id)
2202 .and_then(|v| v.iter().find(|b| &b.backend_id == backend_id))
2203 .unwrap();
2204 v.push(RequestType::AddBackend(backend.clone().to_add_backend()).into());
2205 }
2206 }
2207 }
2208
2209 let mut my_http_fronts: HashSet<(&str, &HttpFrontend)> = HashSet::new();
2210 for (route, front) in self.http_fronts.iter() {
2211 my_http_fronts.insert((route, front));
2212 }
2213 let mut their_http_fronts: HashSet<(&str, &HttpFrontend)> = HashSet::new();
2214 for (route, front) in other.http_fronts.iter() {
2215 their_http_fronts.insert((route, front));
2216 }
2217
2218 let removed_http_fronts = my_http_fronts.difference(&their_http_fronts);
2219 let added_http_fronts = their_http_fronts.difference(&my_http_fronts);
2220
2221 for &(_, front) in removed_http_fronts {
2222 v.push(RequestType::RemoveHttpFrontend(front.clone().into()).into());
2223 }
2224
2225 for &(_, front) in added_http_fronts {
2226 v.push(RequestType::AddHttpFrontend(front.clone().into()).into());
2227 }
2228
2229 let mut my_https_fronts: HashSet<(&String, &HttpFrontend)> = HashSet::new();
2230 for (route, front) in self.https_fronts.iter() {
2231 my_https_fronts.insert((route, front));
2232 }
2233 let mut their_https_fronts: HashSet<(&String, &HttpFrontend)> = HashSet::new();
2234 for (route, front) in other.https_fronts.iter() {
2235 their_https_fronts.insert((route, front));
2236 }
2237 let removed_https_fronts = my_https_fronts.difference(&their_https_fronts);
2238 let added_https_fronts = their_https_fronts.difference(&my_https_fronts);
2239
2240 for &(_, front) in removed_https_fronts {
2241 v.push(RequestType::RemoveHttpsFrontend(front.clone().into()).into());
2242 }
2243
2244 for &(_, front) in added_https_fronts {
2245 v.push(RequestType::AddHttpsFrontend(front.clone().into()).into());
2246 }
2247
2248 let mut my_tcp_fronts: HashSet<(&ClusterId, &TcpFrontend)> = HashSet::new();
2249 for (cluster_id, front_list) in self.tcp_fronts.iter() {
2250 for front in front_list.iter() {
2251 my_tcp_fronts.insert((cluster_id, front));
2252 }
2253 }
2254 let mut their_tcp_fronts: HashSet<(&ClusterId, &TcpFrontend)> = HashSet::new();
2255 for (cluster_id, front_list) in other.tcp_fronts.iter() {
2256 for front in front_list.iter() {
2257 their_tcp_fronts.insert((cluster_id, front));
2258 }
2259 }
2260
2261 let removed_tcp_fronts = my_tcp_fronts.difference(&their_tcp_fronts);
2262 let added_tcp_fronts = their_tcp_fronts.difference(&my_tcp_fronts);
2263
2264 for &(_, front) in removed_tcp_fronts {
2265 v.push(RequestType::RemoveTcpFrontend(front.clone().into()).into());
2266 }
2267
2268 for &(_, front) in added_tcp_fronts {
2269 v.push(RequestType::AddTcpFrontend(front.clone().into()).into());
2270 }
2271
2272 let mut my_udp_fronts: HashSet<(&ClusterId, &UdpFrontend)> = HashSet::new();
2273 for (cluster_id, front_list) in self.udp_fronts.iter() {
2274 for front in front_list.iter() {
2275 my_udp_fronts.insert((cluster_id, front));
2276 }
2277 }
2278 let mut their_udp_fronts: HashSet<(&ClusterId, &UdpFrontend)> = HashSet::new();
2279 for (cluster_id, front_list) in other.udp_fronts.iter() {
2280 for front in front_list.iter() {
2281 their_udp_fronts.insert((cluster_id, front));
2282 }
2283 }
2284
2285 let removed_udp_fronts = my_udp_fronts.difference(&their_udp_fronts);
2286 let added_udp_fronts = their_udp_fronts.difference(&my_udp_fronts);
2287
2288 for &(_, front) in removed_udp_fronts {
2289 v.push(RequestType::RemoveUdpFrontend(front.clone().into()).into());
2290 }
2291
2292 for &(_, front) in added_udp_fronts {
2293 v.push(RequestType::AddUdpFrontend(front.clone().into()).into());
2294 }
2295
2296 let my_certificates: HashSet<(SocketAddr, &Fingerprint)> = HashSet::from_iter(
2298 self.certificates
2299 .iter()
2300 .flat_map(|(addr, certs)| repeat(*addr).zip(certs.keys())),
2301 );
2302 let their_certificates: HashSet<(SocketAddr, &Fingerprint)> = HashSet::from_iter(
2303 other
2304 .certificates
2305 .iter()
2306 .flat_map(|(addr, certs)| repeat(*addr).zip(certs.keys())),
2307 );
2308
2309 let removed_certificates = my_certificates.difference(&their_certificates);
2310 let added_certificates = their_certificates.difference(&my_certificates);
2311
2312 for &(address, fingerprint) in removed_certificates {
2313 v.push(
2314 RequestType::RemoveCertificate(RemoveCertificate {
2315 address: SocketAddress::from(address),
2316 fingerprint: fingerprint.to_string(),
2317 })
2318 .into(),
2319 );
2320 }
2321
2322 for &(address, fingerprint) in added_certificates {
2323 if let Some(certificate_and_key) = other
2324 .certificates
2325 .get(&address)
2326 .and_then(|certs| certs.get(fingerprint))
2327 {
2328 v.push(
2329 RequestType::AddCertificate(AddCertificate {
2330 address: SocketAddress::from(address),
2331 certificate: certificate_and_key.clone(),
2332 expired_at: None,
2333 })
2334 .into(),
2335 );
2336 }
2337 }
2338
2339 for address in added_tcp_listeners {
2340 let listener = &other.tcp_listeners[*address];
2341 if listener.active {
2342 v.push(
2343 RequestType::ActivateListener(ActivateListener {
2344 address: listener.address,
2345 proxy: ListenerType::Tcp.into(),
2346 from_scm: false,
2347 })
2348 .into(),
2349 );
2350 }
2351 }
2352
2353 for address in added_udp_listeners {
2354 let listener = &other.udp_listeners[*address];
2355 if listener.active {
2356 v.push(
2357 RequestType::ActivateListener(ActivateListener {
2358 address: listener.address,
2359 proxy: ListenerType::Udp.into(),
2360 from_scm: false,
2361 })
2362 .into(),
2363 );
2364 }
2365 }
2366
2367 #[cfg(debug_assertions)]
2380 {
2381 let mut replayed = self.clone();
2382 for request in &v {
2383 debug_assert!(
2385 replayed.dispatch(request).is_ok(),
2386 "every request emitted by diff must replay cleanly onto self"
2387 );
2388 }
2389 let nonempty = |m: &BTreeMap<ClusterId, Vec<Backend>>| {
2390 m.iter()
2391 .filter(|(_, v)| !v.is_empty())
2392 .map(|(k, v)| (k.clone(), v.clone()))
2393 .collect::<BTreeMap<_, _>>()
2394 };
2395 let nonempty_tcp = |m: &HashMap<ClusterId, Vec<TcpFrontend>>| {
2396 m.iter()
2397 .filter(|(_, v)| !v.is_empty())
2398 .map(|(k, v)| (k.clone(), v.clone()))
2399 .collect::<HashMap<_, _>>()
2400 };
2401 debug_assert!(
2402 replayed.clusters == other.clusters
2403 && nonempty(&replayed.backends) == nonempty(&other.backends)
2404 && replayed.http_fronts == other.http_fronts
2405 && replayed.https_fronts == other.https_fronts
2406 && nonempty_tcp(&replayed.tcp_fronts) == nonempty_tcp(&other.tcp_fronts)
2407 && replayed.certificates == other.certificates
2408 && replayed.http_listeners == other.http_listeners
2409 && replayed.https_listeners == other.https_listeners
2410 && replayed.tcp_listeners == other.tcp_listeners
2411 && replayed.udp_listeners == other.udp_listeners,
2412 "replaying diff(self, other) onto self must reproduce other's clusters/backends/frontends/certificates/listeners"
2413 );
2414 }
2415
2416 v
2417 }
2418
2419 pub fn hash_state(&self) -> BTreeMap<ClusterId, u64> {
2421 let mut hm: HashMap<ClusterId, DefaultHasher> = self
2422 .clusters
2423 .keys()
2424 .map(|cluster_id| {
2425 let mut hasher = DefaultHasher::new();
2426 self.clusters.get(cluster_id).hash(&mut hasher);
2427 if let Some(backends) = self.backends.get(cluster_id) {
2428 backends.iter().collect::<BTreeSet<_>>().hash(&mut hasher)
2429 }
2430 if let Some(tcp_fronts) = self.tcp_fronts.get(cluster_id) {
2431 tcp_fronts.iter().collect::<BTreeSet<_>>().hash(&mut hasher)
2432 }
2433 (cluster_id.to_owned(), hasher)
2434 })
2435 .collect();
2436
2437 for front in self.http_fronts.values() {
2438 if let Some(cluster_id) = &front.cluster_id
2439 && let Some(hasher) = hm.get_mut(cluster_id)
2440 {
2441 front.hash(hasher);
2442 }
2443 }
2444
2445 for front in self.https_fronts.values() {
2446 if let Some(cluster_id) = &front.cluster_id
2447 && let Some(hasher) = hm.get_mut(cluster_id)
2448 {
2449 front.hash(hasher);
2450 }
2451 }
2452
2453 hm.drain()
2454 .map(|(cluster_id, hasher)| (cluster_id, hasher.finish()))
2455 .collect()
2456 }
2457
2458 pub fn cluster_state(&self, cluster_id: &str) -> Option<ClusterInformation> {
2461 let configuration = self.clusters.get(cluster_id).cloned()?;
2462 info!("{:?}", configuration);
2463
2464 let http_frontends: Vec<RequestHttpFrontend> = self
2465 .http_fronts
2466 .values()
2467 .filter(|front| front.cluster_id.as_deref() == Some(cluster_id))
2468 .map(|front| front.clone().into())
2469 .collect();
2470
2471 let https_frontends: Vec<RequestHttpFrontend> = self
2472 .https_fronts
2473 .values()
2474 .filter(|front| front.cluster_id.as_deref() == Some(cluster_id))
2475 .map(|front| front.clone().into())
2476 .collect();
2477
2478 let tcp_frontends: Vec<RequestTcpFrontend> = self
2479 .tcp_fronts
2480 .get(cluster_id)
2481 .cloned()
2482 .unwrap_or_default()
2483 .iter()
2484 .map(|front| front.clone().into())
2485 .collect();
2486
2487 let udp_frontends: Vec<RequestUdpFrontend> = self
2488 .udp_fronts
2489 .get(cluster_id)
2490 .cloned()
2491 .unwrap_or_default()
2492 .iter()
2493 .map(|front| front.clone().into())
2494 .collect();
2495
2496 let backends: Vec<AddBackend> = self
2497 .backends
2498 .get(cluster_id)
2499 .cloned()
2500 .unwrap_or_default()
2501 .iter()
2502 .map(|backend| backend.clone().into())
2503 .collect();
2504
2505 Some(ClusterInformation {
2506 configuration: Some(configuration),
2507 http_frontends,
2508 https_frontends,
2509 tcp_frontends,
2510 backends,
2511 udp_frontends,
2512 })
2513 }
2514
2515 pub fn count_backends(&self) -> usize {
2516 self.backends.values().fold(0, |acc, v| acc + v.len())
2517 }
2518
2519 pub fn count_frontends(&self) -> usize {
2520 self.http_fronts.values().count()
2521 + self.https_fronts.values().count()
2522 + self.tcp_fronts.values().fold(0, |acc, v| acc + v.len())
2523 + self.udp_fronts.values().fold(0, |acc, v| acc + v.len())
2524 }
2525
2526 pub fn get_cluster_ids_by_domain(
2527 &self,
2528 hostname: String,
2529 path: Option<String>,
2530 ) -> HashSet<ClusterId> {
2531 let mut cluster_ids: HashSet<ClusterId> = HashSet::new();
2532
2533 self.http_fronts.values().for_each(|front| {
2534 if domain_check(&front.hostname, &front.path, &hostname, &path)
2535 && let Some(id) = &front.cluster_id
2536 {
2537 cluster_ids.insert(id.to_string());
2538 }
2539 });
2540
2541 self.https_fronts.values().for_each(|front| {
2542 if domain_check(&front.hostname, &front.path, &hostname, &path)
2543 && let Some(id) = &front.cluster_id
2544 {
2545 cluster_ids.insert(id.to_string());
2546 }
2547 });
2548
2549 cluster_ids
2550 }
2551
2552 pub fn get_certificates(
2553 &self,
2554 filters: QueryCertificatesFilters,
2555 ) -> BTreeMap<String, CertificateAndKey> {
2556 self.certificates
2557 .values()
2558 .flat_map(|hash_map| hash_map.iter())
2559 .filter(|(fingerprint, cert)| {
2560 if let Some(domain) = &filters.domain {
2561 cert.names.contains(domain)
2562 } else if let Some(f) = &filters.fingerprint {
2563 fingerprint.to_string() == *f
2564 } else {
2565 true
2566 }
2567 })
2568 .map(|(fingerprint, cert)| (fingerprint.to_string(), cert.to_owned()))
2569 .collect()
2570 }
2571
2572 pub fn list_frontends(&self, filters: FrontendFilters) -> ListedFrontends {
2573 let list_all = !filters.http && !filters.https && !filters.tcp;
2575
2576 let mut listed_frontends = ListedFrontends::default();
2577
2578 if filters.http || list_all {
2579 for http_frontend in self.http_fronts.iter().filter(|f| {
2580 if let Some(domain) = &filters.domain {
2581 f.1.hostname.contains(domain)
2582 } else {
2583 true
2584 }
2585 }) {
2586 listed_frontends
2587 .http_frontends
2588 .push(http_frontend.1.to_owned().into());
2589 }
2590 }
2591
2592 if filters.https || list_all {
2593 for https_frontend in self.https_fronts.iter().filter(|f| {
2594 if let Some(domain) = &filters.domain {
2595 f.1.hostname.contains(domain)
2596 } else {
2597 true
2598 }
2599 }) {
2600 listed_frontends
2601 .https_frontends
2602 .push(https_frontend.1.to_owned().into());
2603 }
2604 }
2605
2606 if (filters.tcp || list_all) && filters.domain.is_none() {
2607 for tcp_frontend in self.tcp_fronts.values().flat_map(|v| v.iter()) {
2608 listed_frontends
2609 .tcp_frontends
2610 .push(tcp_frontend.to_owned().into())
2611 }
2612 }
2613
2614 if (filters.tcp || list_all) && filters.domain.is_none() {
2620 for udp_frontend in self.udp_fronts.values().flat_map(|v| v.iter()) {
2621 listed_frontends
2622 .udp_frontends
2623 .push(udp_frontend.to_owned().into())
2624 }
2625 }
2626
2627 listed_frontends
2628 }
2629
2630 pub fn list_listeners(&self) -> ListenersList {
2631 ListenersList {
2632 http_listeners: self
2633 .http_listeners
2634 .iter()
2635 .map(|(addr, listener)| (addr.to_string(), listener.clone()))
2636 .collect(),
2637 https_listeners: self
2638 .https_listeners
2639 .iter()
2640 .map(|(addr, listener)| (addr.to_string(), listener.clone()))
2641 .collect(),
2642 tcp_listeners: self
2643 .tcp_listeners
2644 .iter()
2645 .map(|(addr, listener)| (addr.to_string(), *listener))
2646 .collect(),
2647 udp_listeners: self
2648 .udp_listeners
2649 .iter()
2650 .map(|(addr, listener)| (addr.to_string(), *listener))
2651 .collect(),
2652 }
2653 }
2654
2655 pub fn produce_initial_state(&self) -> InitialState {
2657 let mut worker_requests = Vec::new();
2658 for (counter, request) in self.generate_requests().into_iter().enumerate() {
2659 worker_requests.push(WorkerRequest::new(format!("SAVE-{counter}"), request));
2660 }
2661 InitialState {
2662 requests: worker_requests,
2663 }
2664 }
2665
2666 pub fn write_initial_state_to_file(&self, file: &mut File) -> Result<usize, StateError> {
2669 let initial_state = self.produce_initial_state();
2670 let count = initial_state.requests.len();
2671
2672 let bytes_to_write = initial_state.encode_to_vec();
2673 println!("writing {} in the temp file", bytes_to_write.len());
2674 file.write_all(&bytes_to_write)
2675 .map_err(StateError::FileError)?;
2676
2677 file.sync_all().map_err(StateError::FileError)?;
2678
2679 Ok(count)
2680 }
2681
2682 pub fn write_requests_to_file(&self, file: &mut File) -> Result<usize, StateError> {
2686 let mut counter = 0usize;
2687 let requests = self.generate_requests();
2688
2689 for request in requests {
2690 let message = WorkerRequest::new(format!("SAVE-{counter}"), request);
2691
2692 file.write_all(
2693 &serde_json::to_string(&message)
2694 .map(|s| s.into_bytes())
2695 .unwrap_or_default(),
2696 )
2697 .map_err(StateError::FileError)?;
2698
2699 file.write_all(&b"\n\0"[..])
2700 .map_err(StateError::FileError)?;
2701
2702 if counter.is_multiple_of(1000) {
2703 info!("writing {} commands to file", counter);
2704 file.sync_all().map_err(StateError::FileError)?;
2705 }
2706 counter += 1;
2707 }
2708 file.sync_all().map_err(StateError::FileError)?;
2709
2710 Ok(counter)
2711 }
2712}
2713
2714pub fn validate_h2_flood_knobs_http(patch: &UpdateHttpListenerConfig) -> Result<(), StateError> {
2728 macro_rules! require_ge1 {
2729 ($field:expr, $name:literal) => {
2730 if let Some(0) = $field {
2731 return Err(StateError::InvalidValue {
2732 field: $name,
2733 reason: "must be >= 1",
2734 });
2735 }
2736 };
2737 }
2738 require_ge1!(
2739 patch.h2_max_rst_stream_per_window,
2740 "h2_max_rst_stream_per_window"
2741 );
2742 require_ge1!(patch.h2_max_ping_per_window, "h2_max_ping_per_window");
2743 require_ge1!(
2744 patch.h2_max_settings_per_window,
2745 "h2_max_settings_per_window"
2746 );
2747 require_ge1!(
2748 patch.h2_max_empty_data_per_window,
2749 "h2_max_empty_data_per_window"
2750 );
2751 require_ge1!(
2752 patch.h2_max_continuation_frames,
2753 "h2_max_continuation_frames"
2754 );
2755 require_ge1!(patch.h2_max_glitch_count, "h2_max_glitch_count");
2756 require_ge1!(
2757 patch.h2_max_window_update_stream0_per_window,
2758 "h2_max_window_update_stream0_per_window"
2759 );
2760 require_ge1!(patch.h2_max_concurrent_streams, "h2_max_concurrent_streams");
2761 if let Some(v) = patch.h2_stream_shrink_ratio
2764 && v < 2
2765 {
2766 return Err(StateError::InvalidValue {
2767 field: "h2_stream_shrink_ratio",
2768 reason: "must be >= 2",
2769 });
2770 }
2771 require_ge1!(
2774 patch.h2_max_rst_stream_lifetime,
2775 "h2_max_rst_stream_lifetime"
2776 );
2777 require_ge1!(
2778 patch.h2_max_rst_stream_abusive_lifetime,
2779 "h2_max_rst_stream_abusive_lifetime"
2780 );
2781 require_ge1!(
2782 patch.h2_max_rst_stream_emitted_lifetime,
2783 "h2_max_rst_stream_emitted_lifetime"
2784 );
2785 require_ge1!(patch.h2_max_header_list_size, "h2_max_header_list_size");
2786 require_ge1!(patch.h2_max_header_table_size, "h2_max_header_table_size");
2787 require_ge1!(patch.h2_max_header_fields, "h2_max_header_fields");
2788 Ok(())
2789}
2790
2791pub fn validate_h2_flood_knobs_https(patch: &UpdateHttpsListenerConfig) -> Result<(), StateError> {
2793 macro_rules! require_ge1 {
2794 ($field:expr, $name:literal) => {
2795 if let Some(0) = $field {
2796 return Err(StateError::InvalidValue {
2797 field: $name,
2798 reason: "must be >= 1",
2799 });
2800 }
2801 };
2802 }
2803 require_ge1!(
2804 patch.h2_max_rst_stream_per_window,
2805 "h2_max_rst_stream_per_window"
2806 );
2807 require_ge1!(patch.h2_max_ping_per_window, "h2_max_ping_per_window");
2808 require_ge1!(
2809 patch.h2_max_settings_per_window,
2810 "h2_max_settings_per_window"
2811 );
2812 require_ge1!(
2813 patch.h2_max_empty_data_per_window,
2814 "h2_max_empty_data_per_window"
2815 );
2816 require_ge1!(
2817 patch.h2_max_continuation_frames,
2818 "h2_max_continuation_frames"
2819 );
2820 require_ge1!(patch.h2_max_glitch_count, "h2_max_glitch_count");
2821 require_ge1!(
2822 patch.h2_max_window_update_stream0_per_window,
2823 "h2_max_window_update_stream0_per_window"
2824 );
2825 require_ge1!(patch.h2_max_concurrent_streams, "h2_max_concurrent_streams");
2826 if let Some(v) = patch.h2_stream_shrink_ratio
2827 && v < 2
2828 {
2829 return Err(StateError::InvalidValue {
2830 field: "h2_stream_shrink_ratio",
2831 reason: "must be >= 2",
2832 });
2833 }
2834 require_ge1!(
2835 patch.h2_max_rst_stream_lifetime,
2836 "h2_max_rst_stream_lifetime"
2837 );
2838 require_ge1!(
2839 patch.h2_max_rst_stream_abusive_lifetime,
2840 "h2_max_rst_stream_abusive_lifetime"
2841 );
2842 require_ge1!(
2843 patch.h2_max_rst_stream_emitted_lifetime,
2844 "h2_max_rst_stream_emitted_lifetime"
2845 );
2846 require_ge1!(patch.h2_max_header_list_size, "h2_max_header_list_size");
2847 require_ge1!(patch.h2_max_header_table_size, "h2_max_header_table_size");
2848 require_ge1!(patch.h2_max_header_fields, "h2_max_header_fields");
2849 Ok(())
2850}
2851
2852pub fn merge_custom_http_answers(
2865 target: &mut Option<CustomHttpAnswers>,
2866 patch: &CustomHttpAnswers,
2867) {
2868 let current = target.get_or_insert_with(CustomHttpAnswers::default);
2869 macro_rules! merge_field {
2870 ($field:ident) => {
2871 if let Some(ref v) = patch.$field {
2872 current.$field = Some(v.clone());
2873 }
2874 };
2875 }
2876 merge_field!(answer_301);
2877 merge_field!(answer_400);
2878 merge_field!(answer_401);
2879 merge_field!(answer_404);
2880 merge_field!(answer_408);
2881 merge_field!(answer_413);
2882 merge_field!(answer_421);
2883 merge_field!(answer_502);
2884 merge_field!(answer_503);
2885 merge_field!(answer_504);
2886 merge_field!(answer_507);
2887}
2888
2889pub fn validate_alpn_protocols(values: &[String]) -> Result<(), StateError> {
2892 for value in values {
2893 if value != "h2" && value != "http/1.1" {
2894 return Err(StateError::InvalidValue {
2895 field: "alpn_protocols",
2896 reason: "each value must be \"h2\" or \"http/1.1\"",
2897 });
2898 }
2899 }
2900 Ok(())
2901}
2902
2903pub fn validate_sozu_id_header(value: &str) -> Result<(), StateError> {
2914 if value.is_empty() {
2915 return Err(StateError::InvalidValue {
2916 field: "sozu_id_header",
2917 reason: "must not be empty",
2918 });
2919 }
2920 for b in value.bytes() {
2921 let is_tchar = b.is_ascii_alphanumeric()
2922 || matches!(
2923 b,
2924 b'!' | b'#'
2925 | b'$'
2926 | b'%'
2927 | b'&'
2928 | b'\''
2929 | b'*'
2930 | b'+'
2931 | b'-'
2932 | b'.'
2933 | b'^'
2934 | b'_'
2935 | b'`'
2936 | b'|'
2937 | b'~'
2938 );
2939 if !is_tchar {
2940 return Err(StateError::InvalidValue {
2941 field: "sozu_id_header",
2942 reason: "must be a valid HTTP header name (RFC 9110 §5.1 token: alphanumeric or one of !#$%&'*+-.^_`|~)",
2943 });
2944 }
2945 }
2946 Ok(())
2947}
2948
2949fn domain_check(
2950 front_hostname: &str,
2951 front_path_rule: &PathRule,
2952 hostname: &str,
2953 path_prefix: &Option<String>,
2954) -> bool {
2955 if hostname != front_hostname {
2956 return false;
2957 }
2958
2959 if let Some(path) = &path_prefix {
2960 return path == &front_path_rule.value;
2961 }
2962
2963 true
2964}
2965
2966fn canonical_tcp_alpn(alpn: &[String]) -> Vec<String> {
2976 let mut alpn = alpn.to_vec();
2977 alpn.sort();
2978 alpn.dedup();
2979 debug_assert!(
2983 alpn.windows(2).all(|pair| pair[0] < pair[1]),
2984 "canonical_tcp_alpn must return a strictly sorted, duplicate-free list"
2985 );
2986 alpn
2987}
2988
2989fn tcp_frontend_matches(
3000 front: &TcpFrontend,
3001 address: SocketAddr,
3002 sni: &Option<String>,
3003 alpn: &[String],
3004) -> bool {
3005 front.address == address && front.sni == *sni && front.alpn.as_slice() == alpn
3006}
3007
3008struct DiffMap<'a, K: Ord, V, I1, I2> {
3009 my_it: I1,
3010 other_it: I2,
3011 my: Option<(K, &'a V)>,
3012 other: Option<(K, &'a V)>,
3013}
3014
3015fn diff_map<
3017 'a,
3018 K: Ord,
3019 V: PartialEq,
3020 I1: Iterator<Item = (K, &'a V)>,
3021 I2: Iterator<Item = (K, &'a V)>,
3022>(
3023 my: I1,
3024 other: I2,
3025) -> DiffMap<'a, K, V, I1, I2> {
3026 DiffMap {
3027 my_it: my,
3028 other_it: other,
3029 my: None,
3030 other: None,
3031 }
3032}
3033
3034enum DiffResult {
3035 Added,
3036 Removed,
3037 Changed,
3038}
3039
3040impl<'a, K: Ord, V: PartialEq, I1: Iterator<Item = (K, &'a V)>, I2: Iterator<Item = (K, &'a V)>>
3043 std::iter::Iterator for DiffMap<'a, K, V, I1, I2>
3044{
3045 type Item = (K, DiffResult);
3046
3047 fn next(&mut self) -> Option<Self::Item> {
3048 loop {
3049 if self.my.is_none() {
3050 self.my = self.my_it.next();
3051 }
3052 if self.other.is_none() {
3053 self.other = self.other_it.next();
3054 }
3055
3056 match (self.my.take(), self.other.take()) {
3057 (None, other) => return other.map(|(k, _)| (k, DiffResult::Added)),
3061 (Some((k, _)), None) => return Some((k, DiffResult::Removed)),
3064 (Some((k1, _v1)), Some((k2, v2))) if k1 < k2 => {
3066 self.other = Some((k2, v2));
3067 return Some((k1, DiffResult::Removed));
3068 }
3069 (Some((k1, v1)), Some((k2, _v2))) if k1 > k2 => {
3071 self.my = Some((k1, v1));
3072 return Some((k2, DiffResult::Added));
3073 }
3074 (Some((k1, v1)), Some((_k2, v2))) if v1 != v2 => {
3075 return Some((k1, DiffResult::Changed));
3078 }
3079 _ => {}
3080 }
3081 }
3082 }
3083}
3084
3085#[cfg(test)]
3086mod tests {
3087 use rand::{RngExt, rng, seq::SliceRandom};
3088
3089 use super::*;
3090 use crate::proto::command::{
3091 CustomHttpAnswers, LoadBalancingParams, RequestHttpFrontend, RequestTcpFrontend,
3092 RequestUdpFrontend, RulePosition, UdpListenerConfig, UpdateUdpListenerConfig,
3093 };
3094
3095 #[test]
3096 fn serialize() {
3097 let mut state: ConfigState = Default::default();
3098 state
3099 .dispatch(
3100 &RequestType::AddHttpFrontend(RequestHttpFrontend {
3101 cluster_id: Some(String::from("cluster_1")),
3102 hostname: String::from("lolcatho.st:8080"),
3103 path: PathRule::prefix(String::from("/")),
3104 address: SocketAddress::new_v4(0, 0, 0, 0, 8080),
3105 position: RulePosition::Tree.into(),
3106 ..Default::default()
3107 })
3108 .into(),
3109 )
3110 .expect("Could not execute request");
3111 state
3112 .dispatch(
3113 &RequestType::AddHttpFrontend(RequestHttpFrontend {
3114 cluster_id: Some(String::from("cluster_2")),
3115 hostname: String::from("test.local"),
3116 path: PathRule::prefix(String::from("/abc")),
3117 address: SocketAddress::new_v4(0, 0, 0, 0, 8080),
3118 position: RulePosition::Pre.into(),
3119 ..Default::default()
3120 })
3121 .into(),
3122 )
3123 .expect("Could not execute request");
3124 state
3125 .dispatch(
3126 &RequestType::AddBackend(AddBackend {
3127 cluster_id: String::from("cluster_1"),
3128 backend_id: String::from("cluster_1-0"),
3129 address: SocketAddress::new_v4(127, 0, 0, 1, 1026),
3130 ..Default::default()
3131 })
3132 .into(),
3133 )
3134 .expect("Could not execute request");
3135 state
3136 .dispatch(
3137 &RequestType::AddBackend(AddBackend {
3138 cluster_id: String::from("cluster_1"),
3139 backend_id: String::from("cluster_1-1"),
3140 address: SocketAddress::new_v4(127, 0, 0, 1, 1027),
3141 ..Default::default()
3142 })
3143 .into(),
3144 )
3145 .expect("Could not execute request");
3146 state
3147 .dispatch(
3148 &RequestType::AddBackend(AddBackend {
3149 cluster_id: String::from("cluster_2"),
3150 backend_id: String::from("cluster_2-0"),
3151 address: SocketAddress::new_v4(192, 167, 1, 2, 1026),
3152 ..Default::default()
3153 })
3154 .into(),
3155 )
3156 .expect("Could not execute request");
3157 state
3158 .dispatch(
3159 &RequestType::AddBackend(AddBackend {
3160 cluster_id: String::from("cluster_1"),
3161 backend_id: String::from("cluster_1-3"),
3162 address: SocketAddress::new_v4(192, 168, 1, 3, 1027),
3163 ..Default::default()
3164 })
3165 .into(),
3166 )
3167 .expect("Could not execute request");
3168 state
3169 .dispatch(
3170 &RequestType::RemoveBackend(RemoveBackend {
3171 cluster_id: String::from("cluster_1"),
3172 backend_id: String::from("cluster_1-3"),
3173 address: SocketAddress::new_v4(192, 168, 1, 3, 1027),
3174 })
3175 .into(),
3176 )
3177 .expect("Could not execute request");
3178
3179 }
3189
3190 #[test]
3191 fn diff() {
3192 let mut state: ConfigState = Default::default();
3193 state
3194 .dispatch(
3195 &RequestType::AddHttpFrontend(RequestHttpFrontend {
3196 cluster_id: Some(String::from("cluster_1")),
3197 hostname: String::from("lolcatho.st:8080"),
3198 path: PathRule::prefix(String::from("/")),
3199 address: SocketAddress::new_v4(0, 0, 0, 0, 8080),
3200 position: RulePosition::Post.into(),
3201 ..Default::default()
3202 })
3203 .into(),
3204 )
3205 .expect("Could not execute request");
3206 state
3207 .dispatch(
3208 &RequestType::AddHttpFrontend(RequestHttpFrontend {
3209 cluster_id: Some(String::from("cluster_2")),
3210 hostname: String::from("test.local"),
3211 path: PathRule::prefix(String::from("/abc")),
3212 address: SocketAddress::new_v4(0, 0, 0, 0, 8080),
3213 ..Default::default()
3214 })
3215 .into(),
3216 )
3217 .expect("Could not execute request");
3218 state
3219 .dispatch(
3220 &RequestType::AddBackend(AddBackend {
3221 cluster_id: String::from("cluster_1"),
3222 backend_id: String::from("cluster_1-0"),
3223 address: SocketAddress::new_v4(127, 0, 0, 1, 1026),
3224 load_balancing_parameters: Some(LoadBalancingParams::default()),
3225 ..Default::default()
3226 })
3227 .into(),
3228 )
3229 .expect("Could not execute request");
3230 state
3231 .dispatch(
3232 &RequestType::AddBackend(AddBackend {
3233 cluster_id: String::from("cluster_1"),
3234 backend_id: String::from("cluster_1-1"),
3235 address: SocketAddress::new_v4(127, 0, 0, 2, 1027),
3236 load_balancing_parameters: Some(LoadBalancingParams::default()),
3237 ..Default::default()
3238 })
3239 .into(),
3240 )
3241 .expect("Could not execute request");
3242 state
3243 .dispatch(
3244 &RequestType::AddBackend(AddBackend {
3245 cluster_id: String::from("cluster_2"),
3246 backend_id: String::from("cluster_2-0"),
3247 address: SocketAddress::new_v4(192, 167, 1, 2, 1026),
3248 load_balancing_parameters: Some(LoadBalancingParams::default()),
3249 ..Default::default()
3250 })
3251 .into(),
3252 )
3253 .expect("Could not execute request");
3254 state
3255 .dispatch(
3256 &RequestType::AddCluster(Cluster {
3257 cluster_id: String::from("cluster_2"),
3258 sticky_session: true,
3259 https_redirect: true,
3260 ..Default::default()
3261 })
3262 .into(),
3263 )
3264 .expect("Could not execute request");
3265
3266 let mut state2: ConfigState = Default::default();
3267 state2
3268 .dispatch(
3269 &RequestType::AddHttpFrontend(RequestHttpFrontend {
3270 cluster_id: Some(String::from("cluster_1")),
3271 hostname: String::from("lolcatho.st:8080"),
3272 path: PathRule::prefix(String::from("/")),
3273 address: SocketAddress::new_v4(0, 0, 0, 0, 8080),
3274 position: RulePosition::Post.into(),
3275 ..Default::default()
3276 })
3277 .into(),
3278 )
3279 .expect("Could not execute request");
3280 state2
3281 .dispatch(
3282 &RequestType::AddBackend(AddBackend {
3283 cluster_id: String::from("cluster_1"),
3284 backend_id: String::from("cluster_1-0"),
3285 address: SocketAddress::new_v4(127, 0, 0, 1, 1026),
3286 load_balancing_parameters: Some(LoadBalancingParams::default()),
3287 ..Default::default()
3288 })
3289 .into(),
3290 )
3291 .expect("Could not execute request");
3292 state2
3293 .dispatch(
3294 &RequestType::AddBackend(AddBackend {
3295 cluster_id: String::from("cluster_1"),
3296 backend_id: String::from("cluster_1-1"),
3297 address: SocketAddress::new_v4(127, 0, 0, 2, 1027),
3298 load_balancing_parameters: Some(LoadBalancingParams::default()),
3299 ..Default::default()
3300 })
3301 .into(),
3302 )
3303 .expect("Could not execute request");
3304 state2
3305 .dispatch(
3306 &RequestType::AddBackend(AddBackend {
3307 cluster_id: String::from("cluster_1"),
3308 backend_id: String::from("cluster_1-2"),
3309 address: SocketAddress::new_v4(127, 0, 0, 2, 1028),
3310 load_balancing_parameters: Some(LoadBalancingParams::default()),
3311 ..Default::default()
3312 })
3313 .into(),
3314 )
3315 .expect("Could not execute request");
3316 state2
3317 .dispatch(
3318 &RequestType::AddCluster(Cluster {
3319 cluster_id: String::from("cluster_3"),
3320 sticky_session: false,
3321 https_redirect: false,
3322 ..Default::default()
3323 })
3324 .into(),
3325 )
3326 .expect("Could not execute request");
3327
3328 let e: Vec<Request> = vec![
3329 RequestType::RemoveHttpFrontend(RequestHttpFrontend {
3330 cluster_id: Some(String::from("cluster_2")),
3331 hostname: String::from("test.local"),
3332 path: PathRule::prefix(String::from("/abc")),
3333 address: SocketAddress::new_v4(0, 0, 0, 0, 8080),
3334 ..Default::default()
3335 })
3336 .into(),
3337 RequestType::RemoveBackend(RemoveBackend {
3338 cluster_id: String::from("cluster_2"),
3339 backend_id: String::from("cluster_2-0"),
3340 address: SocketAddress::new_v4(192, 167, 1, 2, 1026),
3341 })
3342 .into(),
3343 RequestType::AddBackend(AddBackend {
3344 cluster_id: String::from("cluster_1"),
3345 backend_id: String::from("cluster_1-2"),
3346 address: SocketAddress::new_v4(127, 0, 0, 2, 1028),
3347 load_balancing_parameters: Some(LoadBalancingParams::default()),
3348 ..Default::default()
3349 })
3350 .into(),
3351 RequestType::RemoveCluster(String::from("cluster_2")).into(),
3352 RequestType::AddCluster(Cluster {
3353 cluster_id: String::from("cluster_3"),
3354 sticky_session: false,
3355 https_redirect: false,
3356 ..Default::default()
3357 })
3358 .into(),
3359 ];
3360 let expected_diff: HashSet<&Request> = HashSet::from_iter(e.iter());
3361
3362 let d = state.diff(&state2);
3363 let diff = HashSet::from_iter(d.iter());
3364 println!("diff requests:\n{diff:#?}\n");
3365 println!("expected diff requests:\n{expected_diff:#?}\n");
3366
3367 let hash1 = state.hash_state();
3368 let hash2 = state2.hash_state();
3369 let mut state3 = state.clone();
3370 state3
3371 .dispatch(
3372 &RequestType::AddBackend(AddBackend {
3373 cluster_id: String::from("cluster_1"),
3374 backend_id: String::from("cluster_1-2"),
3375 address: SocketAddress::new_v4(127, 0, 0, 2, 1028),
3376 load_balancing_parameters: Some(LoadBalancingParams::default()),
3377 ..Default::default()
3378 })
3379 .into(),
3380 )
3381 .expect("Could not execute request");
3382 let hash3 = state3.hash_state();
3383 println!("state 1 hashes: {hash1:#?}");
3384 println!("state 2 hashes: {hash2:#?}");
3385 println!("state 3 hashes: {hash3:#?}");
3386
3387 assert_eq!(diff, expected_diff);
3388 }
3389
3390 #[test]
3391 fn cluster_ids_by_domain() {
3392 let mut config = ConfigState::new();
3393 let http_front_cluster1 = RequestHttpFrontend {
3394 cluster_id: Some(String::from("MyCluster_1")),
3395 hostname: String::from("lolcatho.st"),
3396 path: PathRule::prefix(String::from("")),
3397 address: SocketAddress::new_v4(0, 0, 0, 0, 8080),
3398 ..Default::default()
3399 };
3400
3401 let https_front_cluster1 = RequestHttpFrontend {
3402 cluster_id: Some(String::from("MyCluster_1")),
3403 hostname: String::from("lolcatho.st"),
3404 path: PathRule::prefix(String::from("")),
3405 address: SocketAddress::new_v4(0, 0, 0, 0, 8443),
3406 ..Default::default()
3407 };
3408
3409 let http_front_cluster2 = RequestHttpFrontend {
3410 cluster_id: Some(String::from("MyCluster_2")),
3411 hostname: String::from("lolcatho.st"),
3412 path: PathRule::prefix(String::from("/api")),
3413 address: SocketAddress::new_v4(0, 0, 0, 0, 8080),
3414 ..Default::default()
3415 };
3416
3417 let https_front_cluster2 = RequestHttpFrontend {
3418 cluster_id: Some(String::from("MyCluster_2")),
3419 hostname: String::from("lolcatho.st"),
3420 path: PathRule::prefix(String::from("/api")),
3421 address: SocketAddress::new_v4(0, 0, 0, 0, 8443),
3422 ..Default::default()
3423 };
3424
3425 config
3426 .dispatch(&RequestType::AddHttpFrontend(http_front_cluster1).into())
3427 .expect("Could not execute request");
3428 config
3429 .dispatch(&RequestType::AddHttpFrontend(http_front_cluster2).into())
3430 .expect("Could not execute request");
3431 config
3432 .dispatch(&RequestType::AddHttpsFrontend(https_front_cluster1).into())
3433 .expect("Could not execute request");
3434 config
3435 .dispatch(&RequestType::AddHttpsFrontend(https_front_cluster2).into())
3436 .expect("Could not execute request");
3437
3438 let mut cluster1_cluster2: HashSet<ClusterId> = HashSet::new();
3439 cluster1_cluster2.insert(String::from("MyCluster_1"));
3440 cluster1_cluster2.insert(String::from("MyCluster_2"));
3441
3442 let mut cluster2: HashSet<ClusterId> = HashSet::new();
3443 cluster2.insert(String::from("MyCluster_2"));
3444
3445 let empty: HashSet<ClusterId> = HashSet::new();
3446 assert_eq!(
3447 config.get_cluster_ids_by_domain(String::from("lolcatho.st"), None),
3448 cluster1_cluster2
3449 );
3450 assert_eq!(
3451 config
3452 .get_cluster_ids_by_domain(String::from("lolcatho.st"), Some(String::from("/api"))),
3453 cluster2
3454 );
3455 assert_eq!(
3456 config.get_cluster_ids_by_domain(String::from("lolcathost"), None),
3457 empty
3458 );
3459 assert_eq!(
3460 config
3461 .get_cluster_ids_by_domain(String::from("lolcathost"), Some(String::from("/sozu"))),
3462 empty
3463 );
3464 }
3465
3466 #[test]
3467 fn duplicate_backends() {
3468 let mut state: ConfigState = Default::default();
3469 state
3470 .dispatch(
3471 &RequestType::AddBackend(AddBackend {
3472 cluster_id: String::from("cluster_1"),
3473 backend_id: String::from("cluster_1-0"),
3474 address: SocketAddress::new_v4(127, 0, 0, 1, 1026),
3475 load_balancing_parameters: Some(LoadBalancingParams::default()),
3476 ..Default::default()
3477 })
3478 .into(),
3479 )
3480 .expect("Could not execute request");
3481
3482 let b = Backend {
3483 cluster_id: String::from("cluster_1"),
3484 backend_id: String::from("cluster_1-0"),
3485 address: "127.0.0.1:1026".parse().unwrap(),
3486 load_balancing_parameters: Some(LoadBalancingParams::default()),
3487 sticky_id: Some("sticky".to_string()),
3488 backup: None,
3489 };
3490
3491 state
3492 .dispatch(&RequestType::AddBackend(b.clone().to_add_backend()).into())
3493 .expect("Could not execute order");
3494
3495 assert_eq!(state.backends.get("cluster_1").unwrap(), &vec![b]);
3496 }
3497
3498 #[test]
3499 fn remove_backend() {
3500 let mut state: ConfigState = Default::default();
3501 state
3502 .dispatch(
3503 &RequestType::AddCluster(Cluster {
3504 cluster_id: String::from("cluster_1"),
3505 ..Default::default()
3506 })
3507 .into(),
3508 )
3509 .expect("Could not execute request");
3510
3511 for i in 0..10 {
3512 state
3513 .dispatch(
3514 &RequestType::AddBackend(AddBackend {
3515 cluster_id: String::from("cluster_1"),
3516 backend_id: format!("cluster_1-{i}"),
3517 address: SocketAddress::new_v4(127, 0, 0, 1, 1026),
3518 ..Default::default()
3519 })
3520 .into(),
3521 )
3522 .expect("Could not execute request");
3523 }
3524
3525 assert_eq!(state.backends.get("cluster_1").unwrap().len(), 10);
3526
3527 let remove_backend_2 = RequestType::RemoveBackend(RemoveBackend {
3528 cluster_id: String::from("cluster_1"),
3529 backend_id: String::from("cluster_1-0"),
3530 address: SocketAddress::new_v4(127, 0, 0, 1, 1026),
3531 })
3532 .into();
3533
3534 let remove_backend_result = state.dispatch(&remove_backend_2);
3535
3536 assert!(remove_backend_result.is_ok());
3537 assert_eq!(state.backends.get("cluster_1").unwrap().len(), 9);
3538
3539 let redundant_remove = state.dispatch(&remove_backend_2);
3540 assert!(matches!(redundant_remove, Err(StateError::NoChange)));
3541 assert_eq!(state.backends.get("cluster_1").unwrap().len(), 9);
3542 }
3543
3544 #[test]
3545 fn remove_backends_randomly() {
3546 let mut state: ConfigState = Default::default();
3547 state
3548 .dispatch(
3549 &RequestType::AddCluster(Cluster {
3550 cluster_id: String::from("cluster_1"),
3551 ..Default::default()
3552 })
3553 .into(),
3554 )
3555 .expect("Could not execute request");
3556
3557 for _ in 0..1000 {
3558 for i in 0..10 {
3559 state
3560 .dispatch(
3561 &RequestType::AddBackend(AddBackend {
3562 cluster_id: String::from("cluster_1"),
3563 backend_id: format!("cluster_1-{i}"),
3564 address: SocketAddress::new_v4(127, 0, 0, 1, 1026),
3565 ..Default::default()
3566 })
3567 .into(),
3568 )
3569 .expect("Could not execute request");
3570 }
3571
3572 let mut rng = rng();
3573 let mut indexes = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
3574 indexes.shuffle(&mut rng);
3575 let random_count = rng.random_range(1..indexes.len());
3576 let random_indexes: Vec<i32> = indexes.into_iter().take(random_count).collect();
3577
3578 for j in random_indexes {
3579 let remove_backend_result = state.dispatch(
3580 &RequestType::RemoveBackend(RemoveBackend {
3581 cluster_id: String::from("cluster_1"),
3582 backend_id: format!("cluster_1-{j}"),
3583 address: SocketAddress::new_v4(127, 0, 0, 1, 1026),
3584 })
3585 .into(),
3586 );
3587 assert!(remove_backend_result.is_ok());
3588 }
3589 }
3590 }
3591
3592 #[test]
3593 fn listener_diff() {
3594 let mut state: ConfigState = Default::default();
3595 let custom_http_answers = Some(CustomHttpAnswers {
3596 answer_404: Some("test".to_string()),
3597 ..Default::default()
3598 });
3599 state
3600 .dispatch(
3601 &RequestType::AddTcpListener(TcpListenerConfig {
3602 address: SocketAddress::new_v4(0, 0, 0, 0, 1234),
3603 ..Default::default()
3604 })
3605 .into(),
3606 )
3607 .expect("Could not execute request");
3608 state
3609 .dispatch(
3610 &RequestType::ActivateListener(ActivateListener {
3611 address: SocketAddress::new_v4(0, 0, 0, 0, 1234),
3612 proxy: ListenerType::Tcp.into(),
3613 from_scm: false,
3614 })
3615 .into(),
3616 )
3617 .expect("Could not execute request");
3618 state
3619 .dispatch(
3620 &RequestType::AddHttpListener(HttpListenerConfig {
3621 address: SocketAddress::new_v4(0, 0, 0, 0, 8080),
3622 ..Default::default()
3623 })
3624 .into(),
3625 )
3626 .expect("Could not execute request");
3627 state
3628 .dispatch(
3629 &RequestType::AddHttpsListener(HttpsListenerConfig {
3630 address: SocketAddress::new_v4(0, 0, 0, 0, 8443),
3631 ..Default::default()
3632 })
3633 .into(),
3634 )
3635 .expect("Could not execute request");
3636 state
3637 .dispatch(
3638 &RequestType::ActivateListener(ActivateListener {
3639 address: SocketAddress::new_v4(0, 0, 0, 0, 8443),
3640 proxy: ListenerType::Https.into(),
3641 from_scm: false,
3642 })
3643 .into(),
3644 )
3645 .expect("Could not execute request");
3646
3647 let mut state2: ConfigState = Default::default();
3648 state2
3649 .dispatch(
3650 &RequestType::AddTcpListener(TcpListenerConfig {
3651 address: SocketAddress::new_v4(0, 0, 0, 0, 1234),
3652 expect_proxy: true,
3653 ..Default::default()
3654 })
3655 .into(),
3656 )
3657 .expect("Could not execute request");
3658 state2
3659 .dispatch(
3660 &RequestType::AddHttpListener(HttpListenerConfig {
3661 address: SocketAddress::new_v4(0, 0, 0, 0, 8080),
3662 http_answers: custom_http_answers.clone(),
3663 ..Default::default()
3664 })
3665 .into(),
3666 )
3667 .expect("Could not execute request");
3668 state2
3669 .dispatch(
3670 &RequestType::ActivateListener(ActivateListener {
3671 address: SocketAddress::new_v4(0, 0, 0, 0, 8080),
3672 proxy: ListenerType::Http.into(),
3673 from_scm: false,
3674 })
3675 .into(),
3676 )
3677 .expect("Could not execute request");
3678 state2
3679 .dispatch(
3680 &RequestType::AddHttpsListener(HttpsListenerConfig {
3681 address: SocketAddress::new_v4(0, 0, 0, 0, 8443),
3682 http_answers: custom_http_answers.clone(),
3683 ..Default::default()
3684 })
3685 .into(),
3686 )
3687 .expect("Could not execute request");
3688 state2
3689 .dispatch(
3690 &RequestType::ActivateListener(ActivateListener {
3691 address: SocketAddress::new_v4(0, 0, 0, 0, 8443),
3692 proxy: ListenerType::Https.into(),
3693 from_scm: false,
3694 })
3695 .into(),
3696 )
3697 .expect("Could not execute request");
3698
3699 let e: Vec<Request> = vec![
3700 RequestType::RemoveListener(RemoveListener {
3701 address: SocketAddress::new_v4(0, 0, 0, 0, 1234),
3702 proxy: ListenerType::Tcp.into(),
3703 })
3704 .into(),
3705 RequestType::AddTcpListener(TcpListenerConfig {
3706 address: SocketAddress::new_v4(0, 0, 0, 0, 1234),
3707 expect_proxy: true,
3708 ..Default::default()
3709 })
3710 .into(),
3711 RequestType::DeactivateListener(DeactivateListener {
3712 address: SocketAddress::new_v4(0, 0, 0, 0, 1234),
3713 proxy: ListenerType::Tcp.into(),
3714 to_scm: false,
3715 })
3716 .into(),
3717 RequestType::RemoveListener(RemoveListener {
3718 address: SocketAddress::new_v4(0, 0, 0, 0, 8080),
3719 proxy: ListenerType::Http.into(),
3720 })
3721 .into(),
3722 RequestType::AddHttpListener(HttpListenerConfig {
3723 address: SocketAddress::new_v4(0, 0, 0, 0, 8080),
3724 http_answers: custom_http_answers.clone(),
3725 ..Default::default()
3726 })
3727 .into(),
3728 RequestType::ActivateListener(ActivateListener {
3729 address: SocketAddress::new_v4(0, 0, 0, 0, 8080),
3730 proxy: ListenerType::Http.into(),
3731 from_scm: false,
3732 })
3733 .into(),
3734 RequestType::RemoveListener(RemoveListener {
3735 address: SocketAddress::new_v4(0, 0, 0, 0, 8443),
3736 proxy: ListenerType::Https.into(),
3737 })
3738 .into(),
3739 RequestType::AddHttpsListener(HttpsListenerConfig {
3740 address: SocketAddress::new_v4(0, 0, 0, 0, 8443),
3741 http_answers: custom_http_answers.clone(),
3742 ..Default::default()
3743 })
3744 .into(),
3745 RequestType::ActivateListener(ActivateListener {
3751 address: SocketAddress::new_v4(0, 0, 0, 0, 8443),
3752 proxy: ListenerType::Https.into(),
3753 from_scm: false,
3754 })
3755 .into(),
3756 ];
3757
3758 let diff = state.diff(&state2);
3759 println!("expected diff requests:\n{e:#?}\n");
3761 println!("diff requests:\n{diff:#?}\n");
3762
3763 let _hash1 = state.hash_state();
3764 let _hash2 = state2.hash_state();
3765
3766 assert_eq!(diff, e);
3767
3768 let mut replayed = state.clone();
3775 for request in &diff {
3776 replayed
3777 .dispatch(request)
3778 .expect("every diff request must replay cleanly onto the source state");
3779 }
3780 assert_eq!(
3781 replayed.tcp_listeners, state2.tcp_listeners,
3782 "replayed tcp_listeners must match the target state"
3783 );
3784 assert_eq!(
3785 replayed.http_listeners, state2.http_listeners,
3786 "replayed http_listeners must match the target state"
3787 );
3788 assert_eq!(
3789 replayed.https_listeners, state2.https_listeners,
3790 "replayed https_listeners must match the target state"
3791 );
3792 let replayed_8443 = replayed
3795 .https_listeners
3796 .get(&SocketAddr::from(SocketAddress::new_v4(0, 0, 0, 0, 8443)))
3797 .expect("8443 HTTPS listener must exist after replay");
3798 assert!(
3799 replayed_8443.active,
3800 "the 8443 HTTPS listener must stay ACTIVE across a config change"
3801 );
3802 }
3803
3804 #[test]
3805 fn certificate_retrieval() {
3806 let mut state: ConfigState = Default::default();
3807 let certificate_and_key = CertificateAndKey {
3808 certificate: String::from(include_str!("../assets/certificate.pem")),
3809 key: String::from(include_str!("../assets/key.pem")),
3810 certificate_chain: vec![],
3811 versions: vec![],
3812 names: vec!["lolcatho.st".to_string()],
3813 };
3814 let add_certificate = AddCertificate {
3815 address: SocketAddress::new_v4(127, 0, 0, 1, 8080),
3816 certificate: certificate_and_key,
3817 expired_at: None,
3818 };
3819 state
3820 .dispatch(&RequestType::AddCertificate(add_certificate).into())
3821 .expect("Could not add certificate");
3822
3823 println!("state: {state:#?}");
3824
3825 let certificates_found_by_fingerprint = state.get_certificates(QueryCertificatesFilters {
3831 domain: None,
3832 fingerprint: Some(
3833 "ab2618b674e15243fd02a5618c66509e4840ba60e7d64cebec84cdbfeceee0c5".to_string(),
3834 ),
3835 });
3836
3837 println!("found certificate: {certificates_found_by_fingerprint:#?}");
3838
3839 assert!(!certificates_found_by_fingerprint.is_empty());
3840
3841 let certificate_found_by_domain_name = state.get_certificates(QueryCertificatesFilters {
3842 domain: Some("lolcatho.st".to_string()),
3843 fingerprint: None,
3844 });
3845
3846 assert!(!certificate_found_by_domain_name.is_empty());
3847 }
3848
3849 #[test]
3850 fn count_backends_across_clusters() {
3851 let mut state: ConfigState = Default::default();
3852
3853 assert_eq!(state.count_backends(), 0);
3854
3855 state
3856 .dispatch(
3857 &RequestType::AddBackend(AddBackend {
3858 cluster_id: String::from("cluster_1"),
3859 backend_id: String::from("cluster_1-0"),
3860 address: SocketAddress::new_v4(127, 0, 0, 1, 1026),
3861 ..Default::default()
3862 })
3863 .into(),
3864 )
3865 .expect("Could not execute request");
3866 assert_eq!(state.count_backends(), 1);
3867
3868 state
3869 .dispatch(
3870 &RequestType::AddBackend(AddBackend {
3871 cluster_id: String::from("cluster_1"),
3872 backend_id: String::from("cluster_1-1"),
3873 address: SocketAddress::new_v4(127, 0, 0, 1, 1027),
3874 ..Default::default()
3875 })
3876 .into(),
3877 )
3878 .expect("Could not execute request");
3879 assert_eq!(state.count_backends(), 2);
3880
3881 state
3883 .dispatch(
3884 &RequestType::AddBackend(AddBackend {
3885 cluster_id: String::from("cluster_2"),
3886 backend_id: String::from("cluster_2-0"),
3887 address: SocketAddress::new_v4(192, 168, 1, 1, 8080),
3888 ..Default::default()
3889 })
3890 .into(),
3891 )
3892 .expect("Could not execute request");
3893 assert_eq!(state.count_backends(), 3);
3894
3895 state
3897 .dispatch(
3898 &RequestType::RemoveBackend(RemoveBackend {
3899 cluster_id: String::from("cluster_1"),
3900 backend_id: String::from("cluster_1-0"),
3901 address: SocketAddress::new_v4(127, 0, 0, 1, 1026),
3902 })
3903 .into(),
3904 )
3905 .expect("Could not execute request");
3906 assert_eq!(state.count_backends(), 2);
3907 }
3908
3909 #[test]
3910 fn count_frontends_across_types() {
3911 let mut state: ConfigState = Default::default();
3912
3913 assert_eq!(state.count_frontends(), 0);
3914
3915 state
3917 .dispatch(
3918 &RequestType::AddHttpFrontend(RequestHttpFrontend {
3919 cluster_id: Some(String::from("cluster_1")),
3920 hostname: String::from("example.com"),
3921 path: PathRule::prefix(String::from("/")),
3922 address: SocketAddress::new_v4(0, 0, 0, 0, 8080),
3923 position: RulePosition::Tree.into(),
3924 ..Default::default()
3925 })
3926 .into(),
3927 )
3928 .expect("Could not execute request");
3929 assert_eq!(state.count_frontends(), 1);
3930
3931 state
3933 .dispatch(
3934 &RequestType::AddHttpsFrontend(RequestHttpFrontend {
3935 cluster_id: Some(String::from("cluster_1")),
3936 hostname: String::from("secure.example.com"),
3937 path: PathRule::prefix(String::from("/")),
3938 address: SocketAddress::new_v4(0, 0, 0, 0, 8443),
3939 position: RulePosition::Tree.into(),
3940 ..Default::default()
3941 })
3942 .into(),
3943 )
3944 .expect("Could not execute request");
3945 assert_eq!(state.count_frontends(), 2);
3946
3947 state
3949 .dispatch(
3950 &RequestType::AddTcpFrontend(RequestTcpFrontend {
3951 cluster_id: String::from("cluster_2"),
3952 address: SocketAddress::new_v4(0, 0, 0, 0, 5432),
3953 ..Default::default()
3954 })
3955 .into(),
3956 )
3957 .expect("Could not execute request");
3958 assert_eq!(state.count_frontends(), 3);
3959
3960 state
3962 .dispatch(
3963 &RequestType::AddTcpFrontend(RequestTcpFrontend {
3964 cluster_id: String::from("cluster_2"),
3965 address: SocketAddress::new_v4(0, 0, 0, 0, 5433),
3966 ..Default::default()
3967 })
3968 .into(),
3969 )
3970 .expect("Could not execute request");
3971 assert_eq!(state.count_frontends(), 4);
3972
3973 state
3975 .dispatch(
3976 &RequestType::RemoveHttpFrontend(RequestHttpFrontend {
3977 cluster_id: Some(String::from("cluster_1")),
3978 hostname: String::from("example.com"),
3979 path: PathRule::prefix(String::from("/")),
3980 address: SocketAddress::new_v4(0, 0, 0, 0, 8080),
3981 position: RulePosition::Tree.into(),
3982 ..Default::default()
3983 })
3984 .into(),
3985 )
3986 .expect("Could not execute request");
3987 assert_eq!(state.count_frontends(), 3);
3988 }
3989
3990 fn make_https_listener(address: SocketAddress) -> HttpsListenerConfig {
3993 HttpsListenerConfig {
3994 address,
3995 sticky_name: "SOZUBALANCEID".to_owned(),
3996 front_timeout: 60,
3997 back_timeout: 30,
3998 connect_timeout: 3,
3999 request_timeout: 10,
4000 ..Default::default()
4001 }
4002 }
4003
4004 fn make_http_listener(address: SocketAddress) -> HttpListenerConfig {
4005 HttpListenerConfig {
4006 address,
4007 sticky_name: "SOZUBALANCEID".to_owned(),
4008 front_timeout: 60,
4009 back_timeout: 30,
4010 connect_timeout: 3,
4011 request_timeout: 10,
4012 ..Default::default()
4013 }
4014 }
4015
4016 fn make_tcp_listener(address: SocketAddress) -> TcpListenerConfig {
4017 TcpListenerConfig {
4018 address,
4019 front_timeout: 60,
4020 back_timeout: 30,
4021 connect_timeout: 3,
4022 ..Default::default()
4023 }
4024 }
4025
4026 fn make_udp_listener(address: SocketAddress, active: bool) -> UdpListenerConfig {
4027 UdpListenerConfig {
4028 address,
4029 public_address: None,
4030 front_timeout: 30,
4031 back_timeout: 30,
4032 max_rx_datagram_size: 1500,
4033 max_flows: 0,
4034 active,
4035 }
4036 }
4037
4038 #[test]
4048 fn test_udp_state_roundtrip() {
4049 let address = SocketAddress::new_v4(127, 0, 0, 1, 5353);
4050
4051 let mut state = ConfigState::default();
4052 state
4053 .dispatch(&RequestType::AddUdpListener(make_udp_listener(address, true)).into())
4054 .expect("could not add udp listener");
4055 state
4056 .dispatch(
4057 &RequestType::ActivateListener(ActivateListener {
4058 address,
4059 proxy: ListenerType::Udp.into(),
4060 from_scm: false,
4061 })
4062 .into(),
4063 )
4064 .expect("could not activate udp listener");
4065 state
4066 .dispatch(
4067 &RequestType::AddUdpFrontend(RequestUdpFrontend {
4068 cluster_id: "udp_cluster".to_string(),
4069 address,
4070 tags: BTreeMap::from([("owner".to_string(), "team".to_string())]),
4071 })
4072 .into(),
4073 )
4074 .expect("could not add udp frontend");
4075
4076 assert_eq!(state.udp_listeners.len(), 1);
4077 assert!(state.udp_listeners[&address.into()].active);
4078 assert_eq!(
4079 state.udp_fronts.get("udp_cluster").map(Vec::len),
4080 Some(1usize)
4081 );
4082
4083 let logical = |s: &ConfigState| {
4088 let mut c = s.clone();
4089 c.request_counts.clear();
4090 c
4091 };
4092
4093 let mut replayed = ConfigState::default();
4095 for request in state.generate_requests() {
4096 replayed
4097 .dispatch(&request)
4098 .expect("could not replay generated request");
4099 }
4100 assert_eq!(
4101 logical(&state),
4102 logical(&replayed),
4103 "UDP listener + frontend must survive generate_requests → replay"
4104 );
4105
4106 let empty = ConfigState::default();
4108 let mut from_diff = ConfigState::default();
4109 for request in empty.diff(&state) {
4110 from_diff
4111 .dispatch(&request)
4112 .expect("could not replay diff request");
4113 }
4114 assert_eq!(
4115 logical(&state),
4116 logical(&from_diff),
4117 "diff(empty -> state) must reconstruct the UDP listener + frontend"
4118 );
4119
4120 let mut torn_down = state.clone();
4122 for request in state.diff(&empty) {
4123 torn_down
4124 .dispatch(&request)
4125 .expect("could not replay teardown diff request");
4126 }
4127 assert!(
4128 torn_down.udp_listeners.is_empty(),
4129 "diff(state -> empty) must remove the UDP listener"
4130 );
4131 assert!(
4132 torn_down
4133 .udp_fronts
4134 .get("udp_cluster")
4135 .map(Vec::is_empty)
4136 .unwrap_or(true),
4137 "diff(state -> empty) must remove the UDP frontend"
4138 );
4139
4140 state
4142 .dispatch(
4143 &RequestType::UpdateUdpListener(UpdateUdpListenerConfig {
4144 address,
4145 max_flows: Some(4096),
4146 front_timeout: Some(15),
4147 ..Default::default()
4148 })
4149 .into(),
4150 )
4151 .expect("could not update udp listener");
4152 let updated = &state.udp_listeners[&address.into()];
4153 assert_eq!(updated.max_flows, 4096);
4154 assert_eq!(updated.front_timeout, 15);
4155 assert_eq!(
4156 updated.back_timeout, 30,
4157 "unpatched field must be preserved"
4158 );
4159 }
4160
4161 #[test]
4176 fn test_tcp_sni_alpn_state_roundtrip() {
4177 let address = SocketAddress::new_v4(127, 0, 0, 1, 6443);
4178
4179 let mut state = ConfigState::default();
4180 state
4181 .dispatch(
4182 &RequestType::AddTcpListener(TcpListenerConfig {
4183 address,
4184 front_timeout: 60,
4185 back_timeout: 30,
4186 connect_timeout: 3,
4187 active: true,
4188 sni_preread_timeout: Some(2),
4189 sni_preread_max_bytes: Some(8192),
4190 ..Default::default()
4191 })
4192 .into(),
4193 )
4194 .expect("could not add tcp listener");
4195 state
4196 .dispatch(
4197 &RequestType::ActivateListener(ActivateListener {
4198 address,
4199 proxy: ListenerType::Tcp.into(),
4200 from_scm: false,
4201 })
4202 .into(),
4203 )
4204 .expect("could not activate tcp listener");
4205
4206 let frontend_a = RequestTcpFrontend {
4207 cluster_id: "cluster_sni_a".to_string(),
4208 address,
4209 tags: BTreeMap::new(),
4210 sni: Some("example.com".to_string()),
4211 alpn: vec!["h2".to_string()],
4212 };
4213 let frontend_b = RequestTcpFrontend {
4214 cluster_id: "cluster_sni_b".to_string(),
4215 address,
4216 tags: BTreeMap::new(),
4217 sni: Some("other.example.com".to_string()),
4218 alpn: vec![],
4219 };
4220 state
4221 .dispatch(&RequestType::AddTcpFrontend(frontend_a.clone()).into())
4222 .expect("could not add sni-a tcp frontend");
4223 state
4224 .dispatch(&RequestType::AddTcpFrontend(frontend_b.clone()).into())
4225 .expect("could not add sni-b tcp frontend");
4226
4227 assert_eq!(state.tcp_listeners.len(), 1);
4228 assert!(state.tcp_listeners[&address.into()].active);
4229 assert_eq!(
4230 state.tcp_listeners[&address.into()].sni_preread_timeout,
4231 Some(2)
4232 );
4233 assert_eq!(
4234 state.tcp_listeners[&address.into()].sni_preread_max_bytes,
4235 Some(8192)
4236 );
4237 assert_eq!(state.count_tcp_frontends_raw(), 2);
4238
4239 let logical = |s: &ConfigState| {
4244 let mut c = s.clone();
4245 c.request_counts.clear();
4246 c
4247 };
4248
4249 let mut replayed = ConfigState::default();
4251 for request in state.generate_requests() {
4252 replayed
4253 .dispatch(&request)
4254 .expect("could not replay generated request");
4255 }
4256 assert_eq!(
4257 logical(&state),
4258 logical(&replayed),
4259 "SNI/ALPN TCP listener + frontends must survive generate_requests → replay"
4260 );
4261
4262 let empty = ConfigState::default();
4264 let mut from_diff = ConfigState::default();
4265 for request in empty.diff(&state) {
4266 from_diff
4267 .dispatch(&request)
4268 .expect("could not replay diff request");
4269 }
4270 assert_eq!(
4271 logical(&state),
4272 logical(&from_diff),
4273 "diff(empty -> state) must reconstruct the tcp listener + both sni frontends"
4274 );
4275
4276 let mut torn_down = state.clone();
4278 for request in state.diff(&empty) {
4279 torn_down
4280 .dispatch(&request)
4281 .expect("could not replay teardown diff request");
4282 }
4283 assert!(
4284 torn_down.tcp_listeners.is_empty(),
4285 "diff(state -> empty) must remove the tcp listener"
4286 );
4287 assert_eq!(
4288 torn_down.count_tcp_frontends_raw(),
4289 0,
4290 "diff(state -> empty) must remove both sni frontends"
4291 );
4292
4293 let mismatched_removal = state.dispatch(
4297 &RequestType::RemoveTcpFrontend(RequestTcpFrontend {
4298 cluster_id: "cluster_sni_a".to_string(),
4299 address,
4300 tags: BTreeMap::new(),
4301 sni: None,
4302 alpn: vec![],
4303 })
4304 .into(),
4305 );
4306 assert!(
4307 mismatched_removal.is_err(),
4308 "removing by address alone (no matching sni/alpn) must be rejected"
4309 );
4310 assert_eq!(
4311 state.count_tcp_frontends_raw(),
4312 2,
4313 "a rejected mismatched removal must not change either sibling"
4314 );
4315
4316 state
4319 .dispatch(&RequestType::RemoveTcpFrontend(frontend_a).into())
4320 .expect("could not remove sni-a tcp frontend");
4321 assert_eq!(state.count_tcp_frontends_raw(), 1);
4322 let remaining = state
4323 .tcp_fronts
4324 .get("cluster_sni_b")
4325 .expect("cluster_sni_b bucket must survive");
4326 assert_eq!(remaining.len(), 1);
4327 assert_eq!(remaining[0].sni, Some("other.example.com".to_string()));
4328 }
4329
4330 fn add_tcp_test_cluster(state: &mut ConfigState, cluster_id: &str) {
4341 state
4342 .dispatch(
4343 &RequestType::AddCluster(Cluster {
4344 cluster_id: cluster_id.to_string(),
4345 sticky_session: false,
4346 https_redirect: false,
4347 ..Default::default()
4348 })
4349 .into(),
4350 )
4351 .expect("could not add cluster");
4352 }
4353
4354 #[test]
4359 fn add_tcp_frontend_rejects_cross_cluster_duplicate_identity() {
4360 let address = SocketAddress::new_v4(127, 0, 0, 1, 9001);
4361 let mut state = ConfigState::new();
4362 add_tcp_test_cluster(&mut state, "cluster_x");
4363 add_tcp_test_cluster(&mut state, "cluster_y");
4364
4365 state
4366 .dispatch(
4367 &RequestType::AddTcpFrontend(RequestTcpFrontend {
4368 cluster_id: "cluster_x".to_string(),
4369 address,
4370 tags: BTreeMap::new(),
4371 sni: Some("example.com".to_string()),
4372 alpn: vec!["h2".to_string()],
4373 })
4374 .into(),
4375 )
4376 .expect("could not add cluster_x tcp frontend");
4377
4378 let hash_before = state.hash_state();
4379 let count_before = state.count_tcp_frontends_raw();
4380
4381 let result = state.dispatch(
4382 &RequestType::AddTcpFrontend(RequestTcpFrontend {
4383 cluster_id: "cluster_y".to_string(),
4384 address,
4385 tags: BTreeMap::new(),
4386 sni: Some("example.com".to_string()),
4387 alpn: vec!["h2".to_string()],
4388 })
4389 .into(),
4390 );
4391 assert!(
4392 result.is_err(),
4393 "the same (address, sni, alpn) under a different cluster_id must be rejected, \
4394 got: {result:?}"
4395 );
4396 assert_eq!(
4397 state.hash_state(),
4398 hash_before,
4399 "a rejected cross-cluster duplicate must not change the state hash"
4400 );
4401 assert_eq!(
4402 state.count_tcp_frontends_raw(),
4403 count_before,
4404 "a rejected cross-cluster duplicate must not change the frontend count"
4405 );
4406 assert_eq!(state.tcp_fronts.get("cluster_x").map(Vec::len), Some(1));
4407 assert_eq!(
4408 state.tcp_fronts.get("cluster_y").map(Vec::len).unwrap_or(0),
4409 0
4410 );
4411 }
4412
4413 #[test]
4416 fn add_tcp_frontend_rejects_reordered_alpn_duplicate() {
4417 let address = SocketAddress::new_v4(127, 0, 0, 1, 9002);
4418 let mut state = ConfigState::new();
4419 add_tcp_test_cluster(&mut state, "cluster_reorder");
4420
4421 state
4422 .dispatch(
4423 &RequestType::AddTcpFrontend(RequestTcpFrontend {
4424 cluster_id: "cluster_reorder".to_string(),
4425 address,
4426 tags: BTreeMap::new(),
4427 sni: Some("example.com".to_string()),
4428 alpn: vec!["h2".to_string(), "http/1.1".to_string()],
4429 })
4430 .into(),
4431 )
4432 .expect("could not add first tcp frontend");
4433
4434 let hash_before = state.hash_state();
4435 let count_before = state.count_tcp_frontends_raw();
4436
4437 let result = state.dispatch(
4438 &RequestType::AddTcpFrontend(RequestTcpFrontend {
4439 cluster_id: "cluster_reorder".to_string(),
4440 address,
4441 tags: BTreeMap::new(),
4442 sni: Some("example.com".to_string()),
4443 alpn: vec!["http/1.1".to_string(), "h2".to_string()],
4444 })
4445 .into(),
4446 );
4447 assert!(
4448 result.is_err(),
4449 "the same ALPN set in a different order must be rejected as a duplicate \
4450 identity, got: {result:?}"
4451 );
4452 assert_eq!(state.hash_state(), hash_before);
4453 assert_eq!(state.count_tcp_frontends_raw(), count_before);
4454 assert_eq!(
4455 state.tcp_fronts.get("cluster_reorder").map(Vec::len),
4456 Some(1)
4457 );
4458 }
4459
4460 #[test]
4464 fn remove_tcp_frontend_matches_reordered_alpn() {
4465 let address = SocketAddress::new_v4(127, 0, 0, 1, 9003);
4466 let mut state = ConfigState::new();
4467 add_tcp_test_cluster(&mut state, "cluster_remove_reorder");
4468
4469 state
4470 .dispatch(
4471 &RequestType::AddTcpFrontend(RequestTcpFrontend {
4472 cluster_id: "cluster_remove_reorder".to_string(),
4473 address,
4474 tags: BTreeMap::new(),
4475 sni: Some("example.com".to_string()),
4476 alpn: vec!["h2".to_string(), "http/1.1".to_string()],
4477 })
4478 .into(),
4479 )
4480 .expect("could not add tcp frontend");
4481
4482 let result = state.dispatch(
4483 &RequestType::RemoveTcpFrontend(RequestTcpFrontend {
4484 cluster_id: "cluster_remove_reorder".to_string(),
4485 address,
4486 tags: BTreeMap::new(),
4487 sni: Some("example.com".to_string()),
4488 alpn: vec!["http/1.1".to_string(), "h2".to_string()],
4489 })
4490 .into(),
4491 );
4492 assert!(
4493 result.is_ok(),
4494 "removing with the same ALPN set in a different order must succeed, got: {result:?}"
4495 );
4496 assert_eq!(state.count_tcp_frontends_raw(), 0);
4497 }
4498
4499 #[test]
4503 fn add_tcp_frontend_rejects_alpn_overlap_across_clusters_same_sni() {
4504 let address = SocketAddress::new_v4(127, 0, 0, 1, 9004);
4505 let mut state = ConfigState::new();
4506 add_tcp_test_cluster(&mut state, "cluster_overlap_a");
4507 add_tcp_test_cluster(&mut state, "cluster_overlap_b");
4508
4509 state
4510 .dispatch(
4511 &RequestType::AddTcpFrontend(RequestTcpFrontend {
4512 cluster_id: "cluster_overlap_a".to_string(),
4513 address,
4514 tags: BTreeMap::new(),
4515 sni: Some("example.com".to_string()),
4516 alpn: vec!["h2".to_string()],
4517 })
4518 .into(),
4519 )
4520 .expect("could not add cluster_overlap_a tcp frontend");
4521
4522 let hash_before = state.hash_state();
4523 let count_before = state.count_tcp_frontends_raw();
4524
4525 let result = state.dispatch(
4526 &RequestType::AddTcpFrontend(RequestTcpFrontend {
4527 cluster_id: "cluster_overlap_b".to_string(),
4528 address,
4529 tags: BTreeMap::new(),
4530 sni: Some("example.com".to_string()),
4531 alpn: vec!["h2".to_string(), "http/1.1".to_string()],
4532 })
4533 .into(),
4534 );
4535 assert!(
4536 result.is_err(),
4537 "an overlapping ALPN set on the same (address, sni) under a different cluster_id \
4538 must be rejected, got: {result:?}"
4539 );
4540 assert_eq!(state.hash_state(), hash_before);
4541 assert_eq!(state.count_tcp_frontends_raw(), count_before);
4542 assert_eq!(
4543 state
4544 .tcp_fronts
4545 .get("cluster_overlap_b")
4546 .map(Vec::len)
4547 .unwrap_or(0),
4548 0
4549 );
4550 }
4551
4552 #[test]
4555 fn add_tcp_frontend_rejects_no_sni_sni_mixing_both_directions() {
4556 let address_a = SocketAddress::new_v4(127, 0, 0, 1, 9005);
4557 let address_b = SocketAddress::new_v4(127, 0, 0, 1, 9006);
4558 let mut state = ConfigState::new();
4559 add_tcp_test_cluster(&mut state, "cluster_mix_1");
4560 add_tcp_test_cluster(&mut state, "cluster_mix_2");
4561 add_tcp_test_cluster(&mut state, "cluster_mix_3");
4562 add_tcp_test_cluster(&mut state, "cluster_mix_4");
4563
4564 state
4567 .dispatch(
4568 &RequestType::AddTcpFrontend(RequestTcpFrontend {
4569 cluster_id: "cluster_mix_1".to_string(),
4570 address: address_a,
4571 tags: BTreeMap::new(),
4572 sni: None,
4573 alpn: vec![],
4574 })
4575 .into(),
4576 )
4577 .expect("could not add no-sni tcp frontend");
4578 let hash_before_a = state.hash_state();
4579 let count_before_a = state.count_tcp_frontends_raw();
4580 let result_a = state.dispatch(
4581 &RequestType::AddTcpFrontend(RequestTcpFrontend {
4582 cluster_id: "cluster_mix_2".to_string(),
4583 address: address_a,
4584 tags: BTreeMap::new(),
4585 sni: Some("example.com".to_string()),
4586 alpn: vec![],
4587 })
4588 .into(),
4589 );
4590 assert!(
4591 result_a.is_err(),
4592 "an SNI-scoped frontend must not be added to a listener with an existing no-SNI \
4593 front, got: {result_a:?}"
4594 );
4595 assert_eq!(state.hash_state(), hash_before_a);
4596 assert_eq!(state.count_tcp_frontends_raw(), count_before_a);
4597
4598 state
4601 .dispatch(
4602 &RequestType::AddTcpFrontend(RequestTcpFrontend {
4603 cluster_id: "cluster_mix_3".to_string(),
4604 address: address_b,
4605 tags: BTreeMap::new(),
4606 sni: Some("example.com".to_string()),
4607 alpn: vec![],
4608 })
4609 .into(),
4610 )
4611 .expect("could not add sni-scoped tcp frontend");
4612 let hash_before_b = state.hash_state();
4613 let count_before_b = state.count_tcp_frontends_raw();
4614 let result_b = state.dispatch(
4615 &RequestType::AddTcpFrontend(RequestTcpFrontend {
4616 cluster_id: "cluster_mix_4".to_string(),
4617 address: address_b,
4618 tags: BTreeMap::new(),
4619 sni: None,
4620 alpn: vec![],
4621 })
4622 .into(),
4623 );
4624 assert!(
4625 result_b.is_err(),
4626 "a no-SNI frontend must not be added to a listener with existing SNI-scoped \
4627 fronts, got: {result_b:?}"
4628 );
4629 assert_eq!(state.hash_state(), hash_before_b);
4630 assert_eq!(state.count_tcp_frontends_raw(), count_before_b);
4631 }
4632
4633 #[test]
4638 fn add_tcp_frontend_rejects_malformed_sni_shape() {
4639 let address = SocketAddress::new_v4(127, 0, 0, 1, 9007);
4640 let mut state = ConfigState::new();
4641 add_tcp_test_cluster(&mut state, "cluster_malformed");
4642
4643 let hash_before = state.hash_state();
4644 let count_before = state.count_tcp_frontends_raw();
4645
4646 let result = state.dispatch(
4647 &RequestType::AddTcpFrontend(RequestTcpFrontend {
4648 cluster_id: "cluster_malformed".to_string(),
4649 address,
4650 tags: BTreeMap::new(),
4651 sni: Some(".example.com".to_string()),
4653 alpn: vec![],
4654 })
4655 .into(),
4656 );
4657 assert!(
4658 result.is_err(),
4659 "a malformed SNI pattern must be rejected by the master, not merely by \
4660 config-load, got: {result:?}"
4661 );
4662 assert_eq!(state.hash_state(), hash_before);
4663 assert_eq!(state.count_tcp_frontends_raw(), count_before);
4664 assert_eq!(
4665 state
4666 .tcp_fronts
4667 .get("cluster_malformed")
4668 .map(Vec::len)
4669 .unwrap_or(0),
4670 0
4671 );
4672 }
4673
4674 #[test]
4680 fn add_tcp_frontend_rejects_alpn_without_sni() {
4681 let address = SocketAddress::new_v4(127, 0, 0, 1, 9009);
4682 let mut state = ConfigState::new();
4683 add_tcp_test_cluster(&mut state, "cluster_alpn_no_sni");
4684
4685 let hash_before = state.hash_state();
4686 let count_before = state.count_tcp_frontends_raw();
4687
4688 let result = state.dispatch(
4689 &RequestType::AddTcpFrontend(RequestTcpFrontend {
4690 cluster_id: "cluster_alpn_no_sni".to_string(),
4691 address,
4692 tags: BTreeMap::new(),
4693 sni: None,
4694 alpn: vec!["h2".to_string()],
4695 })
4696 .into(),
4697 );
4698 assert!(
4699 result.is_err(),
4700 "a non-empty alpn without sni must be rejected by the master, got: {result:?}"
4701 );
4702 assert_eq!(
4703 state.hash_state(),
4704 hash_before,
4705 "a rejected alpn-without-sni add must not change the state hash"
4706 );
4707 assert_eq!(
4708 state.count_tcp_frontends_raw(),
4709 count_before,
4710 "a rejected alpn-without-sni add must not change the frontend count"
4711 );
4712 assert_eq!(
4713 state
4714 .tcp_fronts
4715 .get("cluster_alpn_no_sni")
4716 .map(Vec::len)
4717 .unwrap_or(0),
4718 0
4719 );
4720 }
4721
4722 #[test]
4728 fn add_tcp_frontend_accepts_exact_and_wildcard_catch_all_siblings() {
4729 let address = SocketAddress::new_v4(127, 0, 0, 1, 9008);
4730 let mut state = ConfigState::new();
4731 add_tcp_test_cluster(&mut state, "cluster_exact_catchall");
4732 add_tcp_test_cluster(&mut state, "cluster_wildcard_catchall");
4733
4734 state
4735 .dispatch(
4736 &RequestType::AddTcpFrontend(RequestTcpFrontend {
4737 cluster_id: "cluster_exact_catchall".to_string(),
4738 address,
4739 tags: BTreeMap::new(),
4740 sni: Some("example.com".to_string()),
4741 alpn: vec![],
4742 })
4743 .into(),
4744 )
4745 .expect("could not add exact catch-all tcp frontend");
4746
4747 let result = state.dispatch(
4748 &RequestType::AddTcpFrontend(RequestTcpFrontend {
4749 cluster_id: "cluster_wildcard_catchall".to_string(),
4750 address,
4751 tags: BTreeMap::new(),
4752 sni: Some("*.example.com".to_string()),
4753 alpn: vec![],
4754 })
4755 .into(),
4756 );
4757 assert!(
4758 result.is_ok(),
4759 "an exact catch-all and a wildcard catch-all on the same address must both be \
4760 accepted, got: {result:?}"
4761 );
4762 assert_eq!(state.count_tcp_frontends_raw(), 2);
4763 }
4764
4765 #[test]
4769 fn list_frontends_includes_udp() {
4770 let tcp_addr = SocketAddress::new_v4(0, 0, 0, 0, 6379);
4771 let udp_addr = SocketAddress::new_v4(0, 0, 0, 0, 5353);
4772
4773 let mut state = ConfigState::default();
4774 state
4775 .dispatch(
4776 &RequestType::AddTcpFrontend(RequestTcpFrontend {
4777 cluster_id: "tcp_cluster".to_string(),
4778 address: tcp_addr,
4779 ..Default::default()
4780 })
4781 .into(),
4782 )
4783 .expect("could not add tcp frontend");
4784 state
4785 .dispatch(
4786 &RequestType::AddUdpFrontend(RequestUdpFrontend {
4787 cluster_id: "udp_cluster".to_string(),
4788 address: udp_addr,
4789 ..Default::default()
4790 })
4791 .into(),
4792 )
4793 .expect("could not add udp frontend");
4794
4795 let all = state.list_frontends(FrontendFilters::default());
4797 assert_eq!(all.tcp_frontends.len(), 1, "tcp frontend must be listed");
4798 assert_eq!(
4799 all.udp_frontends.len(),
4800 1,
4801 "udp frontend must be listed under the default all-pass path"
4802 );
4803 assert_eq!(all.udp_frontends[0].cluster_id, "udp_cluster");
4804 assert_eq!(all.udp_frontends[0].address, udp_addr);
4805
4806 let tcp_only = state.list_frontends(FrontendFilters {
4808 tcp: true,
4809 ..Default::default()
4810 });
4811 assert_eq!(tcp_only.tcp_frontends.len(), 1);
4812 assert_eq!(
4813 tcp_only.udp_frontends.len(),
4814 1,
4815 "udp frontends ride the tcp filter"
4816 );
4817
4818 let http_only = state.list_frontends(FrontendFilters {
4820 http: true,
4821 ..Default::default()
4822 });
4823 assert!(http_only.tcp_frontends.is_empty());
4824 assert!(http_only.udp_frontends.is_empty());
4825
4826 let domain_filtered = state.list_frontends(FrontendFilters {
4828 domain: Some("example.com".to_string()),
4829 ..Default::default()
4830 });
4831 assert!(domain_filtered.tcp_frontends.is_empty());
4832 assert!(domain_filtered.udp_frontends.is_empty());
4833 }
4834
4835 #[test]
4840 fn update_https_listener_happy_path_h2_knobs() {
4841 let addr = SocketAddress::new_v4(0, 0, 0, 0, 8443);
4842 let mut state = ConfigState::new();
4843 state
4844 .dispatch(&RequestType::AddHttpsListener(make_https_listener(addr)).into())
4845 .unwrap();
4846
4847 let patch = UpdateHttpsListenerConfig {
4848 address: addr,
4849 h2_max_rst_stream_per_window: Some(50),
4850 h2_max_ping_per_window: Some(20),
4851 ..Default::default()
4852 };
4853 state
4854 .dispatch(&RequestType::UpdateHttpsListener(patch).into())
4855 .expect("update must succeed");
4856
4857 let listener = state
4858 .https_listeners
4859 .get(&SocketAddr::from(addr))
4860 .expect("listener must be present");
4861 assert_eq!(listener.h2_max_rst_stream_per_window, Some(50));
4862 assert_eq!(listener.h2_max_ping_per_window, Some(20));
4863 assert_eq!(listener.front_timeout, 60);
4865 assert_eq!(listener.h2_max_settings_per_window, None);
4866 }
4867
4868 #[test]
4871 fn update_https_listener_not_found() {
4872 let mut state = ConfigState::new();
4873 let patch = UpdateHttpsListenerConfig {
4874 address: SocketAddress::new_v4(1, 2, 3, 4, 9999),
4875 h2_max_rst_stream_per_window: Some(50),
4876 ..Default::default()
4877 };
4878 let err = state
4879 .dispatch(&RequestType::UpdateHttpsListener(patch).into())
4880 .unwrap_err();
4881 assert!(
4882 matches!(
4883 err,
4884 StateError::NotFound {
4885 kind: ObjectKind::HttpsListener,
4886 ..
4887 }
4888 ),
4889 "expected NotFound, got: {err}"
4890 );
4891 }
4892
4893 #[test]
4896 fn update_https_listener_noop_patch() {
4897 let addr = SocketAddress::new_v4(0, 0, 0, 0, 8443);
4898 let mut state = ConfigState::new();
4899 let original = make_https_listener(addr);
4900 state
4901 .dispatch(&RequestType::AddHttpsListener(original.clone()).into())
4902 .unwrap();
4903
4904 let patch = UpdateHttpsListenerConfig {
4905 address: addr,
4906 ..Default::default()
4907 };
4908 state
4909 .dispatch(&RequestType::UpdateHttpsListener(patch).into())
4910 .expect("no-op patch must succeed");
4911
4912 let listener = state.https_listeners.get(&SocketAddr::from(addr)).unwrap();
4913 assert_eq!(listener.front_timeout, original.front_timeout);
4914 assert_eq!(
4915 listener.h2_max_rst_stream_per_window,
4916 original.h2_max_rst_stream_per_window
4917 );
4918 }
4919
4920 #[test]
4922 fn update_https_listener_invalid_value_flood_knob_zero() {
4923 let addr = SocketAddress::new_v4(0, 0, 0, 0, 8443);
4924 let mut state = ConfigState::new();
4925 state
4926 .dispatch(&RequestType::AddHttpsListener(make_https_listener(addr)).into())
4927 .unwrap();
4928
4929 let patch = UpdateHttpsListenerConfig {
4930 address: addr,
4931 h2_max_rst_stream_per_window: Some(0),
4932 ..Default::default()
4933 };
4934 let err = state
4935 .dispatch(&RequestType::UpdateHttpsListener(patch).into())
4936 .unwrap_err();
4937 assert!(
4938 matches!(
4939 err,
4940 StateError::InvalidValue {
4941 field: "h2_max_rst_stream_per_window",
4942 ..
4943 }
4944 ),
4945 "expected InvalidValue for flood knob 0, got: {err}"
4946 );
4947 }
4948
4949 #[test]
4955 fn add_cluster_invalid_health_check_uri_rejected() {
4956 use crate::proto::command::HealthCheckConfig;
4957
4958 let mut state = ConfigState::new();
4959 let err = state
4960 .dispatch(
4961 &RequestType::AddCluster(Cluster {
4962 cluster_id: String::from("evil_cluster"),
4963 health_check: Some(HealthCheckConfig {
4964 uri: String::from("/foo\r\nGET /admin"),
4965 interval: 5_000,
4966 timeout: 1_000,
4967 healthy_threshold: 2,
4968 unhealthy_threshold: 2,
4969 ..Default::default()
4970 }),
4971 ..Default::default()
4972 })
4973 .into(),
4974 )
4975 .unwrap_err();
4976
4977 assert!(
4978 matches!(
4979 err,
4980 StateError::InvalidValue {
4981 field: "health_check",
4982 ..
4983 }
4984 ),
4985 "expected InvalidValue for CRLF-bearing health-check URI, got: {err}"
4986 );
4987 assert!(
4988 !state.clusters.contains_key("evil_cluster"),
4989 "cluster must not be inserted when health_check fails validation",
4990 );
4991 }
4992
4993 #[test]
4995 fn update_https_listener_alpn_unknown_value_rejected() {
4996 use crate::proto::command::AlpnProtocols;
4997
4998 let addr = SocketAddress::new_v4(0, 0, 0, 0, 8443);
4999 let mut state = ConfigState::new();
5000 state
5001 .dispatch(&RequestType::AddHttpsListener(make_https_listener(addr)).into())
5002 .unwrap();
5003
5004 let patch = UpdateHttpsListenerConfig {
5005 address: addr,
5006 alpn_protocols: Some(AlpnProtocols {
5007 values: vec!["h3".to_owned()],
5008 }),
5009 ..Default::default()
5010 };
5011 let err = state
5012 .dispatch(&RequestType::UpdateHttpsListener(patch).into())
5013 .unwrap_err();
5014 assert!(
5015 matches!(
5016 err,
5017 StateError::InvalidValue {
5018 field: "alpn_protocols",
5019 ..
5020 }
5021 ),
5022 "expected InvalidValue for unknown ALPN, got: {err}"
5023 );
5024 }
5025
5026 #[test]
5028 fn update_https_listener_alpn_empty_reset_accepted() {
5029 use crate::proto::command::AlpnProtocols;
5030
5031 let addr = SocketAddress::new_v4(0, 0, 0, 0, 8443);
5032 let mut state = ConfigState::new();
5033 let mut listener = make_https_listener(addr);
5034 listener.alpn_protocols = vec!["h2".to_owned()];
5035 state
5036 .dispatch(&RequestType::AddHttpsListener(listener).into())
5037 .unwrap();
5038
5039 let patch = UpdateHttpsListenerConfig {
5040 address: addr,
5041 alpn_protocols: Some(AlpnProtocols { values: vec![] }),
5042 ..Default::default()
5043 };
5044 state
5045 .dispatch(&RequestType::UpdateHttpsListener(patch).into())
5046 .expect("empty ALPN reset must succeed");
5047
5048 let listener = state.https_listeners.get(&SocketAddr::from(addr)).unwrap();
5049 assert!(
5050 listener.alpn_protocols.is_empty(),
5051 "ALPN must have been reset to empty"
5052 );
5053 }
5054
5055 #[test]
5057 fn update_https_listener_alpn_valid_values_accepted() {
5058 use crate::proto::command::AlpnProtocols;
5059
5060 let addr = SocketAddress::new_v4(0, 0, 0, 0, 8443);
5061 let mut state = ConfigState::new();
5062 state
5063 .dispatch(&RequestType::AddHttpsListener(make_https_listener(addr)).into())
5064 .unwrap();
5065
5066 let patch = UpdateHttpsListenerConfig {
5067 address: addr,
5068 alpn_protocols: Some(AlpnProtocols {
5069 values: vec!["h2".to_owned(), "http/1.1".to_owned()],
5070 }),
5071 ..Default::default()
5072 };
5073 state
5074 .dispatch(&RequestType::UpdateHttpsListener(patch).into())
5075 .expect("valid ALPN must be accepted");
5076
5077 let listener = state.https_listeners.get(&SocketAddr::from(addr)).unwrap();
5078 assert_eq!(listener.alpn_protocols, vec!["h2", "http/1.1"]);
5079 }
5080
5081 #[test]
5084 fn update_https_listener_alpn_absent_preserves_existing() {
5085 let addr = SocketAddress::new_v4(0, 0, 0, 0, 8443);
5086 let mut state = ConfigState::new();
5087 let mut listener = make_https_listener(addr);
5088 listener.alpn_protocols = vec!["h2".to_owned()];
5089 state
5090 .dispatch(&RequestType::AddHttpsListener(listener).into())
5091 .unwrap();
5092
5093 let patch = UpdateHttpsListenerConfig {
5095 address: addr,
5096 front_timeout: Some(10),
5097 ..Default::default()
5098 };
5099 state
5100 .dispatch(&RequestType::UpdateHttpsListener(patch).into())
5101 .unwrap();
5102
5103 let listener = state.https_listeners.get(&SocketAddr::from(addr)).unwrap();
5104 assert_eq!(
5105 listener.alpn_protocols,
5106 vec!["h2"],
5107 "ALPN must be preserved when not patched"
5108 );
5109 }
5110
5111 #[test]
5113 fn update_https_listener_sozu_id_header_empty_rejected() {
5114 let addr = SocketAddress::new_v4(0, 0, 0, 0, 8443);
5115 let mut state = ConfigState::new();
5116 state
5117 .dispatch(&RequestType::AddHttpsListener(make_https_listener(addr)).into())
5118 .unwrap();
5119
5120 let patch = UpdateHttpsListenerConfig {
5121 address: addr,
5122 sozu_id_header: Some(String::new()),
5123 ..Default::default()
5124 };
5125 let err = state
5126 .dispatch(&RequestType::UpdateHttpsListener(patch).into())
5127 .unwrap_err();
5128 assert!(
5129 matches!(
5130 err,
5131 StateError::InvalidValue {
5132 field: "sozu_id_header",
5133 ..
5134 }
5135 ),
5136 "expected InvalidValue for empty header name, got: {err}"
5137 );
5138 }
5139
5140 #[test]
5142 fn update_https_listener_sozu_id_header_colon_rejected() {
5143 let addr = SocketAddress::new_v4(0, 0, 0, 0, 8443);
5144 let mut state = ConfigState::new();
5145 state
5146 .dispatch(&RequestType::AddHttpsListener(make_https_listener(addr)).into())
5147 .unwrap();
5148
5149 let patch = UpdateHttpsListenerConfig {
5150 address: addr,
5151 sozu_id_header: Some("bad: value".to_owned()),
5152 ..Default::default()
5153 };
5154 let err = state
5155 .dispatch(&RequestType::UpdateHttpsListener(patch).into())
5156 .unwrap_err();
5157 assert!(
5158 matches!(
5159 err,
5160 StateError::InvalidValue {
5161 field: "sozu_id_header",
5162 ..
5163 }
5164 ),
5165 "expected InvalidValue for header name with colon, got: {err}"
5166 );
5167 }
5168
5169 #[test]
5171 fn update_https_listener_sozu_id_header_valid_accepted() {
5172 let addr = SocketAddress::new_v4(0, 0, 0, 0, 8443);
5173 let mut state = ConfigState::new();
5174 state
5175 .dispatch(&RequestType::AddHttpsListener(make_https_listener(addr)).into())
5176 .unwrap();
5177
5178 let patch = UpdateHttpsListenerConfig {
5179 address: addr,
5180 sozu_id_header: Some("X-Edge-Id".to_owned()),
5181 ..Default::default()
5182 };
5183 state
5184 .dispatch(&RequestType::UpdateHttpsListener(patch).into())
5185 .expect("valid header name must be accepted");
5186
5187 let listener = state.https_listeners.get(&SocketAddr::from(addr)).unwrap();
5188 assert_eq!(listener.sozu_id_header.as_deref(), Some("X-Edge-Id"));
5189 }
5190
5191 #[test]
5193 fn update_https_listener_graceful_shutdown_zero_allowed() {
5194 let addr = SocketAddress::new_v4(0, 0, 0, 0, 8443);
5195 let mut state = ConfigState::new();
5196 state
5197 .dispatch(&RequestType::AddHttpsListener(make_https_listener(addr)).into())
5198 .unwrap();
5199
5200 let patch = UpdateHttpsListenerConfig {
5201 address: addr,
5202 h2_graceful_shutdown_deadline_seconds: Some(0),
5203 ..Default::default()
5204 };
5205 state
5206 .dispatch(&RequestType::UpdateHttpsListener(patch).into())
5207 .expect("graceful_shutdown_deadline=0 must be allowed");
5208
5209 let listener = state.https_listeners.get(&SocketAddr::from(addr)).unwrap();
5210 assert_eq!(listener.h2_graceful_shutdown_deadline_seconds, Some(0));
5211 }
5212
5213 #[test]
5217 fn update_http_listener_happy_path() {
5218 let addr = SocketAddress::new_v4(0, 0, 0, 0, 8080);
5219 let mut state = ConfigState::new();
5220 state
5221 .dispatch(&RequestType::AddHttpListener(make_http_listener(addr)).into())
5222 .unwrap();
5223
5224 let patch = UpdateHttpListenerConfig {
5225 address: addr,
5226 front_timeout: Some(15),
5227 h2_max_rst_stream_per_window: Some(25),
5228 ..Default::default()
5229 };
5230 state
5231 .dispatch(&RequestType::UpdateHttpListener(patch).into())
5232 .expect("HTTP update must succeed");
5233
5234 let listener = state.http_listeners.get(&SocketAddr::from(addr)).unwrap();
5235 assert_eq!(listener.front_timeout, 15);
5236 assert_eq!(listener.h2_max_rst_stream_per_window, Some(25));
5237 assert_eq!(listener.back_timeout, 30);
5239 }
5240
5241 #[test]
5243 fn update_http_listener_flood_knob_zero_rejected() {
5244 let addr = SocketAddress::new_v4(0, 0, 0, 0, 8080);
5245 let mut state = ConfigState::new();
5246 state
5247 .dispatch(&RequestType::AddHttpListener(make_http_listener(addr)).into())
5248 .unwrap();
5249
5250 let patch = UpdateHttpListenerConfig {
5251 address: addr,
5252 h2_max_window_update_stream0_per_window: Some(0),
5253 ..Default::default()
5254 };
5255 let err = state
5256 .dispatch(&RequestType::UpdateHttpListener(patch).into())
5257 .unwrap_err();
5258 assert!(
5259 matches!(
5260 err,
5261 StateError::InvalidValue {
5262 field: "h2_max_window_update_stream0_per_window",
5263 ..
5264 }
5265 ),
5266 "expected InvalidValue, got: {err}"
5267 );
5268 }
5269
5270 #[test]
5274 fn update_tcp_listener_happy_path() {
5275 let addr = SocketAddress::new_v4(0, 0, 0, 0, 9000);
5276 let mut state = ConfigState::new();
5277 state
5278 .dispatch(&RequestType::AddTcpListener(make_tcp_listener(addr)).into())
5279 .unwrap();
5280
5281 let patch = UpdateTcpListenerConfig {
5282 address: addr,
5283 front_timeout: Some(5),
5284 ..Default::default()
5285 };
5286 state
5287 .dispatch(&RequestType::UpdateTcpListener(patch).into())
5288 .expect("TCP update must succeed");
5289
5290 let listener = state.tcp_listeners.get(&SocketAddr::from(addr)).unwrap();
5291 assert_eq!(listener.front_timeout, 5);
5292 assert_eq!(listener.back_timeout, 30); }
5294
5295 #[test]
5297 fn update_tcp_listener_not_found() {
5298 let mut state = ConfigState::new();
5299 let patch = UpdateTcpListenerConfig {
5300 address: SocketAddress::new_v4(9, 9, 9, 9, 9999),
5301 front_timeout: Some(5),
5302 ..Default::default()
5303 };
5304 let err = state
5305 .dispatch(&RequestType::UpdateTcpListener(patch).into())
5306 .unwrap_err();
5307 assert!(
5308 matches!(
5309 err,
5310 StateError::NotFound {
5311 kind: ObjectKind::TcpListener,
5312 ..
5313 }
5314 ),
5315 "expected NotFound, got: {err}"
5316 );
5317 }
5318
5319 #[test]
5325 fn dispatch_passes_through_set_metric_detail() {
5326 use crate::proto::command::{MetricDetail, SetMetricDetail};
5327 let mut state = ConfigState::new();
5328 let req: Request = RequestType::SetMetricDetail(SetMetricDetail {
5329 client_id: "test:1".to_owned(),
5330 detail: Some(MetricDetail::DetailBackend as i32),
5331 ttl_seconds: Some(60),
5332 clear: Some(false),
5333 reason: Some("regression-guard".to_owned()),
5334 peer_pid: None,
5335 peer_session_ulid: None,
5336 })
5337 .into();
5338 state
5339 .dispatch(&req)
5340 .expect("SetMetricDetail must traverse dispatch without UndispatchableRequest");
5341 }
5342}