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  }