github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/renter/hostdb/weightedlist_test.go (about) 1 package hostdb 2 3 import ( 4 "crypto/rand" 5 "errors" 6 "math/big" 7 "strconv" 8 "testing" 9 10 "github.com/NebulousLabs/Sia/modules" 11 "github.com/NebulousLabs/Sia/types" 12 ) 13 14 // fakeAddr returns a modules.NetAddress to be used in a HostEntry. Such 15 // addresses are needed in order to satisfy the HostDB's "1 host per IP" rule. 16 func fakeAddr(n uint8) modules.NetAddress { 17 return modules.NetAddress("127.0.0." + strconv.Itoa(int(n)) + ":1") 18 } 19 20 // uniformTreeVerification checks that everything makes sense in the tree given 21 // the number of entries that the tree is supposed to have and also given that 22 // every entropy has the same weight. 23 func uniformTreeVerification(hdb *HostDB, numEntries int) error { 24 // Check that the weight of the hostTree is what is expected. 25 expectedWeight := hdb.hostTree.hostEntry.Weight.Mul64(uint64(numEntries)) 26 if hdb.hostTree.weight.Cmp(expectedWeight) != 0 { 27 return errors.New("expected weight is incorrect") 28 } 29 30 // Check that the length of activeHosts and the count of hostTree are 31 // consistent. 32 if len(hdb.activeHosts) != numEntries { 33 return errors.New("unexpected number of active hosts") 34 } 35 36 // Select many random hosts and do naive statistical analysis on the 37 // results. 38 if !testing.Short() { 39 // Pull a bunch of random hosts and count how many times we pull each 40 // host. 41 selectionMap := make(map[modules.NetAddress]int) 42 expected := 100 43 for i := 0; i < expected*numEntries; i++ { 44 entries := hdb.RandomHosts(1, nil) 45 if len(entries) == 0 { 46 return errors.New("no hosts") 47 } 48 selectionMap[entries[0].NetAddress]++ 49 } 50 51 // See if each host was selected enough times. 52 errorBound := 64 // Pretty large, but will still detect if something is seriously wrong. 53 for _, count := range selectionMap { 54 if count < expected-errorBound || count > expected+errorBound { 55 return errors.New("error bound was breached") 56 } 57 } 58 } 59 60 // Try removing an re-adding all hosts. 61 var removedEntries []*hostEntry 62 for { 63 if hdb.hostTree.weight.IsZero() { 64 break 65 } 66 randWeight, err := rand.Int(rand.Reader, hdb.hostTree.weight.Big()) 67 if err != nil { 68 break 69 } 70 node, err := hdb.hostTree.nodeAtWeight(types.NewCurrency(randWeight)) 71 if err != nil { 72 break 73 } 74 node.removeNode() 75 delete(hdb.activeHosts, node.hostEntry.NetAddress) 76 77 // remove the entry from the hostdb so it won't be selected as a 78 // repeat. 79 removedEntries = append(removedEntries, node.hostEntry) 80 } 81 for _, entry := range removedEntries { 82 hdb.insertNode(entry) 83 } 84 return nil 85 } 86 87 // TestWeightedList inserts and removes nodes in a semi-random manner and 88 // verifies that the tree stays consistent through the adjustments. 89 func TestWeightedList(t *testing.T) { 90 if testing.Short() { 91 t.SkipNow() 92 } 93 94 // Create a hostdb and 3 equal entries to insert. 95 hdb := &HostDB{ 96 activeHosts: make(map[modules.NetAddress]*hostNode), 97 allHosts: make(map[modules.NetAddress]*hostEntry), 98 scanPool: make(chan *hostEntry, scanPoolSize), 99 } 100 101 // Create a bunch of host entries of equal weight. 102 var dbe modules.HostDBEntry 103 firstInsertions := 64 104 for i := 0; i < firstInsertions; i++ { 105 dbe.NetAddress = fakeAddr(uint8(i)) 106 entry := hostEntry{ 107 HostDBEntry: dbe, 108 Weight: types.NewCurrency64(10), 109 } 110 hdb.insertNode(&entry) 111 } 112 err := uniformTreeVerification(hdb, firstInsertions) 113 if err != nil { 114 t.Error(err) 115 } 116 117 // Remove a few hosts and check that the tree is still in order. 118 removals := 12 119 // Keep a map of what we've removed so far. 120 removedMap := make(map[uint8]struct{}) 121 for i := 0; i < removals; i++ { 122 // Try numbers until we roll a number that's not been removed yet. 123 var randInt uint8 124 for { 125 randBig, err := rand.Int(rand.Reader, big.NewInt(int64(firstInsertions))) 126 if err != nil { 127 t.Fatal(err) 128 } 129 randInt = uint8(randBig.Int64()) 130 _, exists := removedMap[randInt] 131 if !exists { 132 break 133 } 134 } 135 136 // Remove the entry and add it to the list of removed entries 137 err := hdb.removeHost(fakeAddr(randInt)) 138 if err != nil { 139 t.Fatal(err) 140 } 141 removedMap[randInt] = struct{}{} 142 } 143 err = uniformTreeVerification(hdb, firstInsertions-removals) 144 if err != nil { 145 t.Error(err) 146 } 147 148 // Do some more insertions. 149 secondInsertions := 64 150 for i := firstInsertions; i < firstInsertions+secondInsertions; i++ { 151 dbe.NetAddress = fakeAddr(uint8(i)) 152 entry := hostEntry{ 153 HostDBEntry: dbe, 154 Weight: types.NewCurrency64(10), 155 } 156 hdb.insertNode(&entry) 157 } 158 err = uniformTreeVerification(hdb, firstInsertions-removals+secondInsertions) 159 if err != nil { 160 t.Error(err) 161 } 162 } 163 164 // TestVariedWeights runs broad statistical tests on selecting hosts with 165 // multiple different weights. 166 func TestVariedWeights(t *testing.T) { 167 if testing.Short() { 168 t.SkipNow() 169 } 170 hdb := &HostDB{ 171 activeHosts: make(map[modules.NetAddress]*hostNode), 172 allHosts: make(map[modules.NetAddress]*hostEntry), 173 scanPool: make(chan *hostEntry, scanPoolSize), 174 } 175 176 // insert i hosts with the weights 0, 1, ..., i-1. 100e3 selections will be made 177 // per weight added to the tree, the total number of selections necessary 178 // will be tallied up as hosts are created. 179 var dbe modules.HostDBEntry 180 hostCount := 5 181 expectedPerWeight := int(10e3) 182 selections := 0 183 for i := 0; i < hostCount; i++ { 184 dbe.NetAddress = fakeAddr(uint8(i)) 185 entry := hostEntry{ 186 HostDBEntry: dbe, 187 Weight: types.NewCurrency64(uint64(i)), 188 } 189 hdb.insertNode(&entry) 190 selections += i * expectedPerWeight 191 } 192 193 // Perform many random selections, noting which host was selected each 194 // time. 195 selectionMap := make(map[string]int) 196 for i := 0; i < selections; i++ { 197 randEntry := hdb.RandomHosts(1, nil) 198 if len(randEntry) == 0 { 199 t.Fatal("no hosts!") 200 } 201 node, exists := hdb.activeHosts[randEntry[0].NetAddress] 202 if !exists { 203 t.Fatal("can't find randomly selected node in tree") 204 } 205 selectionMap[node.hostEntry.Weight.String()]++ 206 } 207 208 // Check that each host was selected an expected number of times. An error 209 // will be reported if the host of 0 weight is ever selected. 210 acceptableError := 0.2 211 for weight, timesSelected := range selectionMap { 212 intWeight, err := strconv.Atoi(weight) 213 if err != nil { 214 t.Fatal(err) 215 } 216 217 expectedSelected := float64(intWeight * expectedPerWeight) 218 if float64(expectedSelected)*acceptableError > float64(timesSelected) || float64(expectedSelected)/acceptableError < float64(timesSelected) { 219 t.Error("weighted list not selecting in a uniform distribution based on weight") 220 t.Error(expectedSelected) 221 t.Error(timesSelected) 222 } 223 } 224 } 225 226 // TestRepeatInsert inserts 2 hosts with the same address. 227 func TestRepeatInsert(t *testing.T) { 228 if testing.Short() { 229 t.SkipNow() 230 } 231 hdb := &HostDB{ 232 activeHosts: make(map[modules.NetAddress]*hostNode), 233 allHosts: make(map[modules.NetAddress]*hostEntry), 234 scanPool: make(chan *hostEntry, scanPoolSize), 235 } 236 237 var dbe modules.HostDBEntry 238 dbe.NetAddress = fakeAddr(0) 239 entry1 := hostEntry{ 240 HostDBEntry: dbe, 241 Weight: types.NewCurrency64(1), 242 } 243 entry2 := entry1 244 hdb.insertNode(&entry1) 245 246 entry2.Weight = types.NewCurrency64(100) 247 hdb.insertNode(&entry2) 248 if len(hdb.activeHosts) != 1 { 249 t.Error("insterting the same entry twice should result in only 1 entry in the hostdb") 250 } 251 } 252 253 // TestNodeAtWeight tests the nodeAtWeight method. 254 func TestNodeAtWeight(t *testing.T) { 255 // create hostTree 256 h1 := new(hostEntry) 257 h1.NetAddress = "foo" 258 h1.Weight = baseWeight 259 ht := createNode(nil, h1) 260 261 // overweight 262 _, err := ht.nodeAtWeight(baseWeight.Mul64(2)) 263 if err != errOverweight { 264 t.Errorf("expected %v, got %v", errOverweight, err) 265 } 266 267 h, err := ht.nodeAtWeight(baseWeight) 268 if err != nil { 269 t.Error(err) 270 } else if h.hostEntry != h1 { 271 t.Errorf("nodeAtWeight returned wrong node: expected %v, got %v", h1, h.hostEntry) 272 } 273 } 274 275 // TestRandomHosts probes the RandomHosts function. 276 func TestRandomHosts(t *testing.T) { 277 // Create the hostdb. 278 hdb := bareHostDB() 279 280 // Empty. 281 if hosts := hdb.RandomHosts(1, nil); len(hosts) != 0 { 282 t.Errorf("empty hostdb returns %v hosts: %v", len(hosts), hosts) 283 } 284 285 // Insert 3 hosts to be selected. 286 var dbe modules.HostDBEntry 287 dbe.NetAddress = fakeAddr(1) 288 entry1 := hostEntry{ 289 HostDBEntry: dbe, 290 Weight: types.NewCurrency64(1), 291 } 292 dbe.NetAddress = fakeAddr(2) 293 entry2 := hostEntry{ 294 HostDBEntry: dbe, 295 Weight: types.NewCurrency64(2), 296 } 297 dbe.NetAddress = fakeAddr(3) 298 entry3 := hostEntry{ 299 HostDBEntry: dbe, 300 Weight: types.NewCurrency64(3), 301 } 302 hdb.insertNode(&entry1) 303 hdb.insertNode(&entry2) 304 hdb.insertNode(&entry3) 305 306 if len(hdb.activeHosts) != 3 { 307 t.Error("wrong number of hosts") 308 } 309 if hdb.hostTree.weight.Cmp(types.NewCurrency64(6)) != 0 { 310 t.Error("unexpected weight at initialization") 311 t.Error(hdb.hostTree.weight) 312 } 313 314 // Grab 1 random host. 315 randHosts := hdb.RandomHosts(1, nil) 316 if len(randHosts) != 1 { 317 t.Error("didn't get 1 hosts") 318 } 319 320 // Grab 2 random hosts. 321 randHosts = hdb.RandomHosts(2, nil) 322 if len(randHosts) != 2 { 323 t.Error("didn't get 2 hosts") 324 } 325 if randHosts[0].NetAddress == randHosts[1].NetAddress { 326 t.Error("doubled up") 327 } 328 329 // Grab 3 random hosts. 330 randHosts = hdb.RandomHosts(3, nil) 331 if len(randHosts) != 3 { 332 t.Error("didn't get 3 hosts") 333 } 334 if randHosts[0].NetAddress == randHosts[1].NetAddress || randHosts[0].NetAddress == randHosts[2].NetAddress || randHosts[1].NetAddress == randHosts[2].NetAddress { 335 t.Error("doubled up") 336 } 337 338 // Grab 4 random hosts. 3 should be returned. 339 randHosts = hdb.RandomHosts(4, nil) 340 if len(randHosts) != 3 { 341 t.Error("didn't get 3 hosts") 342 } 343 if randHosts[0].NetAddress == randHosts[1].NetAddress || randHosts[0].NetAddress == randHosts[2].NetAddress || randHosts[1].NetAddress == randHosts[2].NetAddress { 344 t.Error("doubled up") 345 } 346 347 // Ask for 3 hosts that are not in randHosts. No hosts should be 348 // returned. 349 uniqueHosts := hdb.RandomHosts(3, []modules.NetAddress{ 350 randHosts[0].NetAddress, 351 randHosts[1].NetAddress, 352 randHosts[2].NetAddress, 353 }) 354 if len(uniqueHosts) != 0 { 355 t.Error("didn't get 0 hosts") 356 } 357 358 // Ask for 3 hosts, blacklisting non-existent hosts. 3 should be returned. 359 randHosts = hdb.RandomHosts(3, []modules.NetAddress{"foo", "bar", "baz"}) 360 if len(randHosts) != 3 { 361 t.Error("didn't get 3 hosts") 362 } 363 if randHosts[0].NetAddress == randHosts[1].NetAddress || randHosts[0].NetAddress == randHosts[2].NetAddress || randHosts[1].NetAddress == randHosts[2].NetAddress { 364 t.Error("doubled up") 365 } 366 367 }