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 }