github.com/simpleiot/simpleiot@v0.18.3/client/node.go (about) 1 package client 2 3 import ( 4 "errors" 5 "fmt" 6 "log" 7 "reflect" 8 "sort" 9 "time" 10 11 "github.com/goccy/go-yaml" 12 "github.com/google/uuid" 13 "github.com/nats-io/nats.go" 14 "github.com/simpleiot/simpleiot/data" 15 ) 16 17 // GetNodes over NATS. Maps to the `p.<id>.<parent>` NATS API. 18 // Returns data.ErrDocumentNotFound if node is not found. 19 // If parent is set to "none", the edge details are not included 20 // and the hash is blank. 21 // If parent is set to "all", then all living instances of the node are returned. 22 // If parent is set and id is "all", then all child nodes are returned. 23 // Parent can be set to "root" and id to "all" to fetch the root node(s). 24 func GetNodes(nc *nats.Conn, parent, id, typ string, includeDel bool) ([]data.NodeEdge, error) { 25 if parent == "" { 26 parent = "none" 27 } 28 29 if id == "" { 30 id = "all" 31 } 32 33 var requestPoints data.Points 34 35 if includeDel { 36 requestPoints = append(requestPoints, 37 data.Point{Type: data.PointTypeTombstone, Value: data.BoolToFloat(includeDel)}) 38 } 39 40 if typ != "" { 41 requestPoints = append(requestPoints, 42 data.Point{Type: data.PointTypeNodeType, Text: typ}) 43 } 44 45 reqData, err := requestPoints.ToPb() 46 if err != nil { 47 return nil, fmt.Errorf("Error encoding reqData: %v", err) 48 } 49 50 subject := fmt.Sprintf("nodes.%v.%v", parent, id) 51 nodeMsg, err := nc.Request(subject, reqData, time.Second*20) 52 if err != nil { 53 return []data.NodeEdge{}, err 54 } 55 56 nodes, err := data.PbDecodeNodesRequest(nodeMsg.Data) 57 58 if err != nil { 59 return []data.NodeEdge{}, err 60 } 61 62 return nodes, nil 63 } 64 65 // GetNodesType gets node of a custom type. 66 // id and parent work the same as [GetNodes] 67 // Deleted nodes are not included. 68 func GetNodesType[T any](nc *nats.Conn, parent, id string) ([]T, error) { 69 var x T 70 nodeType := data.ToCamelCase(reflect.TypeOf(x).Name()) 71 72 nodes, err := GetNodes(nc, parent, id, nodeType, false) 73 74 if err != nil { 75 return []T{}, err 76 } 77 78 // decode from NodeEdge to custom types 79 ret := make([]T, len(nodes)) 80 81 for i, n := range nodes { 82 err := data.Decode(data.NodeEdgeChildren{NodeEdge: n, Children: nil}, &ret[i]) 83 if err != nil { 84 log.Println("Error decode node in GetNodeType:", err) 85 } 86 } 87 88 return ret, nil 89 } 90 91 // GetRootNode returns the root node of the instance 92 func GetRootNode(nc *nats.Conn) (data.NodeEdge, error) { 93 rootNodes, err := GetNodes(nc, "root", "all", "", false) 94 95 if err != nil { 96 return data.NodeEdge{}, err 97 } 98 99 if len(rootNodes) == 0 { 100 return data.NodeEdge{}, data.ErrDocumentNotFound 101 } 102 103 return rootNodes[0], nil 104 } 105 106 // GetNodesForUser gets all nodes for a user 107 func GetNodesForUser(nc *nats.Conn, userID string) ([]data.NodeEdge, error) { 108 var none []data.NodeEdge 109 var ret []data.NodeEdge 110 userNodes, err := GetNodes(nc, "all", userID, "", false) 111 if err != nil { 112 return none, err 113 } 114 115 var getChildren func(id string) ([]data.NodeEdge, error) 116 117 // getNodesHelper recursively gets children of a node 118 getChildren = func(id string) ([]data.NodeEdge, error) { 119 var ret []data.NodeEdge 120 121 children, err := GetNodes(nc, id, "all", "", false) 122 if err != nil { 123 return nil, err 124 } 125 126 for _, c := range children { 127 grands, err := getChildren(c.ID) 128 if err != nil { 129 return nil, err 130 } 131 132 ret = append(ret, grands...) 133 } 134 135 ret = append(ret, children...) 136 137 return ret, nil 138 } 139 140 // go through parents of root nodes and recursively get all children 141 for _, un := range userNodes { 142 parents, err := GetNodes(nc, "all", un.Parent, "", false) 143 if err != nil { 144 return none, fmt.Errorf("Error getting root node: %v", err) 145 } 146 147 // The frontend expects the top level nodes to have Parent set 148 // to root 149 for i := range parents { 150 parents[i].Parent = "root" 151 } 152 153 ret = append(ret, parents...) 154 c, err := getChildren(un.Parent) 155 if err != nil { 156 return none, fmt.Errorf("Error getting children: %v", err) 157 } 158 ret = append(ret, c...) 159 } 160 161 ret = data.RemoveDuplicateNodesIDParent(ret) 162 163 return ret, nil 164 } 165 166 // SendNode is used to send a node to a nats server. Can be 167 // used to create nodes. 168 func SendNode(nc *nats.Conn, node data.NodeEdge, origin string) error { 169 170 if origin != "" { 171 for i := range node.Points { 172 if node.Points[i].Origin == "" { 173 node.Points[i].Origin = origin 174 } 175 } 176 177 for i := range node.EdgePoints { 178 if node.EdgePoints[i].Origin == "" { 179 node.EdgePoints[i].Origin = origin 180 } 181 } 182 } 183 184 // we need to send the edge points first if we are creating 185 // a new node, otherwise the upstream will detect an ophraned node 186 // and create a new edge to the root node 187 points := node.Points 188 189 if node.ID == "" { 190 return errors.New("ID must be set") 191 } 192 193 if node.Parent == "" || node.Parent == "none" { 194 return errors.New("Parent must be set when sending a node") 195 } 196 197 err := SendNodePoints(nc, node.ID, points, true) 198 199 if err != nil { 200 return fmt.Errorf("Error sending node: %v", err) 201 } 202 203 if len(node.EdgePoints) <= 0 { 204 // edge should always have a tombstone point, set to false for root node 205 node.EdgePoints = []data.Point{{Time: time.Now(), 206 Type: data.PointTypeTombstone, Origin: origin}} 207 } 208 209 node.EdgePoints = append(node.EdgePoints, data.Point{ 210 Type: data.PointTypeNodeType, 211 Text: node.Type, 212 Origin: origin, 213 }) 214 215 err = SendEdgePoints(nc, node.ID, node.Parent, node.EdgePoints, true) 216 if err != nil { 217 return fmt.Errorf("Error sending edge points: %w", err) 218 219 } 220 221 return nil 222 } 223 224 // SendNodeType is used to send a node to a nats server. Can be 225 // used to create nodes. 226 func SendNodeType[T any](nc *nats.Conn, node T, origin string) error { 227 ne, err := data.Encode(node) 228 if err != nil { 229 return err 230 } 231 232 return SendNode(nc, ne, origin) 233 } 234 235 func duplicateNodeHelper(nc *nats.Conn, node data.NodeEdge, newParent, origin string) error { 236 children, err := GetNodes(nc, node.ID, "all", "", false) 237 if err != nil { 238 return fmt.Errorf("GetNodes error: %v", err) 239 } 240 241 // create new ID for duplicate node 242 node.ID = uuid.New().String() 243 node.Parent = newParent 244 245 err = SendNode(nc, node, origin) 246 if err != nil { 247 return fmt.Errorf("SendNode error: %v", err) 248 } 249 250 for _, c := range children { 251 err := duplicateNodeHelper(nc, c, node.ID, origin) 252 if err != nil { 253 return err 254 } 255 } 256 257 return nil 258 } 259 260 // DuplicateNode is used to Duplicate a node and all its children 261 func DuplicateNode(nc *nats.Conn, id, newParent, origin string) error { 262 nodes, err := GetNodes(nc, "all", id, "", false) 263 if err != nil { 264 return fmt.Errorf("GetNode error: %v", err) 265 } 266 267 if len(nodes) < 1 { 268 return fmt.Errorf("No nodes returned") 269 } 270 271 node := nodes[0] 272 273 switch node.Type { 274 case data.NodeTypeUser: 275 lastName, _ := node.Points.Text(data.PointTypeLastName, "0") 276 lastName = lastName + " (Duplicate)" 277 node.AddPoint(data.Point{Type: data.PointTypeLastName, Key: "0", Text: lastName}) 278 default: 279 desc := node.Desc() + " (Duplicate)" 280 node.AddPoint(data.Point{Type: data.PointTypeDescription, Key: "0", Text: desc}) 281 } 282 283 return duplicateNodeHelper(nc, node, newParent, origin) 284 } 285 286 // DeleteNode removes a node from the specified parent node 287 func DeleteNode(nc *nats.Conn, id, parent string, origin string) error { 288 err := SendEdgePoint(nc, id, parent, data.Point{ 289 Type: data.PointTypeTombstone, 290 Value: 1, 291 Origin: origin, 292 }, true) 293 294 return err 295 } 296 297 // MoveNode moves a node from one parent to another 298 func MoveNode(nc *nats.Conn, id, oldParent, newParent, origin string) error { 299 if newParent == oldParent { 300 return errors.New("can't move node to itself") 301 } 302 303 // fetch the node because we need to know its type 304 nodes, err := GetNodes(nc, "all", id, "", true) 305 if err != nil { 306 return err 307 } 308 309 if len(nodes) < 1 { 310 return errors.New("Error fetching node to get type") 311 } 312 313 err = SendEdgePoints(nc, id, newParent, data.Points{ 314 {Type: data.PointTypeTombstone, Value: 0, Origin: origin}, 315 {Type: data.PointTypeNodeType, Text: nodes[0].Type, Origin: origin}, 316 }, true) 317 318 if err != nil { 319 return err 320 } 321 322 err = SendEdgePoint(nc, id, oldParent, data.Point{ 323 Type: data.PointTypeTombstone, 324 Value: 1, 325 }, true) 326 327 if err != nil { 328 return err 329 } 330 331 return nil 332 } 333 334 // MirrorNode adds a an existing node to a new parent. A node can have 335 // multiple parents. 336 func MirrorNode(nc *nats.Conn, id, newParent, origin string) error { 337 // fetch the node because we need to know its type 338 nodes, err := GetNodes(nc, "all", id, "", true) 339 if err != nil { 340 return err 341 } 342 343 if len(nodes) < 1 { 344 return errors.New("Error fetching node to get type") 345 } 346 347 err = SendEdgePoints(nc, id, newParent, data.Points{ 348 {Type: data.PointTypeTombstone, Value: 0, Origin: origin}, 349 {Type: data.PointTypeNodeType, Text: nodes[0].Type, Origin: origin}, 350 }, true) 351 352 return err 353 } 354 355 // NodeWatcher creates a node watcher. update() is called any time there is an update. 356 // Stop can be called to stop the watcher. get() can be called to get the current value. 357 func NodeWatcher[T any](nc *nats.Conn, id, parent string) (get func() T, stop func(), err error) { 358 stopCh := make(chan struct{}) 359 var current T 360 361 pointUpdates := make(chan []data.Point) 362 edgeUpdates := make(chan []data.Point) 363 364 // create subscriptions first so that we get any updates that might happen between the 365 // time we fetch node and start subscriptions 366 367 stopPointSub, err := SubscribePoints(nc, id, func(points []data.Point) { 368 pointUpdates <- points 369 }) 370 if err != nil { 371 return nil, nil, fmt.Errorf("Point subscribe failed: %v", err) 372 } 373 374 stopEdgeSub, err := SubscribeEdgePoints(nc, id, parent, func(points []data.Point) { 375 edgeUpdates <- points 376 }) 377 if err != nil { 378 return nil, nil, fmt.Errorf("Edge point subscribe failed: %v", err) 379 } 380 381 nodes, err := GetNodesType[T](nc, parent, id) 382 if err != nil { 383 if err != data.ErrDocumentNotFound { 384 return nil, nil, fmt.Errorf("Error getting node: %v", err) 385 } 386 // if document is not found, that is OK, points will populate it once they come in 387 } 388 389 // FIXME: we may still have a race condition where older point updates will overwrite 390 // a new update when we fetch the node. 391 if len(nodes) > 0 { 392 current = nodes[0] 393 } 394 395 getCurrent := make(chan chan T) 396 397 // main loop for watcher. All data access must go through the main 398 // loop to avoid race conditions. 399 go func() { 400 for { 401 select { 402 case <-stopCh: 403 return 404 case r := <-getCurrent: 405 r <- current 406 case pts := <-pointUpdates: 407 err := data.MergePoints(id, pts, ¤t) 408 if err != nil { 409 log.Println("NodeWatcher, error merging points:", err) 410 } 411 case pts := <-edgeUpdates: 412 err := data.MergeEdgePoints(id, parent, pts, ¤t) 413 if err != nil { 414 log.Println("NodeWatcher, error merging edge points:", err) 415 } 416 } 417 } 418 }() 419 420 return func() T { 421 ret := make(chan T) 422 getCurrent <- ret 423 return <-ret 424 }, func() { 425 stopPointSub() 426 stopEdgeSub() 427 close(stopCh) 428 }, nil 429 } 430 431 // SiotExport is the format used for exporting and importing data (currently YAML) 432 type SiotExport struct { 433 Nodes []data.NodeEdgeChildren 434 } 435 436 // ExportNodes is used to export nodes at a particular location to YAML 437 // The YAML format looks like: 438 // 439 // nodes: 440 // - id: inst1 441 // type: device 442 // parent: root 443 // points: 444 // - type: versionApp 445 // children: 446 // - id: d7f5bbe9-a300-4197-93fa-b8e5e07f683a 447 // type: user 448 // parent: inst1 449 // points: 450 // - type: firstName 451 // text: admin 452 // - type: lastName 453 // text: user 454 // - type: phone 455 // - type: email 456 // text: admin 457 // - type: pass 458 // text: admin 459 // 460 // Key="0" and Tombstone points with value set to 0 are removed from the export to make 461 // it easier to read. 462 func ExportNodes(nc *nats.Conn, id string) ([]byte, error) { 463 if id == "root" || id == "" { 464 root, err := GetRootNode(nc) 465 if err != nil { 466 return nil, fmt.Errorf("Error getting root node: %w", err) 467 } 468 id = root.ID 469 } 470 471 rootNodes, err := GetNodes(nc, "all", id, "", false) 472 if err != nil { 473 return nil, fmt.Errorf("Error getting root nodes: %w", err) 474 } 475 476 if len(rootNodes) < 1 { 477 return nil, fmt.Errorf("no root nodes returned") 478 } 479 480 var necNodes []data.NodeEdgeChildren 481 482 // we only export one node as there may be multiple mirrors of the node in the tree 483 nec := data.NodeEdgeChildren{NodeEdge: rootNodes[0], Children: nil} 484 err = exportNodesHelper(nc, &nec) 485 if err != nil { 486 return nil, err 487 } 488 489 necNodes = append(necNodes, nec) 490 491 ne := SiotExport{Nodes: necNodes} 492 493 return yaml.Marshal(ne) 494 } 495 496 func exportNodesHelper(nc *nats.Conn, node *data.NodeEdgeChildren) error { 497 // sort edge and node points 498 sort.Sort(data.ByTypeKey(node.Points)) 499 sort.Sort(data.ByTypeKey(node.EdgePoints)) 500 // reduce a little noise ... 501 // remove tombstone "0" edge points as that does not convey much information 502 // also remove and key="0" fields in points 503 for i, p := range node.Points { 504 if p.Key == "0" { 505 node.Points[i].Key = "" 506 } 507 } 508 509 for i, p := range node.EdgePoints { 510 if p.Key == "0" { 511 node.EdgePoints[i].Key = "" 512 } 513 } 514 515 // remove tombstone 0 edge points 516 i := 0 517 for _, p := range node.EdgePoints { 518 if p.Type == data.PointTypeTombstone && p.Value == 0 { 519 continue 520 } 521 node.EdgePoints[i] = p 522 i++ 523 } 524 525 node.EdgePoints = node.EdgePoints[:i] 526 527 children, err := GetNodes(nc, node.ID, "all", "", false) 528 if err != nil { 529 return fmt.Errorf("Error getting children: %w", err) 530 } 531 532 for _, c := range children { 533 nec := data.NodeEdgeChildren{NodeEdge: c, Children: nil} 534 err := exportNodesHelper(nc, &nec) 535 if err != nil { 536 return err 537 } 538 539 node.Children = append(node.Children, nec) 540 } 541 542 return nil 543 } 544 545 // ImportNodes is used to import nodes at a location in YAML format. New IDs 546 // are generated for all nodes unless preserve IDs is set to true. 547 // If there multiple references to the same ID, 548 // then an attempt is made to replace all of these with the new ID. This also 549 // allows you to use "friendly" ID names in hand generated YAML files. 550 func ImportNodes(nc *nats.Conn, parent string, yamlData []byte, origin string, preserveIDs bool) error { 551 // first make sure the parent node exists 552 var rootNode data.NodeEdge 553 if parent == "root" || parent == "" { 554 var err error 555 rootNode, err = GetRootNode(nc) 556 if err != nil { 557 return err 558 } 559 } else { 560 n, err := GetNodes(nc, "all", parent, "", false) 561 if err != nil { 562 return err 563 } 564 if len(n) < 1 { 565 return fmt.Errorf("Parent node \"%v\" not found", parent) 566 } 567 } 568 569 var imp SiotExport 570 571 err := yaml.Unmarshal(yamlData, &imp) 572 if err != nil { 573 return fmt.Errorf("Error parsing YAML data: %w", err) 574 } 575 576 var importHelper func(data.NodeEdgeChildren) error 577 importHelper = func(node data.NodeEdgeChildren) error { 578 err := SendNode(nc, node.NodeEdge, origin) 579 if err != nil { 580 return fmt.Errorf("Error sending node: %w", err) 581 } 582 583 for _, c := range node.Children { 584 err := importHelper(c) 585 if err != nil { 586 return err 587 } 588 } 589 return nil 590 } 591 592 if len(imp.Nodes) < 1 { 593 return fmt.Errorf("Error: imported data did not have any nodes") 594 } 595 596 // set parent of first node 597 imp.Nodes[0].Parent = parent 598 599 // append (import) to top level node description 600 for i, p := range imp.Nodes[0].Points { 601 if p.Type == data.PointTypeDescription { 602 imp.Nodes[0].Points[i].Text += " (import)" 603 } 604 } 605 606 if preserveIDs { 607 err := checkIDs(imp.Nodes[0], parent) 608 if err != nil { 609 return err 610 } 611 } else { 612 ReplaceIDs(&imp.Nodes[0], parent) 613 } 614 615 err = importHelper(imp.Nodes[0]) 616 617 // if we imported the root node, then we have to tombstone the old root node 618 if parent == "root" && rootNode.ID != imp.Nodes[0].ID { 619 err := DeleteNode(nc, rootNode.ID, parent, "import") 620 if err != nil { 621 return fmt.Errorf("Error deleting old root node: %w", err) 622 } 623 } 624 625 return err 626 } 627 628 func checkIDs(node data.NodeEdgeChildren, parent string) error { 629 if parent == "" { 630 return fmt.Errorf("parent must be specified") 631 } 632 633 if node.Parent != parent { 634 return fmt.Errorf("node parent %v does not match parent %v", node.Parent, parent) 635 } 636 637 if node.ID == "" { 638 return fmt.Errorf("ID cannot be blank") 639 } 640 641 for _, c := range node.Children { 642 err := checkIDs(c, node.ID) 643 if err != nil { 644 return err 645 } 646 } 647 648 return nil 649 } 650 651 // ReplaceIDs is used to replace IDs tree of nodes. 652 // If there multiple references to the same ID, 653 // then an attempt is made to replace all of these with the new ID. 654 // This function modifies the tree that is passed in. 655 // Replace IDs also updates the partent fields. 656 func ReplaceIDs(nodes *data.NodeEdgeChildren, parent string) { 657 // idMap is used to translate old IDs to new 658 idMap := make(map[string]string) 659 660 var replaceHelper func(*data.NodeEdgeChildren, string) 661 replaceHelper = func(n *data.NodeEdgeChildren, parent string) { 662 n.Parent = parent 663 // update node ID 664 var newID string 665 if n.ID == "" { 666 // always assign a new ID if blank 667 newID = uuid.New().String() 668 } else { 669 var ok bool 670 newID, ok = idMap[n.ID] 671 if !ok { 672 newID = uuid.New().String() 673 idMap[n.ID] = newID 674 } 675 } 676 n.ID = newID 677 678 // check for any points that might have node hashes 679 for i, p := range n.Points { 680 if p.Type == data.PointTypeNodeID { 681 if p.Text == "" { 682 continue 683 } 684 newID, ok := idMap[p.Text] 685 if !ok { 686 newID = uuid.New().String() 687 idMap[p.Text] = newID 688 } 689 n.Points[i].Text = newID 690 } 691 } 692 693 for i := range n.Children { 694 replaceHelper(&n.Children[i], n.ID) 695 } 696 } 697 698 replaceHelper(nodes, parent) 699 }