1use std::{
2 collections::{HashMap, hash_map::Entry},
3 sync::{
4 Arc,
5 atomic::{AtomicUsize, Ordering},
6 },
7};
8
9use serde::Deserialize;
10use uuid::Uuid;
11
12use crate::instance::{Instance, State};
13
14static NEXT_IDX: AtomicUsize = AtomicUsize::new(0);
16
17type ConnectionType = String;
19
20#[derive(Clone, Debug, Deserialize)]
22pub(crate) struct BucketRangeEntry {
23 pub tier_name: String,
24 pub bucket_id_start: u64,
25 pub bucket_id_end: u64,
26 pub current_replicaset_uuid: Uuid,
27}
28
29pub mod view {
31 use std::{collections::HashMap, ops::RangeInclusive};
32
33 use uuid::Uuid;
34
35 use super::ConnectionType;
36 use crate::error::{Error, IncompleteBucketMap};
37
38 #[derive(Debug, Clone, PartialEq, Eq)]
40 pub struct Topology {
41 pub(super) tiers: Vec<Tier>,
42 }
43
44 #[derive(Debug, Clone, PartialEq, Eq)]
46 pub struct Tier {
47 pub(super) name: String,
48 pub(super) bucket_count: u64,
49 pub(super) replicasets: Vec<Replicaset>,
50 }
51
52 #[derive(Debug, Clone, PartialEq, Eq)]
54 pub struct Replicaset {
55 pub(super) uuid: Uuid,
56 pub(super) name: String,
57 pub(super) master: Option<Instance>,
58 pub(super) replicas: Vec<Instance>,
59 pub(super) ranges: Vec<RangeInclusive<u64>>,
60 }
61
62 #[derive(Debug, Clone, PartialEq, Eq)]
64 pub struct Instance {
65 pub(super) name: String,
66 pub(super) addresses: HashMap<ConnectionType, String>,
68 pub(super) online: bool,
69 pub(super) is_master: bool,
70 pub(super) raft_id: u64,
71 pub(super) uuid: Uuid,
72 }
73
74 pub type BucketMap<'a> = HashMap<Uuid, &'a [RangeInclusive<u64>]>;
76
77 impl Topology {
78 #[must_use]
80 pub fn tiers(&self) -> &[Tier] {
81 &self.tiers
82 }
83 }
84
85 impl Tier {
86 #[must_use]
88 pub fn name(&self) -> &str {
89 &self.name
90 }
91
92 #[must_use]
94 pub fn replicasets(&self) -> &[Replicaset] {
95 &self.replicasets
96 }
97
98 #[must_use]
100 pub fn bucket_count(&self) -> u64 {
101 self.bucket_count
102 }
103
104 pub fn bucket_map(&self) -> Result<BucketMap<'_>, Error> {
111 let mut ranges: Vec<_> = self.replicasets.iter().flat_map(|rs| &rs.ranges).collect();
112 ranges.sort_unstable_by_key(|r| *r.start());
113 let mut covered = 0;
115 for r in ranges {
116 if *r.start() != covered + 1 || r.is_empty() {
117 break;
118 }
119 covered = *r.end();
120 }
121 if covered != self.bucket_count {
122 return Err(Error::IncompleteBucketMap(Box::new(IncompleteBucketMap {
123 tier: self.name.clone(),
124 covered,
125 expected: self.bucket_count,
126 })));
127 }
128 Ok(self
129 .replicasets
130 .iter()
131 .map(|rs| (rs.uuid(), rs.ranges()))
132 .collect())
133 }
134 }
135
136 impl Replicaset {
137 #[must_use]
139 pub fn uuid(&self) -> Uuid {
140 self.uuid
141 }
142
143 #[must_use]
145 pub fn master(&self) -> Option<&Instance> {
146 self.master.as_ref()
147 }
148
149 #[must_use]
151 pub fn replicas(&self) -> &[Instance] {
152 &self.replicas
153 }
154
155 pub fn instances(&self) -> impl Iterator<Item = &Instance> {
157 self.master.iter().chain(&self.replicas)
158 }
159
160 #[must_use]
162 pub fn ranges(&self) -> &[RangeInclusive<u64>] {
163 &self.ranges
164 }
165
166 #[must_use]
168 pub fn name(&self) -> &str {
169 &self.name
170 }
171 }
172
173 impl Instance {
174 #[must_use]
176 pub fn name(&self) -> &str {
177 &self.name
178 }
179
180 #[must_use]
183 pub fn address(&self, connection_type: impl AsRef<str>) -> Option<&str> {
184 self.addresses
185 .get(connection_type.as_ref())
186 .map(String::as_str)
187 }
188
189 #[must_use]
191 pub fn is_online(&self) -> bool {
192 self.online
193 }
194
195 #[must_use]
197 pub fn is_master(&self) -> bool {
198 self.is_master
199 }
200
201 #[must_use]
203 pub fn raft_id(&self) -> u64 {
204 self.raft_id
205 }
206
207 #[must_use]
209 pub fn uuid(&self) -> Uuid {
210 self.uuid
211 }
212 }
213}
214
215#[derive(Debug, Default)]
217pub(crate) struct Topology {
218 pub(crate) instances: HashMap<String, Arc<Instance>>,
219 pgproto_tiers: Vec<Tier>,
220 other_tiers: HashMap<ConnectionType, Vec<Tier>>,
222}
223
224#[derive(Debug, Clone)]
226struct Tier {
227 name: String,
228 bucket_count: u64,
229 replicasets: Vec<Replicaset>,
230 bucket_map: Vec<BucketSlot>,
231}
232
233#[derive(Debug, Clone)]
235struct BucketSlot {
236 start: u64,
237 end: u64,
238 replicaset_idx: usize,
239}
240
241#[derive(Debug, Clone)]
243struct Replicaset {
244 uuid: Uuid,
245 master: Arc<Instance>,
246 replicas: Vec<Arc<Instance>>,
247}
248
249impl From<(Vec<pg::Row>, Vec<pg::Row>)> for Topology {
250 fn from((topology_rows, bucket_rows): (Vec<pg::Row>, Vec<pg::Row>)) -> Self {
251 let mut by_type: HashMap<ConnectionType, Vec<pg::Row>> = HashMap::new();
253 for row in topology_rows {
254 let Ok(connection_type) = row.try_get::<_, &str>("connection_type") else {
255 continue;
256 };
257 if let Some(rows) = by_type.get_mut(connection_type) {
258 rows.push(row);
259 } else {
260 by_type.insert(connection_type.to_string(), vec![row]);
261 }
262 }
263 let mut buckets: Vec<BucketRangeEntry> = Vec::new();
265 for row in bucket_rows {
266 let entries = if let Ok(json) = row.try_get::<_, serde_json::Value>(0) {
267 serde_json::from_value::<Vec<BucketRangeEntry>>(json)
268 } else if let Ok(s) = row.try_get::<_, &str>(0) {
269 serde_json::from_str(s)
270 } else {
271 log::error!("failed to read bucket ranges column");
272 continue;
273 };
274 match entries {
275 Ok(v) => buckets.extend(v),
276 Err(e) => log::error!("failed to parse bucket ranges: {e}"),
277 }
278 }
279
280 let mut other_tiers: HashMap<ConnectionType, Vec<Tier>> = by_type
281 .into_iter()
282 .map(|(connection_type, rows)| (connection_type, build_tiers(rows, &buckets)))
283 .collect();
284 let pgproto_tiers = other_tiers.remove("pgproto").unwrap_or_default();
285 let instances = pgproto_tiers
287 .iter()
288 .flat_map(|t| &t.replicasets)
289 .flat_map(|rs| {
290 rs.master
291 .is_master
292 .then_some(&rs.master)
293 .into_iter()
294 .chain(&rs.replicas)
295 })
296 .map(|inst| (inst.name.clone(), inst.clone()))
297 .collect();
298 Topology {
299 instances,
300 pgproto_tiers,
301 other_tiers,
302 }
303 }
304}
305
306fn build_tiers(topology_rows: Vec<pg::Row>, buckets: &[BucketRangeEntry]) -> Vec<Tier> {
308 let mut tiers: Vec<Tier> = Vec::new();
309 for row in topology_rows {
310 let inst = match Instance::try_from(row) {
311 Ok(v) => v,
312 Err(e) => {
313 log::error!("failed to convert instance: {e}");
314 continue;
315 }
316 };
317
318 let inst = Arc::new(inst);
319
320 let tier = if let Some(t) = tiers.iter_mut().find(|t| t.name == inst.tier) {
321 t
322 } else {
323 tiers.push(Tier {
324 name: inst.tier.clone(),
325 bucket_count: inst.bucket_count,
326 replicasets: Vec::new(),
327 bucket_map: Vec::new(),
328 });
329 tiers.last_mut().expect("just pushed")
330 };
331
332 let rs = if let Some(r) = tier
333 .replicasets
334 .iter_mut()
335 .find(|r| r.uuid == inst.replicaset_uuid)
336 {
337 r
338 } else {
339 tier.replicasets.push(Replicaset {
340 uuid: inst.replicaset_uuid,
341 master: inst.clone(),
342 replicas: Vec::new(),
343 });
344 tier.replicasets.last_mut().expect("just pushed")
345 };
346
347 if inst.is_master {
348 rs.master = inst.clone();
349 } else {
350 rs.replicas.push(inst.clone());
351 }
352 }
353
354 for entry in buckets {
355 let Some(tier) = tiers.iter_mut().find(|t| t.name == entry.tier_name) else {
356 continue;
357 };
358 let Some(rs_idx) = tier
359 .replicasets
360 .iter()
361 .position(|r| r.uuid == entry.current_replicaset_uuid)
362 else {
363 continue;
364 };
365 tier.bucket_map.push(BucketSlot {
366 start: entry.bucket_id_start,
367 end: entry.bucket_id_end,
368 replicaset_idx: rs_idx,
369 });
370 }
371
372 for tier in &mut tiers {
373 tier.bucket_map.sort_unstable_by_key(|s| s.start);
374 }
375
376 tiers
377}
378
379impl Topology {
380 pub(crate) fn new() -> Self {
382 Self::default()
383 }
384
385 pub(crate) fn merge(&mut self, incoming: Topology) {
388 for (name, existing) in &self.instances {
389 if !incoming.instances.contains_key(name) {
390 existing.set_state(State::Offline);
391 }
392 }
393
394 for (name, inc_inst) in incoming.instances {
397 match self.instances.entry(name) {
398 Entry::Occupied(mut e) => {
399 inc_inst.set_idx(e.get().idx());
400 e.insert(inc_inst);
401 }
402 Entry::Vacant(e) => {
403 inc_inst.set_idx(NEXT_IDX.fetch_add(1, Ordering::Relaxed));
404 e.insert(inc_inst);
405 }
406 }
407 }
408
409 self.pgproto_tiers = incoming.pgproto_tiers;
410 self.other_tiers = incoming.other_tiers;
411 }
412
413 pub(crate) fn resolve(&self, tier: &str, bucket_id: u64, on_master: bool) -> Option<usize> {
416 let t = self.pgproto_tiers.iter().find(|t| t.name == tier)?;
417 let rs = t.lookup(bucket_id)?;
418
419 if on_master || rs.replicas.is_empty() {
420 return if rs.master.is_online() {
421 Some(rs.master.idx())
422 } else {
423 None
424 };
425 }
426
427 let online: Vec<_> = rs.replicas.iter().filter(|r| r.is_online()).collect();
428 if online.is_empty() {
429 return if rs.master.is_online() {
430 Some(rs.master.idx())
431 } else {
432 None
433 };
434 }
435
436 let idx =
437 usize::try_from(bucket_id % online.len() as u64).expect("modulo result fits in usize");
438 Some(online[idx].idx())
439 }
440
441 pub(crate) fn snapshot(&self) -> view::Topology {
444 let mut view_tiers = Vec::with_capacity(self.pgproto_tiers.len());
445 add_to_view(&mut view_tiers, "pgproto", &self.pgproto_tiers);
446 for (connection_type, tiers) in &self.other_tiers {
447 add_to_view(&mut view_tiers, connection_type, tiers);
448 }
449 view_tiers.sort_unstable_by(|a, b| a.name.cmp(&b.name));
450 for tier in &mut view_tiers {
451 sort(&mut tier.replicasets);
452 }
453 view::Topology { tiers: view_tiers }
454 }
455
456 pub(crate) fn bucket_count(&self, tier: &str) -> Option<u64> {
458 self.pgproto_tiers
459 .iter()
460 .find(|t| t.name == tier)
461 .map(|t| t.bucket_count)
462 }
463}
464
465fn add_to_view(view_tiers: &mut Vec<view::Tier>, connection_type: &str, tiers: &[Tier]) {
468 for tier in tiers {
469 let view_tier = if let Some(t) = view_tiers.iter_mut().find(|t| t.name == tier.name) {
470 t
471 } else {
472 view_tiers.push(view::Tier {
473 name: tier.name.clone(),
474 bucket_count: tier.bucket_count,
475 replicasets: Vec::new(),
476 });
477 view_tiers.last_mut().expect("just pushed")
478 };
479
480 for (idx, rs) in tier.replicasets.iter().enumerate() {
481 let view_rs =
482 if let Some(r) = view_tier.replicasets.iter_mut().find(|r| r.uuid == rs.uuid) {
483 r
484 } else {
485 view_tier.replicasets.push(view::Replicaset {
486 uuid: rs.uuid,
487 name: rs.master.replicaset_name.clone(),
488 master: None,
489 replicas: Vec::new(),
490 ranges: tier
491 .bucket_map
492 .iter()
493 .filter(|s| s.replicaset_idx == idx)
494 .map(|s| s.start..=s.end)
495 .collect(),
496 });
497 view_tier.replicasets.last_mut().expect("just pushed")
498 };
499
500 if rs.master.is_master {
501 match &mut view_rs.master {
502 Some(m) => {
503 m.addresses
504 .insert(connection_type.to_string(), rs.master.addr.to_string());
505 }
506 None => view_rs.master = Some(instance(&rs.master, connection_type)),
507 }
508 }
509 for r in &rs.replicas {
510 if let Some(i) = view_rs.replicas.iter_mut().find(|i| i.name == r.name) {
511 i.addresses
512 .insert(connection_type.to_string(), r.addr.to_string());
513 } else {
514 view_rs.replicas.push(instance(r, connection_type));
515 }
516 }
517 }
518 }
519}
520
521impl Tier {
522 fn lookup(&self, bucket_id: u64) -> Option<&Replicaset> {
524 let i = self
525 .bucket_map
526 .partition_point(|s| s.start <= bucket_id)
527 .checked_sub(1)?;
528 let slot = &self.bucket_map[i];
529 if bucket_id <= slot.end {
530 self.replicasets.get(slot.replicaset_idx)
531 } else {
532 None
533 }
534 }
535}
536
537fn sort(replicasets: &mut [view::Replicaset]) {
539 replicasets.sort_unstable_by_key(|rs| rs.uuid);
540 for rs in replicasets {
541 rs.replicas.sort_unstable_by(|a, b| a.name.cmp(&b.name));
542 }
543}
544
545fn instance(inst: &Instance, connection_type: &str) -> view::Instance {
547 view::Instance {
548 name: inst.name.clone(),
549 addresses: HashMap::from([(connection_type.to_string(), inst.addr.to_string())]),
550 online: inst.is_online(),
551 is_master: inst.is_master,
552 raft_id: inst.raft_id,
553 uuid: inst.uuid,
554 }
555}
556
557#[cfg(test)]
558mod tests {
559 use super::*;
560 use crate::error::Error;
561
562 const RS1: Uuid = Uuid::from_u128(1);
563 const RS2: Uuid = Uuid::from_u128(2);
564
565 fn make_topology(name: &str, tier: &str, rs_uuid: Uuid) -> Topology {
567 let inst = Arc::new(Instance::new_for_test(
568 name,
569 "127.0.0.1:3301",
570 tier,
571 rs_uuid,
572 true,
573 3000,
574 ));
575
576 let mut instances = HashMap::new();
577 instances.insert(inst.name.clone(), inst.clone());
578
579 let rs = Replicaset {
580 uuid: rs_uuid,
581 master: inst.clone(),
582 replicas: vec![],
583 };
584 let tier_obj = Tier {
585 name: tier.to_string(),
586 bucket_count: 3000,
587 replicasets: vec![rs],
588 bucket_map: vec![BucketSlot {
589 start: 1,
590 end: 3000,
591 replicaset_idx: 0,
592 }],
593 };
594
595 Topology {
596 instances,
597 pgproto_tiers: vec![tier_obj],
598 other_tiers: HashMap::new(),
599 }
600 }
601
602 #[test]
604 fn bucket_map_rejects_an_incomplete_map() {
605 let mut topo = Topology::new();
606 let mut incoming = make_topology("i1", "default", RS1);
607 incoming.pgproto_tiers[0].bucket_map = vec![
608 BucketSlot {
609 start: 1,
610 end: 1500,
611 replicaset_idx: 0,
612 },
613 BucketSlot {
615 start: 1501,
616 end: 3000,
617 replicaset_idx: 7,
618 },
619 ];
620 topo.merge(incoming);
621
622 match topo.snapshot().tiers()[0].bucket_map() {
623 Err(Error::IncompleteBucketMap(m)) => {
624 assert_eq!(m.tier, "default");
625 assert_eq!((m.covered, m.expected), (1500, 3000));
626 }
627 other => panic!("a half-covered tier must not be returned: {other:?}"),
628 }
629 }
630
631 #[test]
633 fn bucket_map_rejects_a_tier_with_no_ranges() {
634 let mut topo = Topology::new();
635 let mut incoming = make_topology("i1", "default", RS1);
636 incoming.pgproto_tiers[0].bucket_map.clear();
637 topo.merge(incoming);
638
639 assert!(matches!(
640 topo.snapshot().tiers()[0].bucket_map(),
641 Err(Error::IncompleteBucketMap(ref m)) if m.covered == 0
642 ));
643 }
644
645 #[test]
647 fn snapshot_ranges_agree_with_resolve() {
648 let mut topo = Topology::new();
649 let mut incoming = make_topology("i1", "default", RS1);
650 let i2 = Arc::new(Instance::new_for_test(
651 "i2",
652 "127.0.0.1:3302",
653 "default",
654 RS2,
655 true,
656 3000,
657 ));
658 incoming.instances.insert(i2.name.clone(), i2.clone());
659 let tier = &mut incoming.pgproto_tiers[0];
660 tier.replicasets.push(Replicaset {
661 uuid: RS2,
662 master: i2,
663 replicas: vec![],
664 });
665 tier.bucket_map = vec![
666 BucketSlot {
667 start: 1,
668 end: 1000,
669 replicaset_idx: 0,
670 },
671 BucketSlot {
672 start: 1001,
673 end: 2000,
674 replicaset_idx: 1,
675 },
676 BucketSlot {
677 start: 2001,
678 end: 3000,
679 replicaset_idx: 0,
680 },
681 ];
682 topo.merge(incoming);
683
684 let snapshot = topo.snapshot();
685 let tier = &snapshot.tiers()[0];
686 assert_eq!(tier.name(), "default");
687 let owned: Vec<_> = tier
688 .replicasets()
689 .iter()
690 .map(|rs| {
691 (
692 rs.uuid(),
693 rs.master().map(view::Instance::name),
694 rs.ranges().len(),
695 )
696 })
697 .collect();
698 assert_eq!(owned, [(RS1, Some("i1"), 2), (RS2, Some("i2"), 1)]);
699 let bucket_map = tier.bucket_map().expect("map covers the tier");
700 assert_eq!(bucket_map[&RS1], [1..=1000, 2001..=3000]);
701
702 for rs in tier.replicasets() {
703 let expected = topo.instances[rs.master().expect("named master").name()].idx();
704 for r in rs.ranges() {
705 for bucket in [*r.start(), *r.end()] {
706 assert_eq!(topo.resolve("default", bucket, true), Some(expected));
707 }
708 }
709 }
710 }
711
712 #[test]
714 fn snapshot_reports_no_master_until_one_is_named() {
715 let inst = Arc::new(Instance::new_for_test(
717 "i1",
718 "127.0.0.1:3301",
719 "default",
720 RS1,
721 false,
722 3000,
723 ));
724 let mut incoming = make_topology("i1", "default", RS1);
725 incoming.instances.insert(inst.name.clone(), inst.clone());
726 let rs = &mut incoming.pgproto_tiers[0].replicasets[0];
727 rs.master = inst.clone();
728 rs.replicas = vec![inst];
729 let mut topo = Topology::new();
730 topo.merge(incoming);
731
732 assert_eq!(
733 topo.resolve("default", 1, true),
734 Some(topo.instances["i1"].idx())
735 );
736 let snapshot = topo.snapshot();
737 let rs = &snapshot.tiers()[0].replicasets()[0];
738 assert_eq!(rs.master(), None);
739 let replicas: Vec<_> = rs.replicas().iter().map(view::Instance::name).collect();
740 assert_eq!(replicas, ["i1"]);
741 }
742
743 #[test]
745 fn replicaset_lists_the_master_first() {
746 let mut topo = make_topology("m", "default", RS1);
747 topo.pgproto_tiers[0].replicasets[0].replicas = ["b", "a"]
748 .map(|name| {
749 Arc::new(Instance::new_for_test(
750 name,
751 "127.0.0.1:3302",
752 "default",
753 RS1,
754 false,
755 3000,
756 ))
757 })
758 .into();
759
760 let snapshot = topo.snapshot();
761 let rs = &snapshot.tiers()[0].replicasets()[0];
762 let instances: Vec<_> = rs.instances().map(|i| (i.name(), i.is_master())).collect();
763 assert_eq!(instances, [("m", true), ("a", false), ("b", false)]);
764 }
765
766 #[test]
772 fn merge_preserves_pool_idx_in_tiers() {
773 NEXT_IDX.fetch_add(1, Ordering::Relaxed);
775
776 let mut topo = Topology::new();
777
778 topo.merge(make_topology("i1", "default", RS1));
780 let expected_idx = topo.instances["i1"].idx();
781 assert!(expected_idx > 0, "precondition: idx must be non-zero");
782
783 topo.merge(make_topology("i1", "default", RS1));
787
788 assert_eq!(
789 topo.resolve("default", 1, true),
790 Some(expected_idx),
791 "resolve must return the stable pool slot, not the default 0 from the fresh Arc"
792 );
793 }
794
795 #[test]
798 fn merge_keeps_one_object_per_instance() {
799 let mut topo = Topology::new();
800 topo.merge(make_topology("i1", "default", RS1));
801 topo.merge(make_topology("i1", "default", RS1));
802
803 let master = &topo.pgproto_tiers[0].replicasets[0].master;
804 assert!(Arc::ptr_eq(&topo.instances["i1"], master));
805 }
806
807 fn with_tier(tier: Tier) -> Topology {
809 Topology {
810 instances: HashMap::new(),
811 pgproto_tiers: Vec::new(),
812 other_tiers: HashMap::from([("iproto".to_string(), vec![tier])]),
813 }
814 }
815
816 fn tier_at(addr: &str) -> Tier {
818 let inst = Arc::new(Instance::new_for_test(
819 "i1", addr, "default", RS1, true, 3000,
820 ));
821 Tier {
822 name: "default".to_string(),
823 bucket_count: 3000,
824 replicasets: vec![Replicaset {
825 uuid: RS1,
826 master: inst,
827 replicas: vec![],
828 }],
829 bucket_map: vec![BucketSlot {
830 start: 1,
831 end: 3000,
832 replicaset_idx: 0,
833 }],
834 }
835 }
836
837 #[test]
839 fn snapshot_merges_the_addresses_of_an_instance() {
840 let mut topo = with_tier(tier_at("127.0.0.1:3301"));
841 topo.pgproto_tiers = vec![tier_at("127.0.0.1:5432")];
842
843 let snapshot = topo.snapshot();
844 let rs = &snapshot.tiers()[0].replicasets()[0];
845 assert!(rs.replicas().is_empty());
846 let i1 = rs.master().expect("named master");
847 assert_eq!(i1.address("pgproto"), Some("127.0.0.1:5432"));
848 assert_eq!(i1.address("iproto"), Some("127.0.0.1:3301"));
849 }
850
851 #[test]
853 fn snapshot_keeps_an_instance_without_pgproto() {
854 let topo = with_tier(tier_at("127.0.0.1:3301"));
855
856 let snapshot = topo.snapshot();
857 let tier = &snapshot.tiers()[0];
858 let i1 = tier.replicasets()[0].master().expect("named master");
859 assert_eq!(i1.address("pgproto"), None);
860 assert_eq!(i1.address("iproto"), Some("127.0.0.1:3301"));
861 assert!(tier.bucket_map().is_ok());
862 }
863
864 #[test]
866 fn merge_replaces_other_tiers() {
867 let mut topo = with_tier(tier_at("127.0.0.1:3301"));
868 topo.merge(with_tier(tier_at("127.0.0.1:3302")));
869
870 let snapshot = topo.snapshot();
871 let i1 = snapshot.tiers()[0].replicasets()[0]
872 .master()
873 .expect("named master");
874 assert_eq!(i1.address("iproto"), Some("127.0.0.1:3302"));
875 }
876
877 #[test]
879 fn snapshot_is_independent_of_insertion_order() {
880 let inst = |name: &str, rs: Uuid, master: bool| {
881 Arc::new(Instance::new_for_test(
882 name,
883 "127.0.0.1:3301",
884 "default",
885 rs,
886 master,
887 3000,
888 ))
889 };
890 let build = |reversed: bool| {
891 let mut sets = vec![
892 (RS1, 1, 1500, vec!["a", "b"]),
893 (RS2, 1501, 3000, vec!["c", "d"]),
894 ];
895 if reversed {
896 sets.reverse();
897 }
898 let mut tier = Tier {
899 name: "default".to_string(),
900 bucket_count: 3000,
901 replicasets: Vec::new(),
902 bucket_map: Vec::new(),
903 };
904 for (idx, (uuid, start, end, mut names)) in sets.into_iter().enumerate() {
905 if reversed {
906 names.reverse();
907 }
908 tier.replicasets.push(Replicaset {
909 uuid,
910 master: inst(&format!("{uuid}-m"), uuid, true),
911 replicas: names.into_iter().map(|n| inst(n, uuid, false)).collect(),
912 });
913 tier.bucket_map.push(BucketSlot {
914 start,
915 end,
916 replicaset_idx: idx,
917 });
918 }
919 tier.bucket_map.sort_unstable_by_key(|s| s.start);
920 let mut topo = Topology::new();
921 topo.pgproto_tiers = vec![tier];
922 topo.snapshot()
923 };
924
925 assert_eq!(build(false), build(true));
926 }
927}