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

     1  package store
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"log"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/nats-io/nats.go"
    12  	"github.com/simpleiot/simpleiot/api"
    13  	"github.com/simpleiot/simpleiot/client"
    14  	"github.com/simpleiot/simpleiot/data"
    15  	"github.com/simpleiot/simpleiot/internal/pb"
    16  	"google.golang.org/protobuf/proto"
    17  )
    18  
    19  var reportMetricsPeriod = time.Minute
    20  
    21  // Store implements the SIOT NATS api
    22  type Store struct {
    23  	params        Params
    24  	nc            *nats.Conn
    25  	subscriptions map[string]*nats.Subscription
    26  	db            *DbSqlite
    27  	authorizer    api.Authorizer
    28  
    29  	// cycle metrics track how long it takes to handle a point
    30  	metricCycleNodePoint     *client.Metric
    31  	metricCycleNodeEdgePoint *client.Metric
    32  	metricCycleNode          *client.Metric
    33  	metricCycleNodeChildren  *client.Metric
    34  
    35  	// Pending counts how many points are being buffered by the NATS client
    36  	metricPendingNodePoint     *client.Metric
    37  	metricPendingNodeEdgePoint *client.Metric
    38  
    39  	chStop        chan struct{}
    40  	chStopMetrics chan struct{}
    41  	chWaitStart   chan struct{}
    42  }
    43  
    44  // Params are used to configure a store
    45  type Params struct {
    46  	File      string
    47  	AuthToken string
    48  	Server    string
    49  	Nc        *nats.Conn
    50  	// ID for the instance -- it is only used when initializing the store.
    51  	// ID must be unique. If ID is not set, then a UUID is generated.
    52  	ID string
    53  }
    54  
    55  // NewStore creates a new NATS client for handling SIOT requests
    56  func NewStore(p Params) (*Store, error) {
    57  	db, err := NewSqliteDb(p.File, p.ID)
    58  	if err != nil {
    59  		return nil, fmt.Errorf("Error opening db: %v", err)
    60  	}
    61  
    62  	// we don't have node ID yet, but need to init here so we can start
    63  	// collecting data
    64  
    65  	authorizer, err := api.NewKey(db.meta.JWTKey)
    66  	if err != nil {
    67  		return nil, fmt.Errorf("Error creating authorizer: %v", err)
    68  	}
    69  
    70  	log.Println("store connecting to nats server:", p.Server)
    71  	return &Store{
    72  		params:        p,
    73  		nc:            p.Nc,
    74  		db:            db,
    75  		authorizer:    authorizer,
    76  		subscriptions: make(map[string]*nats.Subscription),
    77  		chStop:        make(chan struct{}),
    78  		chStopMetrics: make(chan struct{}),
    79  		chWaitStart:   make(chan struct{}),
    80  		metricCycleNodePoint: client.NewMetric(p.Nc, "",
    81  			data.PointTypeMetricNatsCycleNodePoint, reportMetricsPeriod),
    82  		metricCycleNodeEdgePoint: client.NewMetric(p.Nc, "",
    83  			data.PointTypeMetricNatsCycleNodeEdgePoint, reportMetricsPeriod),
    84  		metricCycleNode: client.NewMetric(p.Nc, "",
    85  			data.PointTypeMetricNatsCycleNode, reportMetricsPeriod),
    86  		metricCycleNodeChildren: client.NewMetric(p.Nc, "",
    87  			data.PointTypeMetricNatsCycleNodeChildren, reportMetricsPeriod),
    88  	}, nil
    89  }
    90  
    91  // GetAuthorizer returns a type that can be used in JWT Auth mechanisms
    92  func (st *Store) GetAuthorizer() api.Authorizer {
    93  	return st.authorizer
    94  }
    95  
    96  // Run connects to NATS server and set up handlers for things we are interested in
    97  func (st *Store) Run() error {
    98  	nc := st.params.Nc
    99  	var err error
   100  	st.subscriptions["nodePoints"], err = nc.Subscribe("p.*", st.handleNodePoints)
   101  	if err != nil {
   102  		return fmt.Errorf("Subscribe node points error: %w", err)
   103  	}
   104  
   105  	st.subscriptions["edgePoints"], err = nc.Subscribe("p.*.*", st.handleEdgePoints)
   106  	if err != nil {
   107  		return fmt.Errorf("Subscribe edge points error: %w", err)
   108  	}
   109  
   110  	if st.subscriptions["nodes"], err = nc.Subscribe("nodes.*.*", st.handleNodesRequest); err != nil {
   111  		return fmt.Errorf("Subscribe node error: %w", err)
   112  	}
   113  
   114  	/*
   115  		if st.subscriptions["notifications"], err = nc.Subscribe("node.*.not", st.handleNotification); err != nil {
   116  			return fmt.Errorf("Subscribe notification error: %w", err)
   117  		}
   118  
   119  		if st.subscriptions["messages"], err = nc.Subscribe("node.*.msg", st.handleMessage); err != nil {
   120  			return fmt.Errorf("Subscribe message error: %w", err)
   121  		}
   122  	*/
   123  
   124  	if st.subscriptions["auth.user"], err = nc.Subscribe("auth.user", st.handleAuthUser); err != nil {
   125  		return fmt.Errorf("Subscribe auth error: %w", err)
   126  	}
   127  
   128  	if st.subscriptions["auth.getNatsURI"], err = nc.Subscribe("auth.getNatsURI", st.handleAuthGetNatsURI); err != nil {
   129  		return fmt.Errorf("Subscribe auth error: %w", err)
   130  	}
   131  
   132  	if st.subscriptions["admin.storeVerify"], err = nc.Subscribe("admin.storeVerify", st.handleStoreVerify); err != nil {
   133  		return fmt.Errorf("Subscribe dbVerify error: %w", err)
   134  	}
   135  
   136  	if st.subscriptions["admin.storeMaint"], err = nc.Subscribe("admin.storeMaint", st.handleStoreMaint); err != nil {
   137  		return fmt.Errorf("Subscribe dbMaint error: %w", err)
   138  	}
   139  
   140  done:
   141  	for {
   142  		select {
   143  		case <-st.chWaitStart:
   144  			// don't need to do anything as simply reading this
   145  			// channel will unblock the caller
   146  		case <-st.chStop:
   147  			log.Println("Store stopped")
   148  			break done
   149  		}
   150  	}
   151  
   152  	// clean up
   153  	for k := range st.subscriptions {
   154  		err := st.subscriptions[k].Unsubscribe()
   155  		if err != nil {
   156  			log.Printf("Error unsubscribing from %v: %v\n", k, err)
   157  		}
   158  	}
   159  
   160  	st.db.Close()
   161  
   162  	return nil
   163  }
   164  
   165  // Stop the store
   166  func (st *Store) Stop(_ error) {
   167  	close(st.chStop)
   168  }
   169  
   170  // WaitStart waits for store to start
   171  func (st *Store) WaitStart(ctx context.Context) error {
   172  	waitDone := make(chan struct{})
   173  
   174  	go func() {
   175  		// the following will block until the main store select
   176  		// loop starts
   177  		st.chWaitStart <- struct{}{}
   178  		close(waitDone)
   179  	}()
   180  
   181  	select {
   182  	case <-ctx.Done():
   183  		return errors.New("Store wait timeout or canceled")
   184  	case <-waitDone:
   185  		// all is well
   186  		return nil
   187  	}
   188  }
   189  
   190  // Reset the store by permanently wiping all data
   191  func (st *Store) Reset() error {
   192  	return st.db.reset()
   193  }
   194  
   195  // StartMetrics for various handling operations. Metrics are sent to the node ID given
   196  // FIXME, this can probably move to the node package for device nodes
   197  func (st *Store) StartMetrics(nodeID string) error {
   198  	st.metricCycleNodePoint.SetNodeID(nodeID)
   199  	st.metricCycleNodeEdgePoint.SetNodeID(nodeID)
   200  	st.metricCycleNode.SetNodeID(nodeID)
   201  	st.metricCycleNodeChildren.SetNodeID(nodeID)
   202  
   203  	st.metricPendingNodePoint = client.NewMetric(st.nc, nodeID,
   204  		data.PointTypeMetricNatsPendingNodePoint, reportMetricsPeriod)
   205  	st.metricPendingNodeEdgePoint = client.NewMetric(st.nc, nodeID,
   206  		data.PointTypeMetricNatsPendingNodeEdgePoint, reportMetricsPeriod)
   207  
   208  	t := time.NewTimer(time.Millisecond)
   209  
   210  	for {
   211  		select {
   212  		case <-st.chStopMetrics:
   213  			return errors.New("Store stopping metrics")
   214  
   215  		case <-t.C:
   216  			pendingNodePoints, _, err := st.subscriptions["nodePoints"].Pending()
   217  			if err != nil {
   218  				log.Println("Error getting pendingNodePoints:", err)
   219  			}
   220  
   221  			err = st.metricPendingNodePoint.AddSample(float64(pendingNodePoints))
   222  			if err != nil {
   223  				log.Println("Error handling metric:", err)
   224  			}
   225  
   226  			pendingEdgePoints, _, err := st.subscriptions["edgePoints"].Pending()
   227  			if err != nil {
   228  				log.Println("Error getting pendingEdgePoints:", err)
   229  			}
   230  
   231  			err = st.metricPendingNodeEdgePoint.AddSample(float64(pendingEdgePoints))
   232  			if err != nil {
   233  				log.Println("Error handling metric:", err)
   234  			}
   235  			t.Reset(time.Second * 10)
   236  		}
   237  	}
   238  }
   239  
   240  // StopMetrics ...
   241  func (st *Store) StopMetrics(_ error) {
   242  	close(st.chStopMetrics)
   243  }
   244  
   245  func (st *Store) handleNodePoints(msg *nats.Msg) {
   246  	start := time.Now()
   247  	defer func() {
   248  		t := time.Since(start).Milliseconds()
   249  		err := st.metricCycleNodePoint.AddSample(float64(t))
   250  		if err != nil {
   251  			log.Println("Error stopping metrics:", err)
   252  		}
   253  	}()
   254  
   255  	nodeID, points, err := client.DecodeNodePointsMsg(msg)
   256  
   257  	if err != nil {
   258  		fmt.Printf("Error decoding nats message: %v: %v", msg.Subject, err)
   259  		st.reply(msg.Reply, errors.New("error decoding node points subject"))
   260  		return
   261  	}
   262  
   263  	// write points to database
   264  	err = st.db.nodePoints(nodeID, points)
   265  
   266  	if err != nil {
   267  		// TODO track error stats
   268  		log.Printf("Error writing nodeID (%v) to Db: %v", nodeID, err)
   269  		log.Println("msg subject:", msg.Subject)
   270  		st.reply(msg.Reply, err)
   271  		return
   272  	}
   273  
   274  	// process point in upstream nodes
   275  	err = st.processPointsUpstream(nodeID, nodeID, points)
   276  	if err != nil {
   277  		// TODO track error stats
   278  		log.Println("Error processing point in upstream nodes:", err)
   279  	}
   280  
   281  	st.reply(msg.Reply, nil)
   282  }
   283  
   284  func (st *Store) handleEdgePoints(msg *nats.Msg) {
   285  	start := time.Now()
   286  	defer func() {
   287  		t := time.Since(start).Milliseconds()
   288  		err := st.metricCycleNodeEdgePoint.AddSample(float64(t))
   289  		if err != nil {
   290  			log.Println("handle edge point error:", err)
   291  		}
   292  	}()
   293  
   294  	nodeID, parentID, points, err := client.DecodeEdgePointsMsg(msg)
   295  
   296  	if err != nil {
   297  		fmt.Printf("Error decoding nats message: %v: %v", msg.Subject, err)
   298  		st.reply(msg.Reply, errors.New("error decoding edge points subject"))
   299  		return
   300  	}
   301  
   302  	// write points to database. Its important that we write to the DB
   303  	// before sending points upstream, or clients may do a rescan and not
   304  	// see the node is deleted.
   305  	err = st.db.edgePoints(nodeID, parentID, points)
   306  
   307  	if err != nil {
   308  		// TODO track error stats
   309  		log.Printf("Error writing edge points (%v:%v) to Db: %v", nodeID, parentID, err)
   310  		st.reply(msg.Reply, err)
   311  	}
   312  
   313  	// process point in upstream nodes. We need to do this before writing
   314  	// to DB, otherwise the point will not be sent upstream
   315  	err = st.processEdgePointsUpstream(nodeID, nodeID, parentID, points)
   316  	if err != nil {
   317  		// TODO track error stats
   318  		log.Println("Error processing point in upstream nodes:", err)
   319  	}
   320  
   321  	st.reply(msg.Reply, nil)
   322  }
   323  
   324  func (st *Store) handleNodesRequest(msg *nats.Msg) {
   325  	start := time.Now()
   326  	defer func() {
   327  		t := time.Since(start).Milliseconds()
   328  		err := st.metricCycleNode.AddSample(float64(t))
   329  		if err != nil {
   330  			log.Println("handleNodesRequest error:", err)
   331  		}
   332  	}()
   333  
   334  	resp := &pb.NodesRequest{}
   335  	var err error
   336  	var parent string
   337  	var nodeID string
   338  	var includeDel bool
   339  	var nodeType string
   340  	var nodes data.Nodes
   341  
   342  	chunks := strings.Split(msg.Subject, ".")
   343  	if len(chunks) < 3 {
   344  		resp.Error = fmt.Sprintf("Error in message subject: %v", msg.Subject)
   345  		goto handleNodeDone
   346  	}
   347  
   348  	parent = chunks[1]
   349  	nodeID = chunks[2]
   350  
   351  	if len(msg.Data) > 0 {
   352  		pts, err := data.PbDecodePoints(msg.Data)
   353  		if err != nil {
   354  			resp.Error = fmt.Sprintf("Error decoding points %v", err)
   355  			goto handleNodeDone
   356  		}
   357  
   358  		for _, p := range pts {
   359  			switch p.Type {
   360  			case data.PointTypeTombstone:
   361  				includeDel = data.FloatToBool(p.Value)
   362  			case data.PointTypeNodeType:
   363  				nodeType = p.Text
   364  			}
   365  		}
   366  	}
   367  
   368  	nodes, err = st.db.getNodes(nil, parent, nodeID, nodeType, includeDel)
   369  
   370  	if err != nil {
   371  		if err != data.ErrDocumentNotFound {
   372  			resp.Error = fmt.Sprintf("NATS handler: Error getting node %v from db: %v\n", nodeID, err)
   373  		} else {
   374  			resp.Error = data.ErrDocumentNotFound.Error()
   375  		}
   376  	}
   377  
   378  handleNodeDone:
   379  	resp.Nodes, err = nodes.ToPbNodes()
   380  	if err != nil {
   381  		resp.Error = fmt.Sprintf("Error pb encoding node: %v\n", err)
   382  	}
   383  
   384  	data, err := proto.Marshal(resp)
   385  	if err != nil {
   386  		log.Println("marshal error:", err)
   387  		return
   388  	}
   389  
   390  	err = st.nc.Publish(msg.Reply, data)
   391  	if err != nil {
   392  		log.Println("NATS: Error publishing response to node request:", err)
   393  	}
   394  }
   395  
   396  // TODO, maybe someday we should return error node instead of no data
   397  func (st *Store) handleAuthUser(msg *nats.Msg) {
   398  	var points data.Points
   399  	var err error
   400  	resp := &pb.NodesRequest{}
   401  
   402  	returnNothing := func() {
   403  		err = st.nc.Publish(msg.Reply, nil)
   404  		if err != nil {
   405  			log.Println("NATS: Error publishing response to auth.user")
   406  		}
   407  	}
   408  
   409  	if len(msg.Data) <= 0 {
   410  		log.Println("No data in auth.user")
   411  		returnNothing()
   412  		return
   413  	}
   414  
   415  	points, err = data.PbDecodePoints(msg.Data)
   416  	if err != nil {
   417  		log.Println("Error decoding auth.user params:", err)
   418  		returnNothing()
   419  		return
   420  	}
   421  
   422  	emailP, ok := points.Find(data.PointTypeEmail, "")
   423  	if !ok {
   424  		log.Println("Error, auth.user no email point")
   425  		returnNothing()
   426  		return
   427  	}
   428  
   429  	passP, ok := points.Find(data.PointTypePass, "")
   430  	if !ok {
   431  		log.Println("Error, auth.user no password point")
   432  		returnNothing()
   433  		return
   434  	}
   435  
   436  	nodes, err := st.db.userCheck(emailP.Text, passP.Text)
   437  
   438  	if err != nil || len(nodes) <= 0 {
   439  		log.Println("Error, invalid user")
   440  		returnNothing()
   441  		return
   442  	}
   443  
   444  	user, err := data.NodeToUser(nodes[0].ToNode())
   445  
   446  	token, err := st.authorizer.NewToken(user.ID)
   447  	if err != nil {
   448  		log.Println("Error creating token")
   449  		returnNothing()
   450  		return
   451  	}
   452  
   453  	nodes = append(nodes, data.NodeEdge{
   454  		Type: data.NodeTypeJWT,
   455  		Points: data.Points{
   456  			{
   457  				Type: data.PointTypeToken,
   458  				Text: token,
   459  				Key:  "0",
   460  			},
   461  		},
   462  	})
   463  
   464  	resp.Nodes, err = nodes.ToPbNodes()
   465  	if err != nil {
   466  		resp.Error = fmt.Sprintf("Error pb encoding node: %v\n", err)
   467  	}
   468  
   469  	data, err := proto.Marshal(resp)
   470  
   471  	err = st.nc.Publish(msg.Reply, data)
   472  	if err != nil {
   473  		log.Println("NATS: Error publishing response to node request:", err)
   474  	}
   475  }
   476  
   477  func (st *Store) handleAuthGetNatsURI(msg *nats.Msg) {
   478  	points := data.Points{
   479  		{Type: data.PointTypeURI, Text: st.params.Server},
   480  		{Type: data.PointTypeToken, Text: st.params.AuthToken},
   481  	}
   482  
   483  	data, err := points.ToPb()
   484  
   485  	if err != nil {
   486  		data = []byte(err.Error())
   487  	}
   488  
   489  	err = st.nc.Publish(msg.Reply, data)
   490  	if err != nil {
   491  		log.Println("NATS: Error publishing response to gets NATS URI request:", err)
   492  	}
   493  }
   494  
   495  func (st *Store) handleStoreVerify(msg *nats.Msg) {
   496  	var ret string
   497  	hashErr := st.db.verifyNodeHashes(false)
   498  	if hashErr != nil {
   499  		ret = hashErr.Error()
   500  	}
   501  
   502  	err := st.nc.Publish(msg.Reply, []byte(ret))
   503  	if err != nil {
   504  		log.Println("NATS: Error publishing response to node request:", err)
   505  	}
   506  }
   507  
   508  func (st *Store) handleStoreMaint(msg *nats.Msg) {
   509  	var ret string
   510  	hashErr := st.db.verifyNodeHashes(true)
   511  	if hashErr != nil {
   512  		ret = hashErr.Error()
   513  	}
   514  
   515  	err := st.nc.Publish(msg.Reply, []byte(ret))
   516  	if err != nil {
   517  		log.Println("NATS: Error publishing response to node request:", err)
   518  	}
   519  }
   520  
   521  // used for messages that want an ACK
   522  func (st *Store) reply(subject string, err error) {
   523  	if subject == "" {
   524  		// node is not expecting a reply
   525  		return
   526  	}
   527  
   528  	reply := ""
   529  
   530  	if err != nil {
   531  		reply = err.Error()
   532  	}
   533  
   534  	e := st.nc.Publish(subject, []byte(reply))
   535  	if e != nil {
   536  		log.Println("Error ack reply:", e)
   537  	}
   538  }
   539  
   540  func (st *Store) processPointsUpstream(upNodeID, nodeID string, points data.Points) error {
   541  	// at this point, the point update has already been written to the DB
   542  	sub := fmt.Sprintf("up.%v.%v", upNodeID, nodeID)
   543  
   544  	err := client.SendPoints(st.nc, sub, points, false)
   545  
   546  	if err != nil {
   547  		return err
   548  	}
   549  
   550  	if upNodeID == "none" {
   551  		// we are at the top, stop
   552  		return nil
   553  	}
   554  
   555  	ups, err := st.db.up(upNodeID, false)
   556  	if err != nil {
   557  		return err
   558  	}
   559  
   560  	for _, up := range ups {
   561  		err = st.processPointsUpstream(up, nodeID, points)
   562  		if err != nil {
   563  			log.Println("Rules -- error processing upstream node:", err)
   564  		}
   565  	}
   566  
   567  	/* FIXME needs to be move to client
   568  
   569  	if currentNodeID == nodeID {
   570  		// check if device node that it has not been orphaned
   571  		node, err := st.db.node(nodeID)
   572  		if err != nil {
   573  			log.Println("Error getting node:", err)
   574  		}
   575  
   576  		if node.Type == data.NodeTypeDevice {
   577  			hasUpstream := false
   578  			for _, e := range edges {
   579  				if !e.IsTombstone() {
   580  					hasUpstream = true
   581  				}
   582  			}
   583  
   584  			if !hasUpstream {
   585  				fmt.Println("STORE: orphaned node: ", node)
   586  				if len(edges) < 1 {
   587  					// create upstream edge
   588  					err := client.SendEdgePoint(st.nc, nodeID, "none", data.Point{
   589  						Type:  data.PointTypeTombstone,
   590  						Value: 0,
   591  					}, false)
   592  					if err != nil {
   593  						log.Println("Error sending edge point:", err)
   594  					}
   595  				} else {
   596  					// undelete existing edge
   597  					e := edges[0]
   598  					err := client.SendEdgePoint(st.nc, e.Down, e.Up, data.Point{
   599  						Type:  data.PointTypeTombstone,
   600  						Value: 0,
   601  					}, false)
   602  					if err != nil {
   603  						log.Println("Error sending edge point:", err)
   604  					}
   605  				}
   606  			}
   607  		}
   608  	}
   609  	*/
   610  
   611  	return nil
   612  }
   613  
   614  func (st *Store) processEdgePointsUpstream(upNodeID, nodeID, parentID string, points data.Points) error {
   615  	sub := fmt.Sprintf("up.%v.%v.%v", upNodeID, nodeID, parentID)
   616  
   617  	err := client.SendPoints(st.nc, sub, points, false)
   618  
   619  	if err != nil {
   620  		return err
   621  	}
   622  
   623  	if upNodeID == "none" {
   624  		// we are at the top, stop
   625  		return nil
   626  	}
   627  
   628  	ups, err := st.db.up(upNodeID, true)
   629  	if err != nil {
   630  		return err
   631  	}
   632  
   633  	for _, up := range ups {
   634  		err = st.processEdgePointsUpstream(up, nodeID, parentID, points)
   635  		if err != nil {
   636  			log.Println("Rules -- error processing upstream node:", err)
   637  		}
   638  	}
   639  
   640  	return nil
   641  }