aboutsummaryrefslogtreecommitdiff
path: root/src/rpc/layout/test.rs
blob: e963907348dee21c534933007bd730f56bc88ae9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
use std::cmp::min;
use std::collections::HashMap;

use garage_util::crdt::Crdt;
use garage_util::error::*;

use crate::layout::*;

// This function checks that the partition size S computed is at least better than the
// one given by a very naive algorithm. To do so, we try to run the naive algorithm
// assuming a partion size of S+1. If we succed, it means that the optimal assignment
// was not optimal. The naive algorithm is the following :
// - we compute the max number of partitions associated to every node, capped at the
// partition number. It gives the number of tokens of every node.
// - every zone has a number of tokens equal to the sum of the tokens of its nodes.
// - we cycle over the partitions and associate zone tokens while respecting the
// zone redundancy constraint.
// NOTE: the naive algorithm is not optimal. Counter example:
// take nb_partition = 3  ; replication_factor = 5; redundancy = 4;
// number of tokens by zone : (A, 4), (B,1), (C,4), (D, 4), (E, 2)
// With these parameters, the naive algo fails, whereas there is a solution:
// (A,A,C,D,E) , (A,B,C,D,D) (A,C,C,D,E)
fn check_against_naive(cl: &LayoutVersion) -> Result<bool, Error> {
	let over_size = cl.partition_size + 1;
	let mut zone_token = HashMap::<String, usize>::new();

	let (zones, zone_to_id) = cl.generate_nongateway_zone_ids()?;

	if zones.is_empty() {
		return Ok(false);
	}

	for z in zones.iter() {
		zone_token.insert(z.clone(), 0);
	}
	for uuid in cl.nongateway_nodes() {
		let z = cl.get_node_zone(&uuid)?;
		let c = cl.get_node_capacity(&uuid).unwrap();
		zone_token.insert(
			z.to_string(),
			zone_token[z] + min(NB_PARTITIONS, (c / over_size) as usize),
		);
	}

	// For every partition, we count the number of zone already associated and
	// the name of the last zone associated

	let mut id_zone_token = vec![0; zones.len()];
	for (z, t) in zone_token.iter() {
		id_zone_token[zone_to_id[z]] = *t;
	}

	let mut nb_token = vec![0; NB_PARTITIONS];
	let mut last_zone = vec![zones.len(); NB_PARTITIONS];

	let mut curr_zone = 0;

	let redundancy = cl.effective_zone_redundancy();

	for replic in 0..cl.replication_factor {
		for p in 0..NB_PARTITIONS {
			while id_zone_token[curr_zone] == 0
				|| (last_zone[p] == curr_zone
					&& redundancy - nb_token[p] <= cl.replication_factor - replic)
			{
				curr_zone += 1;
				if curr_zone >= zones.len() {
					return Ok(true);
				}
			}
			id_zone_token[curr_zone] -= 1;
			if last_zone[p] != curr_zone {
				nb_token[p] += 1;
				last_zone[p] = curr_zone;
			}
		}
	}

	return Ok(false);
}

fn show_msg(msg: &Message) {
	for s in msg.iter() {
		println!("{}", s);
	}
}

fn update_layout(
	cl: &mut LayoutHistory,
	node_capacity_vec: &[u64],
	node_zone_vec: &[&'static str],
	zone_redundancy: usize,
) {
	let staging = cl.staging.get_mut();

	for (i, (capacity, zone)) in node_capacity_vec
		.iter()
		.zip(node_zone_vec.iter())
		.enumerate()
	{
		let node_id = [i as u8; 32].into();

		let update = staging.roles.update_mutator(
			node_id,
			NodeRoleV(Some(NodeRole {
				zone: zone.to_string(),
				capacity: Some(*capacity),
				tags: (vec![]),
			})),
		);
		staging.roles.merge(&update);
	}
	staging.parameters.update(LayoutParameters {
		zone_redundancy: ZoneRedundancy::AtLeast(zone_redundancy),
	});

	cl.update_hashes();
}

#[test]
fn test_assignment() {
	let mut node_capacity_vec = vec![4000, 1000, 2000];
	let mut node_zone_vec = vec!["A", "B", "C"];

	let mut cl = LayoutHistory::new(3);
	update_layout(&mut cl, &node_capacity_vec, &node_zone_vec, 3);
	let v = cl.current().version;
	let (mut cl, msg) = cl.apply_staged_changes(Some(v + 1)).unwrap();
	show_msg(&msg);
	assert_eq!(cl.check(), Ok(()));
	assert!(check_against_naive(cl.current()).unwrap());

	node_capacity_vec = vec![4000, 1000, 1000, 3000, 1000, 1000, 2000, 10000, 2000];
	node_zone_vec = vec!["A", "B", "C", "C", "C", "B", "G", "H", "I"];
	update_layout(&mut cl, &node_capacity_vec, &node_zone_vec, 2);
	let v = cl.current().version;
	let (mut cl, msg) = cl.apply_staged_changes(Some(v + 1)).unwrap();
	show_msg(&msg);
	assert_eq!(cl.check(), Ok(()));
	assert!(check_against_naive(cl.current()).unwrap());

	node_capacity_vec = vec![4000, 1000, 2000, 7000, 1000, 1000, 2000, 10000, 2000];
	update_layout(&mut cl, &node_capacity_vec, &node_zone_vec, 3);
	let v = cl.current().version;
	let (mut cl, msg) = cl.apply_staged_changes(Some(v + 1)).unwrap();
	show_msg(&msg);
	assert_eq!(cl.check(), Ok(()));
	assert!(check_against_naive(cl.current()).unwrap());

	node_capacity_vec = vec![
		4000000, 4000000, 2000000, 7000000, 1000000, 9000000, 2000000, 10000, 2000000,
	];
	update_layout(&mut cl, &node_capacity_vec, &node_zone_vec, 1);
	let v = cl.current().version;
	let (cl, msg) = cl.apply_staged_changes(Some(v + 1)).unwrap();
	show_msg(&msg);
	assert_eq!(cl.check(), Ok(()));
	assert!(check_against_naive(cl.current()).unwrap());
}