github.com/simpleiot/simpleiot@v0.18.3/client/manager_test.go (about) 1 package client_test 2 3 import ( 4 "fmt" 5 "log" 6 "strconv" 7 "testing" 8 "time" 9 10 "github.com/nats-io/nats.go" 11 "github.com/simpleiot/simpleiot/client" 12 "github.com/simpleiot/simpleiot/data" 13 "github.com/simpleiot/simpleiot/server" 14 ) 15 16 type testNode struct { 17 ID string `node:"id"` 18 Parent string `node:"parent"` 19 Description string `point:"description"` 20 Port int `point:"port"` 21 Role string `edgepoint:"role"` 22 } 23 24 type testNodeClient struct { 25 nc *nats.Conn 26 config testNode 27 stop chan struct{} 28 stopped chan struct{} 29 newPoints chan client.NewPoints 30 newEdgePoints chan client.NewPoints 31 chGetConfig chan chan testNode 32 } 33 34 func newTestNodeClient(nc *nats.Conn, config testNode) *testNodeClient { 35 return &testNodeClient{ 36 nc: nc, 37 config: config, 38 stop: make(chan struct{}), 39 stopped: make(chan struct{}), 40 newPoints: make(chan client.NewPoints), 41 newEdgePoints: make(chan client.NewPoints), 42 chGetConfig: make(chan chan testNode), 43 } 44 } 45 46 func (tnc *testNodeClient) Run() error { 47 for { 48 select { 49 case <-tnc.stop: 50 close(tnc.stopped) 51 return nil 52 case pts := <-tnc.newPoints: 53 err := data.MergePoints(pts.ID, pts.Points, &tnc.config) 54 if err != nil { 55 log.Println("error merging new points:", err) 56 } 57 case pts := <-tnc.newEdgePoints: 58 err := data.MergeEdgePoints(pts.ID, pts.Parent, pts.Points, &tnc.config) 59 if err != nil { 60 log.Println("error merging new points:", err) 61 } 62 case ch := <-tnc.chGetConfig: 63 ch <- tnc.config 64 } 65 } 66 } 67 68 func (tnc *testNodeClient) Stop(_ error) { 69 close(tnc.stop) 70 } 71 72 func (tnc *testNodeClient) Points(nodeID string, points []data.Point) { 73 tnc.newPoints <- client.NewPoints{nodeID, "", points} 74 } 75 76 func (tnc *testNodeClient) EdgePoints(nodeID, parentID string, points []data.Point) { 77 tnc.newEdgePoints <- client.NewPoints{nodeID, parentID, points} 78 } 79 80 func (tnc *testNodeClient) getConfig() testNode { 81 result := make(chan testNode) 82 tnc.chGetConfig <- result 83 return <-result 84 } 85 86 func TestManager(t *testing.T) { 87 nc, root, stop, err := server.TestServer() 88 89 if err != nil { 90 t.Fatal("Error starting test server: ", err) 91 } 92 93 defer stop() 94 95 testConfig := testNode{"ID-testNode", root.ID, "fancy test node", 8118, ""} 96 97 // hydrate database with test data 98 err = client.SendNodeType(nc, testConfig, "test") 99 if err != nil { 100 t.Fatal("Error sending node: ", err) 101 } 102 103 var testClient *testNodeClient 104 newClient := make(chan *testNodeClient) 105 106 // wrap newTestNodeClient so we can stash a link to testClient 107 var newTestNodeClientWrapper = func(nc *nats.Conn, config testNode) client.Client { 108 testClient := newTestNodeClient(nc, config) 109 newClient <- testClient 110 return testClient 111 } 112 113 // Create a new manager for nodes of type "testNode". The manager looks for new nodes under the 114 // root and if it finds any, it instantiates a new client, and sends point updates to it 115 m := client.NewManager(nc, newTestNodeClientWrapper, nil) 116 117 managerStopped := make(chan struct{}) 118 119 startErr := make(chan error) 120 121 go func() { 122 err := m.Run() 123 if err != nil { 124 startErr <- fmt.Errorf("manager start returned error: %v", err) 125 } 126 127 close(managerStopped) 128 }() 129 130 // wait for client to be created 131 select { 132 case testClient = <-newClient: 133 case <-time.After(time.Second): 134 t.Fatal("Test client not created") 135 } 136 137 // verify config got passed in to the constructer 138 currentConfig := testClient.getConfig() 139 if currentConfig != testConfig { 140 t.Errorf("Initial test config is not correct, exp %+v, got %+v", testConfig, currentConfig) 141 } 142 143 // Test point updates 144 modifiedDescription := "updated description" 145 146 err = client.SendNodePoint(nc, currentConfig.ID, 147 data.Point{Type: "description", Text: modifiedDescription, Origin: "test"}, true) 148 149 if err != nil { 150 t.Errorf("Error sending point: %v", err) 151 } 152 153 time.Sleep(10 * time.Millisecond) 154 155 if testClient.getConfig().Description != modifiedDescription { 156 t.Error("Description not modified") 157 } 158 159 // Test edge point updates to node 160 modifiedRole := "user" 161 162 err = client.SendEdgePoint(nc, currentConfig.ID, currentConfig.Parent, 163 data.Point{Type: data.PointTypeRole, Text: modifiedRole, Origin: "test"}, true) 164 165 if err != nil { 166 t.Errorf("Error sending edge point: %v", err) 167 } 168 169 time.Sleep(10 * time.Millisecond) 170 171 if testClient.getConfig().Role != modifiedRole { 172 t.Error("Role not modified") 173 } 174 175 // Shutdown 176 m.Stop(nil) 177 178 select { 179 case <-testClient.stopped: 180 // all is well 181 case <-time.After(time.Second): 182 t.Fatal("Timeout waiting for client to be stopped") 183 } 184 185 select { 186 case <-managerStopped: 187 // all is well 188 case <-time.After(time.Second): 189 t.Fatal("manager did not stop") 190 } 191 192 select { 193 case <-startErr: 194 t.Fatal("Manager start returned an error: ", err) 195 default: 196 // all is well 197 } 198 } 199 200 func TestManagerAddRemove(t *testing.T) { 201 nc, root, stop, err := server.TestServer() 202 203 if err != nil { 204 t.Fatal("Error starting test server: ", err) 205 } 206 207 defer stop() 208 209 newClient := make(chan *testNodeClient) 210 211 // wrap newTestNodeClient so we can get a handle to new clients 212 var newTestNodeClientWrapper = func(nc *nats.Conn, config testNode) client.Client { 213 testClient := newTestNodeClient(nc, config) 214 newClient <- testClient 215 return testClient 216 } 217 218 // Create a new manager for nodes of type "testNode". The manager looks for new nodes under the 219 // root and if it finds any, it instantiates a new client, and sends point updates to it 220 m := client.NewManager(nc, newTestNodeClientWrapper, nil) 221 222 managerStopped := make(chan struct{}) 223 224 startErr := make(chan error) 225 226 go func() { 227 err := m.Run() 228 if err != nil { 229 startErr <- fmt.Errorf("manager start returned error: %v", err) 230 } 231 232 close(managerStopped) 233 }() 234 235 // populate with new testNode 236 testConfig := testNode{"ID-testnode", root.ID, "fancy test node", 8118, "admin"} 237 // populate database with test node 238 err = client.SendNodeType(nc, testConfig, "test") 239 if err != nil { 240 t.Fatal("Error sending node: ", err) 241 } 242 243 var testClient *testNodeClient 244 245 // wait for client to be created 246 select { 247 case testClient = <-newClient: 248 case <-time.After(time.Second * 10): 249 t.Fatal("Timeout waiting for new client to be created") 250 } 251 252 // verify config got passed in to the constructer 253 currentConfig := testClient.getConfig() 254 // ID was not populated when we originally created the node 255 testConfig.ID = currentConfig.ID 256 testConfig.Parent = currentConfig.Parent 257 if currentConfig != testConfig { 258 t.Errorf("Initial test config is not correct, exp %+v, got %+v", testConfig, currentConfig) 259 } 260 261 /* FIXME is there a better way to test this?? 262 // wait to make sure we don't create duplicate clients on each scan 263 select { 264 case testClient = <-newClient: 265 t.Fatal("duplicate client created") 266 case <-time.After(time.Second * 10): 267 // all is well 268 } 269 */ 270 271 // test deleting client 272 err = client.SendEdgePoint(nc, currentConfig.ID, currentConfig.Parent, 273 data.Point{Type: data.PointTypeTombstone, Value: 1, Origin: "test"}, true) 274 275 if err != nil { 276 t.Errorf("Error sending edge point: %v", err) 277 } 278 279 select { 280 case <-testClient.stopped: 281 // all is well 282 case <-time.After(time.Second * 10): 283 t.Fatal("Timeout waiting for client to be removed") 284 } 285 286 m.Stop(nil) 287 288 select { 289 case <-managerStopped: 290 // all is well 291 case <-time.After(time.Second * 10): 292 t.Fatal("manager did not stop") 293 } 294 295 select { 296 case <-startErr: 297 t.Fatal("Manager start returned an error: ", err) 298 default: 299 // all is well 300 } 301 } 302 303 type testX struct { 304 ID string `node:"id"` 305 Parent string `node:"parent"` 306 Description string `point:"description"` 307 Role string `edgepoint:"role"` 308 TestYs []testY `child:"testY"` 309 } 310 311 type testY struct { 312 ID string `node:"id"` 313 Parent string `node:"parent"` 314 Description string `point:"description"` 315 Role string `edgepoint:"role"` 316 } 317 318 type testXClient struct { 319 nc *nats.Conn 320 config testX 321 stop chan struct{} 322 stopped chan struct{} 323 newPoints chan client.NewPoints 324 newEdgePoints chan client.NewPoints 325 chGetConfig chan chan testX 326 } 327 328 func newTestXClient(nc *nats.Conn, config testX) *testXClient { 329 return &testXClient{ 330 nc: nc, 331 config: config, 332 stop: make(chan struct{}), 333 stopped: make(chan struct{}), 334 newPoints: make(chan client.NewPoints), 335 newEdgePoints: make(chan client.NewPoints), 336 chGetConfig: make(chan chan testX), 337 } 338 } 339 340 func (tnc *testXClient) Run() error { 341 for { 342 select { 343 case <-tnc.stop: 344 close(tnc.stopped) 345 return nil 346 case pts := <-tnc.newPoints: 347 err := data.MergePoints(pts.ID, pts.Points, &tnc.config) 348 if err != nil { 349 log.Println("error merging new points:", err) 350 } 351 case pts := <-tnc.newEdgePoints: 352 err := data.MergeEdgePoints(pts.ID, pts.Parent, pts.Points, &tnc.config) 353 if err != nil { 354 log.Println("error merging new points:", err) 355 } 356 case ch := <-tnc.chGetConfig: 357 ch <- tnc.config 358 } 359 } 360 } 361 362 func (tnc *testXClient) Stop(_ error) { 363 close(tnc.stop) 364 } 365 366 func (tnc *testXClient) Points(nodeID string, points []data.Point) { 367 tnc.newPoints <- client.NewPoints{nodeID, "", points} 368 } 369 370 func (tnc *testXClient) EdgePoints(nodeID, parentID string, points []data.Point) { 371 tnc.newEdgePoints <- client.NewPoints{nodeID, parentID, points} 372 } 373 374 func (tnc *testXClient) getConfig() testX { 375 result := make(chan testX) 376 tnc.chGetConfig <- result 377 return <-result 378 } 379 380 func TestManagerChildren(t *testing.T) { 381 nc, root, stop, err := server.TestServer() 382 383 if err != nil { 384 t.Fatal("Error starting test server: ", err) 385 } 386 387 defer stop() 388 389 // hydrate database with test data 390 testXConfig := testX{"ID-X", root.ID, "testX node", "", nil} 391 392 err = client.SendNodeType(nc, testXConfig, "test") 393 if err != nil { 394 t.Fatal("Error sending node: ", err) 395 } 396 397 // create child node 398 testYConfig := testY{"ID-Y", testXConfig.ID, "testY node", ""} 399 400 err = client.SendNodeType(nc, testYConfig, "test") 401 if err != nil { 402 t.Fatal("Error sending node: ", err) 403 } 404 405 var testClient *testXClient 406 newClient := make(chan *testXClient) 407 408 // wrap newTestNodeClient so we can stash a link to testClient 409 var newTestXClientWrapper = func(nc *nats.Conn, config testX) client.Client { 410 testClient := newTestXClient(nc, config) 411 newClient <- testClient 412 return testClient 413 } 414 415 // Create a new manager for nodes of type "testNode". The manager looks for new nodes under the 416 // root and if it finds any, it instantiates a new client, and sends point updates to it 417 m := client.NewManager(nc, newTestXClientWrapper, nil) 418 419 managerStopped := make(chan struct{}) 420 421 startErr := make(chan error) 422 423 go func() { 424 err := m.Run() 425 if err != nil { 426 startErr <- fmt.Errorf("manager start returned error: %v", err) 427 } 428 429 close(managerStopped) 430 }() 431 432 // wait for client to be created 433 select { 434 case testClient = <-newClient: 435 case <-time.After(time.Second): 436 t.Fatal("Test client not created") 437 } 438 439 // verify config got passed in to the constructer 440 currentConfig := testClient.getConfig() 441 442 if currentConfig.ID != testXConfig.ID { 443 t.Fatal("X ID not correct: ", currentConfig.ID) 444 } 445 446 if len(currentConfig.TestYs) < 1 { 447 t.Fatal("No TestYs") 448 } 449 450 if currentConfig.TestYs[0].ID != testYConfig.ID { 451 t.Fatal("Y ID not correct") 452 } 453 454 if currentConfig.TestYs[0].Description != testYConfig.Description { 455 t.Fatal("Y description not correct") 456 } 457 458 // Test child point updates 459 modifiedDescription := "updated description" 460 461 err = client.SendNodePoint(nc, testYConfig.ID, 462 data.Point{Type: "description", Text: modifiedDescription, Origin: "test"}, true) 463 464 if err != nil { 465 t.Errorf("Error sending point: %v", err) 466 } 467 468 time.Sleep(20 * time.Millisecond) 469 470 if testClient.getConfig().TestYs[0].Description != modifiedDescription { 471 t.Error("Child Description not modified") 472 } 473 474 // Test parent edge point updates 475 modifiedRole := "admin" 476 477 err = client.SendEdgePoint(nc, testXConfig.ID, testXConfig.Parent, 478 data.Point{Type: data.PointTypeRole, Text: modifiedRole, Origin: "test"}, true) 479 480 if err != nil { 481 t.Errorf("Error sending edge point: %v", err) 482 } 483 484 time.Sleep(10 * time.Millisecond) 485 486 if testClient.getConfig().Role != modifiedRole { 487 t.Error("Parent Role not modified") 488 } 489 490 // Test child edge point updates 491 modifiedRole = "user" 492 493 err = client.SendEdgePoint(nc, testYConfig.ID, testYConfig.Parent, 494 data.Point{Type: data.PointTypeRole, Text: modifiedRole, Origin: "test"}, true) 495 496 if err != nil { 497 t.Errorf("Error sending edge point: %v", err) 498 } 499 500 time.Sleep(10 * time.Millisecond) 501 502 if testClient.getConfig().TestYs[0].Role != modifiedRole { 503 t.Error("Child Role not modified") 504 } 505 506 // create 2nd child node 507 testYConfig2 := testY{"ID-Y2", testXConfig.ID, "testY node 2", ""} 508 509 err = client.SendNodeType(nc, testYConfig2, "test") 510 if err != nil { 511 t.Fatal("Error sending node: ", err) 512 } 513 514 // wait for client to be re-created 515 select { 516 case testClient = <-newClient: 517 case <-time.After(time.Second): 518 t.Fatal("Test client not re-created") 519 } 520 521 if len(testClient.getConfig().TestYs) < 2 { 522 t.Fatal("Not seeing new child") 523 } 524 525 // remove child node 526 err = client.SendEdgePoint(nc, testYConfig2.ID, testYConfig2.Parent, 527 data.Point{Type: data.PointTypeTombstone, Value: 1, Origin: "test"}, true) 528 529 if err != nil { 530 t.Errorf("Error sending edge point: %v", err) 531 } 532 533 // wait for client to be re-created 534 select { 535 case testClient = <-newClient: 536 case <-time.After(time.Second): 537 t.Fatal("Test client not re-created") 538 } 539 540 if len(testClient.getConfig().TestYs) != 1 { 541 t.Fatal("failed to remove child node") 542 } 543 544 // since this test does a lot of node modifications, let's use this as an opportunity 545 // to verify the database hashes 546 err = client.AdminStoreVerify(nc) 547 if err != nil { 548 t.Fatal("Verify failed: ", err) 549 } 550 } 551 552 // Some clients, like rules, rely on child nodes and we want to make sure if 553 // we add a lot of child nodes the client gets restarted after the last 554 // node is added. At one point we had a bug where there was not happening. 555 func TestManagerLotsChildren(t *testing.T) { 556 t.Skip("problem not debugged yet") 557 nc, root, stop, err := server.TestServer() 558 559 if err != nil { 560 t.Fatal("Error starting test server: ", err) 561 } 562 563 defer stop() 564 565 yCount := 1000 566 newClient := make(chan *testXClient, yCount*2) 567 568 // wrap newTestNodeClient so we can get access to test client 569 var newTestXClientWrapper = func(nc *nats.Conn, config testX) client.Client { 570 testClient := newTestXClient(nc, config) 571 newClient <- testClient 572 return testClient 573 } 574 575 // Create a new manager for nodes of type "testNode". The manager looks for new nodes under the 576 // root and if it finds any, it instantiates a new client, and sends point updates to it 577 m := client.NewManager(nc, newTestXClientWrapper, nil) 578 579 managerStopped := make(chan struct{}) 580 581 startErr := make(chan error) 582 583 go func() { 584 err := m.Run() 585 if err != nil { 586 startErr <- fmt.Errorf("manager start returned error: %v", err) 587 } 588 589 close(managerStopped) 590 }() 591 592 // hydrate database with test data 593 testXConfig := testX{"ID-X", root.ID, "testX node", "", nil} 594 595 err = client.SendNodeType(nc, testXConfig, "test") 596 if err != nil { 597 t.Fatal("Error sending node: ", err) 598 } 599 600 go func() { 601 for i := 0; i < yCount; i++ { 602 // create child node 603 testYConfig := testY{"ID-Y-" + strconv.Itoa(i), testXConfig.ID, "testY node " + strconv.Itoa(i), ""} 604 605 err = client.SendNodeType(nc, testYConfig, "test") 606 if err != nil { 607 fmt.Println("Error sending node: ", err) 608 } 609 } 610 }() 611 612 count := 0 613 614 fmt.Println("Start for loop") 615 timeout := time.NewTimer(time.Second * 15) 616 for { 617 select { 618 case <-timeout.C: 619 t.Fatalf("Timeout waiting, exp %v Ys, got %v", yCount, count) 620 case c := <-newClient: 621 count = len(c.config.TestYs) 622 // fmt.Println("Len of Ys: ", count) 623 if count == yCount { 624 fmt.Println("Yay, got right nuber of y's") 625 return 626 } 627 case err := <-startErr: 628 t.Fatal("Error starting client manager: ", err) 629 case <-managerStopped: 630 fmt.Println("Manager stopped") 631 } 632 } 633 634 }