github.com/simpleiot/simpleiot@v0.18.3/client/sync.go (about)

     1  package client
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"log"
     7  	"time"
     8  
     9  	"github.com/nats-io/nats.go"
    10  	"github.com/simpleiot/simpleiot/data"
    11  )
    12  
    13  // Sync represents an sync node config
    14  type Sync struct {
    15  	ID             string `node:"id"`
    16  	Parent         string `node:"parent"`
    17  	Description    string `point:"description"`
    18  	URI            string `point:"uri"`
    19  	AuthToken      string `point:"authToken"`
    20  	Period         int    `point:"period"`
    21  	Disabled       bool   `point:"disabled"`
    22  	SyncCount      int    `point:"syncCount"`
    23  	SyncCountReset bool   `point:"syncCountReset"`
    24  }
    25  
    26  type newEdge struct {
    27  	parent string
    28  	id     string
    29  	local  bool
    30  }
    31  
    32  // SyncClient is a SIOT client used to handle upstream connections
    33  type SyncClient struct {
    34  	nc                  *nats.Conn
    35  	ncLocal             *nats.Conn
    36  	ncRemote            *nats.Conn
    37  	rootLocal           data.NodeEdge
    38  	rootRemote          data.NodeEdge
    39  	config              Sync
    40  	stop                chan struct{}
    41  	newPoints           chan NewPoints
    42  	newEdgePoints       chan NewPoints
    43  	subRemoteNodePoints map[string]*nats.Subscription
    44  	subRemoteEdgePoints map[string]*nats.Subscription
    45  	subRemoteUp         *nats.Subscription
    46  	chConnected         chan bool
    47  	initialSub          bool
    48  	chNewEdge           chan newEdge
    49  }
    50  
    51  // NewSyncClient constructor
    52  func NewSyncClient(nc *nats.Conn, config Sync) Client {
    53  	return &SyncClient{
    54  		nc:                  nc,
    55  		config:              config,
    56  		stop:                make(chan struct{}),
    57  		newPoints:           make(chan NewPoints),
    58  		newEdgePoints:       make(chan NewPoints),
    59  		chConnected:         make(chan bool),
    60  		subRemoteNodePoints: make(map[string]*nats.Subscription),
    61  		subRemoteEdgePoints: make(map[string]*nats.Subscription),
    62  		chNewEdge:           make(chan newEdge),
    63  	}
    64  }
    65  
    66  // Run the main logic for this client and blocks until stopped
    67  func (up *SyncClient) Run() error {
    68  	// create a new NATs connection to the local server as we need to
    69  	// turn echo off
    70  	uri, token, err := GetNatsURI(up.nc)
    71  	if err != nil {
    72  		return fmt.Errorf("Error getting NATS URI: %v", err)
    73  	}
    74  
    75  	opts := EdgeOptions{
    76  		URI:       uri,
    77  		AuthToken: token,
    78  		NoEcho:    true,
    79  		Connected: func() {
    80  			log.Printf("Sync: %v: Local Connected: %v\n", up.config.Description, uri)
    81  		},
    82  		Disconnected: func() {
    83  			log.Printf("Sync: %v: Local Disconnected\n", up.config.Description)
    84  		},
    85  		Reconnected: func() {
    86  			log.Printf("Sync: %v: Local Reconnected\n", up.config.Description)
    87  		},
    88  		Closed: func() {
    89  			log.Printf("Sync: %v: Local Closed\n", up.config.Description)
    90  		},
    91  	}
    92  
    93  	up.ncLocal, err = EdgeConnect(opts)
    94  	if err != nil {
    95  		return fmt.Errorf("Error connection to local NATS: %v", err)
    96  	}
    97  
    98  	chLocalNodePoints := make(chan NewPoints)
    99  	chLocalEdgePoints := make(chan NewPoints)
   100  
   101  	subLocalNodePoints, err := up.ncLocal.Subscribe(SubjectNodeAllPoints(), func(msg *nats.Msg) {
   102  		nodeID, points, err := DecodeNodePointsMsg(msg)
   103  
   104  		if err != nil {
   105  			log.Println("Error decoding point:", err)
   106  			return
   107  		}
   108  
   109  		chLocalNodePoints <- NewPoints{ID: nodeID, Points: points}
   110  
   111  	})
   112  	if err != nil {
   113  		log.Println("SyncClient: error subscribing:", err)
   114  	}
   115  
   116  	subLocalEdgePoints, err := up.ncLocal.Subscribe(SubjectEdgeAllPoints(), func(msg *nats.Msg) {
   117  		nodeID, parentID, points, err := DecodeEdgePointsMsg(msg)
   118  
   119  		if err != nil {
   120  			log.Println("Error decoding point:", err)
   121  			return
   122  		}
   123  
   124  		chLocalEdgePoints <- NewPoints{ID: nodeID, Parent: parentID, Points: points}
   125  
   126  		for _, p := range points {
   127  			if p.Type == data.PointTypeTombstone && p.Value == 0 {
   128  				// a new node was likely created, make sure we watch it
   129  				up.chNewEdge <- newEdge{parent: parentID, id: nodeID, local: true}
   130  			}
   131  		}
   132  	})
   133  	if err != nil {
   134  		log.Println("SyncClient: error subscribing:", err)
   135  	}
   136  
   137  	checkPeriod := func() {
   138  		if up.config.Period < 1 {
   139  			up.config.Period = 20
   140  			points := data.Points{
   141  				{Type: data.PointTypePeriod, Value: float64(up.config.Period)},
   142  			}
   143  
   144  			err = SendPoints(up.nc, SubjectNodePoints(up.config.ID), points, false)
   145  			if err != nil {
   146  				log.Println("Error resetting sync sync count:", err)
   147  			}
   148  		}
   149  	}
   150  
   151  	checkPeriod()
   152  
   153  	syncTicker := time.NewTicker(time.Second * 10)
   154  	syncTicker.Stop()
   155  
   156  	connectTimer := time.NewTimer(time.Millisecond * 10)
   157  
   158  	up.rootLocal, err = GetRootNode(up.nc)
   159  	if err != nil {
   160  		return fmt.Errorf("Error getting root node: %v", err)
   161  	}
   162  
   163  	connected := false
   164  	up.initialSub = false
   165  
   166  done:
   167  	for {
   168  		select {
   169  		case <-up.stop:
   170  			log.Println("Stopping upstream client:", up.config.Description)
   171  			break done
   172  		case <-connectTimer.C:
   173  			err := up.connect()
   174  			if err != nil {
   175  				log.Printf("Sync connect failure: %v: %v\n",
   176  					up.config.Description, err)
   177  				connectTimer.Reset(30 * time.Second)
   178  			}
   179  		case <-syncTicker.C:
   180  			err := up.syncNode("root", up.rootLocal.ID)
   181  			if err != nil {
   182  				log.Println("Error syncing:", err)
   183  			}
   184  
   185  		case conn := <-up.chConnected:
   186  			connected = conn
   187  			if conn {
   188  				syncTicker.Reset(time.Duration(up.config.Period) * time.Second)
   189  				err := up.syncNode("root", up.rootLocal.ID)
   190  				if err != nil {
   191  					log.Println("Error syncing:", err)
   192  				}
   193  
   194  				if !up.initialSub {
   195  					// set up initial subscriptions to remote nodes
   196  					err = up.subscribeRemoteNode(up.rootLocal.Parent, up.rootLocal.ID)
   197  					if err != nil {
   198  						log.Println("Sync: initial sub failed:", err)
   199  					} else {
   200  						up.initialSub = true
   201  					}
   202  				}
   203  			} else {
   204  				syncTicker.Stop()
   205  				// the following is required in case a new server
   206  				// is set up which may have a new root
   207  				up.rootRemote = data.NodeEdge{}
   208  			}
   209  		case pts := <-chLocalNodePoints:
   210  			if connected {
   211  				err = SendNodePoints(up.ncRemote, pts.ID, pts.Points, false)
   212  				if err != nil {
   213  					log.Println("Error sending node points to remote system:", err)
   214  				}
   215  			}
   216  		case pts := <-chLocalEdgePoints:
   217  			if connected {
   218  				err = SendEdgePoints(up.ncRemote, pts.ID, pts.Parent, pts.Points, false)
   219  				if err != nil {
   220  					log.Println("Error sending edge points to remote system:", err)
   221  				}
   222  			}
   223  		case pts := <-up.newPoints:
   224  			err := data.MergePoints(pts.ID, pts.Points, &up.config)
   225  			if err != nil {
   226  				log.Println("error merging new points:", err)
   227  			}
   228  
   229  			for _, p := range pts.Points {
   230  				switch p.Type {
   231  				case data.PointTypeURI,
   232  					data.PointTypeAuthToken,
   233  					data.PointTypeDisabled:
   234  					// we need to restart the sync connection
   235  					up.disconnect()
   236  					connectTimer.Reset(10 * time.Millisecond)
   237  				case data.PointTypePeriod:
   238  					checkPeriod()
   239  					if connected {
   240  						syncTicker.Reset(time.Duration(up.config.Period) *
   241  							time.Second)
   242  					}
   243  				}
   244  			}
   245  
   246  			if up.config.SyncCountReset {
   247  				up.config.SyncCount = 0
   248  				up.config.SyncCountReset = false
   249  
   250  				points := data.Points{
   251  					{Type: data.PointTypeSyncCount, Value: 0},
   252  					{Type: data.PointTypeSyncCountReset, Value: 0},
   253  				}
   254  
   255  				err = SendPoints(up.nc, SubjectNodePoints(up.config.ID), points, false)
   256  				if err != nil {
   257  					log.Println("Error resetting sync sync count:", err)
   258  				}
   259  			}
   260  
   261  		case pts := <-up.newEdgePoints:
   262  			err := data.MergeEdgePoints(pts.ID, pts.Parent, pts.Points, &up.config)
   263  			if err != nil {
   264  				log.Println("error merging new points:", err)
   265  			}
   266  		case edge := <-up.chNewEdge:
   267  			if !edge.local {
   268  				// a new remote node was created, if it does not exist here,
   269  				// create it
   270  
   271  				// if parent is upstream root, then we don't worry about it
   272  				if edge.parent == up.rootRemote.ID {
   273  					break
   274  				}
   275  
   276  				nodes, err := GetNodes(up.ncLocal, edge.parent, edge.id, "", true)
   277  				if err != nil {
   278  					log.Println("Error getting local node:", err)
   279  					break
   280  				}
   281  
   282  				if len(nodes) > 0 {
   283  					// local node already exists, so don't do anything
   284  					break
   285  				}
   286  				// local node does not exist, so get the remote and send it
   287  			fetchAgain:
   288  				// edge points are sent first, so it may take a bit before we see
   289  				// the node points
   290  				time.Sleep(10 * time.Millisecond)
   291  				nodes, err = GetNodes(up.ncRemote, edge.parent, edge.id, "", true)
   292  				if err != nil {
   293  					log.Println("Error getting node:", err)
   294  					break
   295  				}
   296  				for _, n := range nodes {
   297  					// if type is not populated yet, try again
   298  					if n.Type == "" {
   299  						goto fetchAgain
   300  					}
   301  					err := up.sendNodesLocal(n)
   302  					if err != nil {
   303  						log.Println("Error chNewEdge sendNodesLocal:", err)
   304  					}
   305  				}
   306  			}
   307  
   308  			err = up.subscribeRemoteNode(edge.parent, edge.id)
   309  			if err != nil {
   310  				log.Println("Error subscribing to new edge:", err)
   311  			}
   312  		}
   313  	}
   314  
   315  	// clean up
   316  	err = subLocalNodePoints.Unsubscribe()
   317  	if err != nil {
   318  		log.Println("Error unsubscribing node points from local bus:", err)
   319  	}
   320  
   321  	err = subLocalEdgePoints.Unsubscribe()
   322  	if err != nil {
   323  		log.Println("Error unsubscribing edge points from local bus:", err)
   324  	}
   325  
   326  	up.disconnect()
   327  	up.ncLocal.Close()
   328  
   329  	return nil
   330  }
   331  
   332  // Stop sends a signal to the Run function to exit
   333  func (up *SyncClient) Stop(_ error) {
   334  	close(up.stop)
   335  }
   336  
   337  // Points is called by the Manager when new points for this
   338  // node are received.
   339  func (up *SyncClient) Points(nodeID string, points []data.Point) {
   340  	up.newPoints <- NewPoints{nodeID, "", points}
   341  }
   342  
   343  // EdgePoints is called by the Manager when new edge points for this
   344  // node are received.
   345  func (up *SyncClient) EdgePoints(nodeID, parentID string, points []data.Point) {
   346  	up.newEdgePoints <- NewPoints{nodeID, parentID, points}
   347  }
   348  
   349  func (up *SyncClient) connect() error {
   350  	if up.config.Disabled {
   351  		log.Printf("Sync %v disabled", up.config.Description)
   352  		return nil
   353  	}
   354  
   355  	opts := EdgeOptions{
   356  		URI:       up.config.URI,
   357  		AuthToken: up.config.AuthToken,
   358  		NoEcho:    true,
   359  		Connected: func() {
   360  			up.chConnected <- true
   361  			log.Printf("Sync: %v: Remote Connected: %v\n",
   362  				up.config.Description, up.config.URI)
   363  		},
   364  		Disconnected: func() {
   365  			up.chConnected <- false
   366  			log.Printf("Sync: %v: Remote Disconnected\n", up.config.Description)
   367  		},
   368  		Reconnected: func() {
   369  			up.chConnected <- true
   370  			log.Printf("Sync: %v: Remote Reconnected\n", up.config.Description)
   371  		},
   372  		Closed: func() {
   373  			log.Printf("Sync: %v: Remote Closed\n", up.config.Description)
   374  		},
   375  	}
   376  
   377  	var err error
   378  	up.ncRemote, err = EdgeConnect(opts)
   379  
   380  	if err != nil {
   381  		return fmt.Errorf("Error connection to upstream NATS: %v", err)
   382  	}
   383  
   384  	return nil
   385  }
   386  
   387  func (up *SyncClient) subscribeRemoteNodePoints(id string) error {
   388  	if _, ok := up.subRemoteNodePoints[id]; !ok {
   389  		var err error
   390  		up.subRemoteNodePoints[id], err = up.ncRemote.Subscribe(SubjectNodePoints(id), func(msg *nats.Msg) {
   391  			nodeID, points, err := DecodeNodePointsMsg(msg)
   392  			if err != nil {
   393  				log.Println("Error decoding point:", err)
   394  				return
   395  			}
   396  
   397  			err = SendNodePoints(up.ncLocal, nodeID, points, false)
   398  			if err != nil {
   399  				log.Println("Error sending node points to remote system:", err)
   400  			}
   401  		})
   402  
   403  		if err != nil {
   404  			return err
   405  		}
   406  	}
   407  
   408  	return nil
   409  }
   410  
   411  func (up *SyncClient) subscribeRemoteEdgePoints(parent, id string) error {
   412  	if _, ok := up.subRemoteEdgePoints[id]; !ok {
   413  		var err error
   414  		key := id + ":" + parent
   415  		up.subRemoteEdgePoints[key], err = up.ncRemote.Subscribe(SubjectEdgePoints(id, parent),
   416  			func(msg *nats.Msg) {
   417  				nodeID, parentID, points, err := DecodeEdgePointsMsg(msg)
   418  				if err != nil {
   419  					log.Println("Error decoding point:", err)
   420  					return
   421  				}
   422  
   423  				err = SendEdgePoints(up.ncLocal, nodeID, parentID, points, false)
   424  				if err != nil {
   425  					log.Println("Error sending edge points to remote system:", err)
   426  				}
   427  			})
   428  
   429  		if err != nil {
   430  			return err
   431  		}
   432  	}
   433  	return nil
   434  }
   435  
   436  func (up *SyncClient) subscribeRemoteNode(parent, id string) error {
   437  	err := up.subscribeRemoteNodePoints(id)
   438  	if err != nil {
   439  		return err
   440  	}
   441  
   442  	err = up.subscribeRemoteEdgePoints(parent, id)
   443  	if err != nil {
   444  		return err
   445  	}
   446  
   447  	// we walk through all local nodes and and subscribe to remote changes
   448  	children, err := GetNodes(up.ncLocal, id, "all", "", true)
   449  	if err != nil {
   450  		return err
   451  	}
   452  
   453  	for _, c := range children {
   454  		err := up.subscribeRemoteNode(c.Parent, c.ID)
   455  		if err != nil {
   456  			return err
   457  		}
   458  	}
   459  
   460  	return nil
   461  }
   462  
   463  func (up *SyncClient) disconnect() {
   464  	for key, sub := range up.subRemoteNodePoints {
   465  		err := sub.Unsubscribe()
   466  		if err != nil {
   467  			log.Println("Error unsubscribing from remote:", err)
   468  		}
   469  		delete(up.subRemoteNodePoints, key)
   470  	}
   471  
   472  	for key, sub := range up.subRemoteEdgePoints {
   473  		err := sub.Unsubscribe()
   474  		if err != nil {
   475  			log.Println("Error unsubscribing from remote:", err)
   476  		}
   477  		delete(up.subRemoteEdgePoints, key)
   478  	}
   479  
   480  	up.initialSub = false
   481  	if up.subRemoteUp != nil {
   482  		err := up.subRemoteUp.Unsubscribe()
   483  		if err != nil {
   484  			log.Println("subRemoteUp.Unsubscribe() error:", err)
   485  		}
   486  		up.subRemoteUp = nil
   487  	}
   488  
   489  	if up.ncRemote != nil {
   490  		up.ncRemote.Close()
   491  		up.ncRemote = nil
   492  		up.rootRemote = data.NodeEdge{}
   493  	}
   494  }
   495  
   496  // sendNodesRemote is used to send node and children over nats
   497  // from one NATS server to another. Typically from the current instance
   498  // to an upstream.
   499  func (up *SyncClient) sendNodesRemote(node data.NodeEdge) error {
   500  	if node.Parent == "root" {
   501  		node.Parent = up.rootRemote.ID
   502  	}
   503  
   504  	err := SendNode(up.ncRemote, node, up.config.ID)
   505  	if err != nil {
   506  		return err
   507  	}
   508  
   509  	// process child nodes
   510  	childNodes, err := GetNodes(up.nc, node.ID, "all", "", false)
   511  	if err != nil {
   512  		return fmt.Errorf("Error getting node children: %v", err)
   513  	}
   514  
   515  	for _, childNode := range childNodes {
   516  		err := up.sendNodesRemote(childNode)
   517  
   518  		if err != nil {
   519  			return fmt.Errorf("Error sending child node: %v", err)
   520  		}
   521  	}
   522  
   523  	return nil
   524  }
   525  
   526  // sendNodesLocal is used to send node and children over nats
   527  // from one NATS server to another. Typically from the current instance
   528  // to an upstream.
   529  func (up *SyncClient) sendNodesLocal(node data.NodeEdge) error {
   530  	err := SendNode(up.ncLocal, node, up.config.ID)
   531  	if err != nil {
   532  		return err
   533  	}
   534  
   535  	// process child nodes
   536  	childNodes, err := GetNodes(up.nc, node.ID, "all", "", false)
   537  	if err != nil {
   538  		return fmt.Errorf("Error getting node children: %v", err)
   539  	}
   540  
   541  	for _, childNode := range childNodes {
   542  		err := up.sendNodesLocal(childNode)
   543  
   544  		if err != nil {
   545  			return fmt.Errorf("Error sending child node: %v", err)
   546  		}
   547  	}
   548  
   549  	return nil
   550  }
   551  
   552  func (up *SyncClient) syncNode(parent, id string) error {
   553  	var err error
   554  	if up.rootRemote.ID == "" {
   555  		up.rootRemote, err = GetRootNode(up.ncRemote)
   556  		if err != nil {
   557  			log.Printf("Sync: %v, error getting upstream root: %v\n", up.config.Description, err)
   558  			return fmt.Errorf("Error getting upstream root: %v", err)
   559  		}
   560  	}
   561  
   562  	if up.subRemoteUp == nil {
   563  		subject := fmt.Sprintf("up.%v.*.*", up.rootLocal.ID)
   564  		up.subRemoteUp, err = up.ncRemote.Subscribe(subject, func(msg *nats.Msg) {
   565  			_, id, parent, points, err := DecodeUpEdgePointsMsg(msg)
   566  			if err != nil {
   567  				log.Println("Error decoding remote up points:", err)
   568  			} else {
   569  				for _, p := range points {
   570  					if p.Type == data.PointTypeTombstone &&
   571  						p.Value == 0 {
   572  						// we have a new node
   573  						up.chNewEdge <- newEdge{
   574  							parent: parent, id: id}
   575  					}
   576  				}
   577  			}
   578  		})
   579  
   580  		if err != nil {
   581  			log.Println("Error subscribing to remote up...:", err)
   582  		}
   583  	}
   584  
   585  	// Why do we do this?
   586  	if parent == "root" {
   587  		parent = "all"
   588  	}
   589  
   590  	nodeLocals, err := GetNodes(up.nc, parent, id, "", true)
   591  	if err != nil {
   592  		return fmt.Errorf("Error getting local node: %v", err)
   593  	}
   594  
   595  	if len(nodeLocals) == 0 {
   596  		return errors.New("local nodes not found")
   597  	}
   598  
   599  	nodeLocal := nodeLocals[0]
   600  
   601  	nodeUps, upErr := GetNodes(up.ncRemote, parent, id, "", true)
   602  	if upErr != nil {
   603  		if upErr != data.ErrDocumentNotFound {
   604  			return fmt.Errorf("Error getting upstream root node: %v", upErr)
   605  		}
   606  	}
   607  
   608  	var nodeUp data.NodeEdge
   609  
   610  	nodeDeleted := false
   611  	nodeFound := len(nodeUps) > 0
   612  
   613  	if nodeFound {
   614  		nodeDeleted = true
   615  		for _, n := range nodeUps {
   616  			ts, _ := n.IsTombstone()
   617  			if !ts {
   618  				nodeDeleted = false
   619  				break
   620  			}
   621  		}
   622  	}
   623  
   624  	if nodeDeleted {
   625  		nodeUp = nodeUps[0]
   626  		// restore a node on the upstream
   627  		// update the local tombstone timestamp so it is newer than the remote tombstone timestamp
   628  		log.Printf("Sync: undeleting remote node: %v:%v\n", nodeUp.Parent, nodeUp.ID)
   629  		pTS := data.Point{Time: time.Now(), Type: data.PointTypeTombstone, Value: 0}
   630  		err := SendEdgePoint(up.ncRemote, nodeUp.ID, nodeUp.Parent, pTS, true)
   631  		if err != nil {
   632  			return fmt.Errorf("Error undeleting upstream node: %v", err)
   633  		}
   634  
   635  		// FIXME, remove this return
   636  		return nil
   637  	}
   638  
   639  	if !nodeFound {
   640  		log.Printf("Sync node %v does not exist, sending\n", nodeLocal.Desc())
   641  		err := up.sendNodesRemote(nodeLocal)
   642  		if err != nil {
   643  			return fmt.Errorf("Error sending node upstream: %w", err)
   644  		}
   645  
   646  		err = up.subscribeRemoteNode(nodeLocal.ID, nodeLocal.Parent)
   647  		if err != nil {
   648  			return fmt.Errorf("Error subscribing to node changes: %w", err)
   649  		}
   650  
   651  		return nil
   652  	}
   653  
   654  	nodeUp = nodeUps[0]
   655  
   656  	if nodeLocal.ID == up.rootLocal.ID {
   657  		// we need to back out the edge points from the hash as don't want to sync those
   658  		for _, p := range nodeUp.EdgePoints {
   659  			nodeUp.Hash ^= p.CRC()
   660  		}
   661  
   662  		for _, p := range nodeLocal.EdgePoints {
   663  			nodeLocal.Hash ^= p.CRC()
   664  		}
   665  	}
   666  
   667  	if nodeUp.Hash == nodeLocal.Hash {
   668  		// we're good!
   669  		return nil
   670  	}
   671  
   672  	// only increment count once during sync
   673  	if nodeLocal.ID == up.rootLocal.ID {
   674  		up.config.SyncCount++
   675  		points := data.Points{
   676  			{Type: data.PointTypeSyncCount, Value: float64(up.config.SyncCount)},
   677  		}
   678  
   679  		err = SendPoints(up.nc, SubjectNodePoints(up.config.ID), points, false)
   680  		if err != nil {
   681  			log.Println("Error resetting sync sync count:", err)
   682  		}
   683  	}
   684  
   685  	log.Printf("sync %v: syncing node: %v, hash up: 0x%x, down: 0x%x ",
   686  		up.config.Description,
   687  		nodeLocal.Desc(),
   688  		nodeUp.Hash, nodeLocal.Hash)
   689  
   690  	// first compare node points
   691  	// key in below map is the index of the point in the upstream node
   692  	upstreamProcessed := make(map[int]bool)
   693  
   694  	for _, p := range nodeLocal.Points {
   695  		found := false
   696  		for i, pUp := range nodeUp.Points {
   697  			if p.IsMatch(pUp.Type, pUp.Key) {
   698  				found = true
   699  				upstreamProcessed[i] = true
   700  				if p.Time.After(pUp.Time) {
   701  					// need to send point upstream
   702  					err := SendNodePoint(up.ncRemote, nodeUp.ID, p, true)
   703  					if err != nil {
   704  						log.Println("Error syncing point upstream:", err)
   705  					}
   706  				} else if p.Time.Before(pUp.Time) {
   707  					// need to update point locally
   708  					err := SendNodePoint(up.nc, nodeLocal.ID, pUp, true)
   709  					if err != nil {
   710  						log.Println("Error syncing point from upstream:", err)
   711  					}
   712  				}
   713  			}
   714  		}
   715  
   716  		if !found {
   717  			err := SendNodePoint(up.ncRemote, nodeUp.ID, p, true)
   718  			if err != nil {
   719  				log.Println("Error sending point:", err)
   720  			}
   721  		}
   722  	}
   723  
   724  	// check for any points that do not exist locally
   725  	for i, pUp := range nodeUp.Points {
   726  		if _, ok := upstreamProcessed[i]; !ok {
   727  			err := SendNodePoint(up.nc, nodeLocal.ID, pUp, true)
   728  			if err != nil {
   729  				log.Println("Error syncing point from upstream:", err)
   730  			}
   731  		}
   732  	}
   733  
   734  	// now compare edge points
   735  	// key in below map is the index of the point in the upstream node
   736  	upstreamProcessed = make(map[int]bool)
   737  
   738  	// only check edge points if we are not the root node
   739  	if nodeLocal.ID != up.rootLocal.ID {
   740  		for _, p := range nodeLocal.EdgePoints {
   741  			found := false
   742  			for i, pUp := range nodeUp.EdgePoints {
   743  				if p.IsMatch(pUp.Type, pUp.Key) {
   744  					found = true
   745  					upstreamProcessed[i] = true
   746  					if p.Time.After(pUp.Time) {
   747  						// need to send point upstream
   748  						err := SendEdgePoint(up.ncRemote, nodeUp.ID, nodeUp.Parent, p, true)
   749  						if err != nil {
   750  							log.Println("Error syncing point upstream:", err)
   751  						}
   752  					} else if p.Time.Before(pUp.Time) {
   753  						// need to update point locally
   754  						err := SendEdgePoint(up.nc, nodeLocal.ID, nodeLocal.Parent, pUp, true)
   755  						if err != nil {
   756  							log.Println("Error syncing point from upstream:", err)
   757  						}
   758  					}
   759  				}
   760  			}
   761  
   762  			if !found {
   763  				err := SendEdgePoint(up.ncRemote, nodeUp.ID, nodeUp.Parent, p, true)
   764  				if err != nil {
   765  					log.Println("Error sending point:", err)
   766  				}
   767  			}
   768  		}
   769  
   770  		// check for any points that do not exist locally
   771  		for i, pUp := range nodeUp.EdgePoints {
   772  			if _, ok := upstreamProcessed[i]; !ok {
   773  				err := SendEdgePoint(up.nc, nodeLocal.ID, nodeLocal.Parent, pUp, true)
   774  				if err != nil {
   775  					log.Println("Error syncing edge point from upstream:", err)
   776  				}
   777  			}
   778  		}
   779  	}
   780  
   781  	// sync child nodes
   782  	children, err := GetNodes(up.ncLocal, nodeLocal.ID, "all", "", false)
   783  	if err != nil {
   784  		return fmt.Errorf("Error getting local node children: %v", err)
   785  	}
   786  
   787  	// FIXME optimization we get the edges here and not the full child node
   788  	upChildren, err := GetNodes(up.ncRemote, nodeUp.ID, "all", "", false)
   789  	if err != nil {
   790  		return fmt.Errorf("Error getting upstream node children: %v", err)
   791  	}
   792  
   793  	// map index is index of upChildren
   794  	upChildProcessed := make(map[int]bool)
   795  
   796  	for _, child := range children {
   797  		found := false
   798  		for i, upChild := range upChildren {
   799  			if child.ID == upChild.ID {
   800  				found = true
   801  				upChildProcessed[i] = true
   802  				if child.Hash != upChild.Hash {
   803  					err := up.syncNode(nodeLocal.ID, child.ID)
   804  					if err != nil {
   805  						fmt.Println("Error syncing node: ", err)
   806  					}
   807  				}
   808  			}
   809  		}
   810  
   811  		if !found {
   812  			// need to send node upstream
   813  			err := up.sendNodesRemote(child)
   814  			if err != nil {
   815  				log.Println("Error sending node upstream:", err)
   816  			}
   817  
   818  			err = up.subscribeRemoteNode(child.Parent, child.ID)
   819  			if err != nil {
   820  				log.Println("Error subscribing to upstream:", err)
   821  			}
   822  		}
   823  	}
   824  
   825  	for i, upChild := range upChildren {
   826  		if _, ok := upChildProcessed[i]; !ok {
   827  			err := up.sendNodesLocal(upChild)
   828  			if err != nil {
   829  				log.Println("Error getting node from upstream:", err)
   830  			}
   831  			err = up.subscribeRemoteNode(upChild.Parent, upChild.ID)
   832  			if err != nil {
   833  				log.Println("Error subscribing to upstream:", err)
   834  			}
   835  		}
   836  	}
   837  
   838  	return nil
   839  }