infrahub-server 1.2.8__py3-none-any.whl → 1.2.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- infrahub/cli/db.py +14 -5
- infrahub/computed_attribute/tasks.py +33 -12
- infrahub/core/branch/tasks.py +0 -2
- infrahub/core/diff/calculator.py +4 -3
- infrahub/core/diff/combiner.py +1 -2
- infrahub/core/diff/coordinator.py +44 -28
- infrahub/core/diff/data_check_synchronizer.py +3 -2
- infrahub/core/diff/enricher/hierarchy.py +38 -27
- infrahub/core/diff/ipam_diff_parser.py +5 -4
- infrahub/core/diff/merger/merger.py +20 -18
- infrahub/core/diff/model/field_specifiers_map.py +64 -0
- infrahub/core/diff/model/path.py +55 -58
- infrahub/core/diff/parent_node_adder.py +14 -16
- infrahub/core/diff/query/drop_nodes.py +42 -0
- infrahub/core/diff/query/field_specifiers.py +8 -7
- infrahub/core/diff/query/filters.py +15 -1
- infrahub/core/diff/query/save.py +3 -0
- infrahub/core/diff/query_parser.py +49 -52
- infrahub/core/diff/repository/deserializer.py +36 -23
- infrahub/core/diff/repository/repository.py +31 -12
- infrahub/core/graph/__init__.py +1 -1
- infrahub/core/initialization.py +23 -7
- infrahub/core/migrations/graph/__init__.py +2 -0
- infrahub/core/migrations/graph/m014_remove_index_attr_value.py +4 -2
- infrahub/core/migrations/graph/m027_delete_isolated_nodes.py +50 -0
- infrahub/core/query/branch.py +27 -17
- infrahub/core/query/diff.py +65 -38
- infrahub/core/query/node.py +15 -4
- infrahub/core/query/relationship.py +17 -3
- infrahub/core/query/subquery.py +2 -2
- infrahub/core/schema/schema_branch.py +3 -0
- infrahub/core/validators/uniqueness/query.py +24 -8
- infrahub/database/__init__.py +0 -7
- infrahub/database/memgraph.py +0 -12
- infrahub/database/neo4j.py +0 -12
- infrahub/graphql/mutations/computed_attribute.py +5 -1
- infrahub/graphql/queries/diff/tree.py +2 -1
- infrahub/server.py +1 -5
- infrahub/trigger/models.py +11 -1
- infrahub/trigger/setup.py +38 -13
- infrahub/trigger/tasks.py +1 -4
- infrahub/workflows/initialization.py +1 -3
- {infrahub_server-1.2.8.dist-info → infrahub_server-1.2.9.dist-info}/METADATA +1 -1
- {infrahub_server-1.2.8.dist-info → infrahub_server-1.2.9.dist-info}/RECORD +49 -47
- infrahub_testcontainers/container.py +1 -0
- infrahub_testcontainers/docker-compose.test.yml +3 -1
- infrahub/database/manager.py +0 -15
- {infrahub_server-1.2.8.dist-info → infrahub_server-1.2.9.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.2.8.dist-info → infrahub_server-1.2.9.dist-info}/WHEEL +0 -0
- {infrahub_server-1.2.8.dist-info → infrahub_server-1.2.9.dist-info}/entry_points.txt +0 -0
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from collections import defaultdict
|
|
4
3
|
from dataclasses import dataclass, field
|
|
5
4
|
from typing import TYPE_CHECKING, Any
|
|
6
5
|
from uuid import uuid4
|
|
@@ -15,6 +14,7 @@ from infrahub.core.constants import (
|
|
|
15
14
|
from infrahub.core.constants.database import DatabaseEdgeType
|
|
16
15
|
from infrahub.core.timestamp import Timestamp
|
|
17
16
|
|
|
17
|
+
from .model.field_specifiers_map import NodeFieldSpecifierMap
|
|
18
18
|
from .model.path import (
|
|
19
19
|
DatabasePath,
|
|
20
20
|
DiffAttribute,
|
|
@@ -23,6 +23,7 @@ from .model.path import (
|
|
|
23
23
|
DiffRelationship,
|
|
24
24
|
DiffRoot,
|
|
25
25
|
DiffSingleRelationship,
|
|
26
|
+
NodeIdentifier,
|
|
26
27
|
)
|
|
27
28
|
|
|
28
29
|
if TYPE_CHECKING:
|
|
@@ -394,8 +395,7 @@ class DiffRelationshipIntermediate:
|
|
|
394
395
|
@dataclass
|
|
395
396
|
class DiffNodeIntermediate(TrackedStatusUpdates):
|
|
396
397
|
force_action: DiffAction | None
|
|
397
|
-
|
|
398
|
-
kind: str
|
|
398
|
+
identifier: NodeIdentifier
|
|
399
399
|
db_id: str
|
|
400
400
|
from_time: Timestamp
|
|
401
401
|
status: RelationshipStatus
|
|
@@ -403,6 +403,14 @@ class DiffNodeIntermediate(TrackedStatusUpdates):
|
|
|
403
403
|
# {(name, identifier): DiffRelationshipIntermediate}
|
|
404
404
|
relationships_by_identifier: dict[tuple[str, str], DiffRelationshipIntermediate] = field(default_factory=dict)
|
|
405
405
|
|
|
406
|
+
@property
|
|
407
|
+
def uuid(self) -> str:
|
|
408
|
+
return self.identifier.uuid
|
|
409
|
+
|
|
410
|
+
@property
|
|
411
|
+
def kind(self) -> str:
|
|
412
|
+
return self.identifier.kind
|
|
413
|
+
|
|
406
414
|
def to_diff_node(self, from_time: Timestamp, include_unchanged: bool) -> DiffNode:
|
|
407
415
|
attributes = []
|
|
408
416
|
for attr in self.attributes_by_name.values():
|
|
@@ -424,8 +432,7 @@ class DiffNodeIntermediate(TrackedStatusUpdates):
|
|
|
424
432
|
if self.force_action:
|
|
425
433
|
action = self.force_action
|
|
426
434
|
return DiffNode(
|
|
427
|
-
|
|
428
|
-
kind=self.kind,
|
|
435
|
+
identifier=self.identifier,
|
|
429
436
|
changed_at=changed_at,
|
|
430
437
|
action=action,
|
|
431
438
|
attributes=attributes,
|
|
@@ -441,11 +448,11 @@ class DiffNodeIntermediate(TrackedStatusUpdates):
|
|
|
441
448
|
class DiffRootIntermediate:
|
|
442
449
|
uuid: str
|
|
443
450
|
branch: str
|
|
444
|
-
|
|
451
|
+
nodes_by_identifier: dict[NodeIdentifier, DiffNodeIntermediate] = field(default_factory=dict)
|
|
445
452
|
|
|
446
453
|
def to_diff_root(self, from_time: Timestamp, to_time: Timestamp, include_unchanged: bool) -> DiffRoot:
|
|
447
454
|
nodes = []
|
|
448
|
-
for node in self.
|
|
455
|
+
for node in self.nodes_by_identifier.values():
|
|
449
456
|
if node.is_empty:
|
|
450
457
|
continue
|
|
451
458
|
diff_node = node.to_diff_node(from_time=from_time, include_unchanged=include_unchanged)
|
|
@@ -462,7 +469,7 @@ class DiffQueryParser:
|
|
|
462
469
|
schema_manager: SchemaManager,
|
|
463
470
|
from_time: Timestamp,
|
|
464
471
|
to_time: Timestamp | None = None,
|
|
465
|
-
previous_node_field_specifiers:
|
|
472
|
+
previous_node_field_specifiers: NodeFieldSpecifierMap | None = None,
|
|
466
473
|
) -> None:
|
|
467
474
|
self.base_branch_name = base_branch.name
|
|
468
475
|
self.diff_branch_name = diff_branch.name
|
|
@@ -476,9 +483,9 @@ class DiffQueryParser:
|
|
|
476
483
|
self.diff_branched_from_time = Timestamp(diff_branch.get_branched_from())
|
|
477
484
|
self._diff_root_by_branch: dict[str, DiffRootIntermediate] = {}
|
|
478
485
|
self._final_diff_root_by_branch: dict[str, DiffRoot] = {}
|
|
479
|
-
self._previous_node_field_specifiers = previous_node_field_specifiers or
|
|
480
|
-
self._new_node_field_specifiers:
|
|
481
|
-
self._current_node_field_specifiers:
|
|
486
|
+
self._previous_node_field_specifiers = previous_node_field_specifiers or NodeFieldSpecifierMap()
|
|
487
|
+
self._new_node_field_specifiers: NodeFieldSpecifierMap | None = None
|
|
488
|
+
self._current_node_field_specifiers: NodeFieldSpecifierMap | None = None
|
|
482
489
|
|
|
483
490
|
def get_branches(self) -> set[str]:
|
|
484
491
|
return set(self._final_diff_root_by_branch.keys())
|
|
@@ -490,48 +497,33 @@ class DiffQueryParser:
|
|
|
490
497
|
return self._final_diff_root_by_branch[branch]
|
|
491
498
|
return DiffRoot(from_time=self.from_time, to_time=self.to_time, uuid=str(uuid4()), branch=branch, nodes=[])
|
|
492
499
|
|
|
493
|
-
def get_diff_node_field_specifiers(self) ->
|
|
500
|
+
def get_diff_node_field_specifiers(self) -> NodeFieldSpecifierMap:
|
|
501
|
+
node_field_specifiers_map = NodeFieldSpecifierMap()
|
|
494
502
|
if self.diff_branch_name not in self._diff_root_by_branch:
|
|
495
|
-
return
|
|
496
|
-
node_field_specifiers_map: dict[str, set[str]] = defaultdict(set)
|
|
503
|
+
return node_field_specifiers_map
|
|
497
504
|
diff_root = self._diff_root_by_branch[self.diff_branch_name]
|
|
498
|
-
for node in diff_root.
|
|
505
|
+
for node in diff_root.nodes_by_identifier.values():
|
|
499
506
|
for attribute_name in node.attributes_by_name:
|
|
500
|
-
node_field_specifiers_map
|
|
507
|
+
node_field_specifiers_map.add_entry(node_uuid=node.uuid, kind=node.kind, field_name=attribute_name)
|
|
501
508
|
for relationship_diff in node.relationships_by_identifier.values():
|
|
502
|
-
node_field_specifiers_map
|
|
509
|
+
node_field_specifiers_map.add_entry(
|
|
510
|
+
node_uuid=node.uuid, kind=node.kind, field_name=relationship_diff.identifier
|
|
511
|
+
)
|
|
503
512
|
return node_field_specifiers_map
|
|
504
513
|
|
|
505
|
-
def
|
|
506
|
-
self, node_specifiers: dict[str, set[str]], node_specifiers_to_remove: dict[str, set[str]]
|
|
507
|
-
) -> dict[str, set[str]]:
|
|
508
|
-
final_node_specifiers: dict[str, set[str]] = defaultdict(set)
|
|
509
|
-
for node_uuid, field_names_set in node_specifiers.items():
|
|
510
|
-
specifiers_to_remove = node_specifiers_to_remove.get(node_uuid, set())
|
|
511
|
-
final_specifiers = field_names_set - specifiers_to_remove
|
|
512
|
-
if final_specifiers:
|
|
513
|
-
final_node_specifiers[node_uuid] = final_specifiers
|
|
514
|
-
return final_node_specifiers
|
|
515
|
-
|
|
516
|
-
def get_new_node_field_specifiers(self) -> dict[str, set[str]]:
|
|
514
|
+
def get_new_node_field_specifiers(self) -> NodeFieldSpecifierMap:
|
|
517
515
|
if self._new_node_field_specifiers is not None:
|
|
518
516
|
return self._new_node_field_specifiers
|
|
519
517
|
branch_node_specifiers = self.get_diff_node_field_specifiers()
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
)
|
|
523
|
-
self._new_node_field_specifiers = new_node_field_specifiers
|
|
524
|
-
return new_node_field_specifiers
|
|
518
|
+
self._new_node_field_specifiers = branch_node_specifiers - self._previous_node_field_specifiers
|
|
519
|
+
return self._new_node_field_specifiers
|
|
525
520
|
|
|
526
|
-
def get_current_node_field_specifiers(self) ->
|
|
521
|
+
def get_current_node_field_specifiers(self) -> NodeFieldSpecifierMap:
|
|
527
522
|
if self._current_node_field_specifiers is not None:
|
|
528
523
|
return self._current_node_field_specifiers
|
|
529
524
|
new_node_field_specifiers = self.get_new_node_field_specifiers()
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
)
|
|
533
|
-
self._current_node_field_specifiers = current_node_field_specifiers
|
|
534
|
-
return current_node_field_specifiers
|
|
525
|
+
self._current_node_field_specifiers = self._previous_node_field_specifiers - new_node_field_specifiers
|
|
526
|
+
return self._current_node_field_specifiers
|
|
535
527
|
|
|
536
528
|
def read_result(self, query_result: QueryResult) -> None:
|
|
537
529
|
path = query_result.get_path(label="diff_path")
|
|
@@ -565,11 +557,12 @@ class DiffQueryParser:
|
|
|
565
557
|
return self._diff_root_by_branch[branch]
|
|
566
558
|
|
|
567
559
|
def _get_diff_node(self, database_path: DatabasePath, diff_root: DiffRootIntermediate) -> DiffNodeIntermediate:
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
560
|
+
identifier = NodeIdentifier(
|
|
561
|
+
uuid=database_path.node_id, kind=database_path.node_kind, labels=database_path.node_labels
|
|
562
|
+
)
|
|
563
|
+
if identifier not in diff_root.nodes_by_identifier:
|
|
564
|
+
diff_root.nodes_by_identifier[identifier] = DiffNodeIntermediate(
|
|
565
|
+
identifier=identifier,
|
|
573
566
|
db_id=database_path.node_db_id,
|
|
574
567
|
from_time=database_path.node_changed_at,
|
|
575
568
|
status=database_path.node_status,
|
|
@@ -577,7 +570,7 @@ class DiffQueryParser:
|
|
|
577
570
|
if database_path.node_branch_support is BranchSupportType.AGNOSTIC
|
|
578
571
|
else None,
|
|
579
572
|
)
|
|
580
|
-
diff_node = diff_root.
|
|
573
|
+
diff_node = diff_root.nodes_by_identifier[identifier]
|
|
581
574
|
# special handling for nodes that have their kind updated, which results in 2 nodes with the same uuid
|
|
582
575
|
if diff_node.db_id != database_path.node_db_id and (
|
|
583
576
|
database_path.node_changed_at > diff_node.from_time
|
|
@@ -587,7 +580,7 @@ class DiffQueryParser:
|
|
|
587
580
|
== (RelationshipStatus.DELETED, RelationshipStatus.ACTIVE)
|
|
588
581
|
)
|
|
589
582
|
):
|
|
590
|
-
diff_node.kind = database_path.node_kind
|
|
583
|
+
diff_node.identifier.kind = database_path.node_kind
|
|
591
584
|
diff_node.db_id = database_path.node_db_id
|
|
592
585
|
diff_node.from_time = database_path.node_changed_at
|
|
593
586
|
diff_node.status = database_path.node_status
|
|
@@ -634,7 +627,9 @@ class DiffQueryParser:
|
|
|
634
627
|
from_time = self.from_time
|
|
635
628
|
if branch_name == self.base_branch_name:
|
|
636
629
|
new_node_field_specifiers = self.get_new_node_field_specifiers()
|
|
637
|
-
if
|
|
630
|
+
if new_node_field_specifiers.has_entry(
|
|
631
|
+
node_uuid=diff_node.uuid, kind=diff_node.kind, field_name=attribute_name
|
|
632
|
+
):
|
|
638
633
|
from_time = self.diff_branched_from_time
|
|
639
634
|
if attribute_name not in diff_node.attributes_by_name:
|
|
640
635
|
diff_node.attributes_by_name[attribute_name] = DiffAttributeIntermediate(
|
|
@@ -678,7 +673,9 @@ class DiffQueryParser:
|
|
|
678
673
|
from_time = self.from_time
|
|
679
674
|
if branch_name == self.base_branch_name:
|
|
680
675
|
new_node_field_specifiers = self.get_new_node_field_specifiers()
|
|
681
|
-
if
|
|
676
|
+
if new_node_field_specifiers.has_entry(
|
|
677
|
+
node_uuid=diff_node.uuid, kind=diff_node.kind, field_name=relationship_schema.get_identifier()
|
|
678
|
+
):
|
|
682
679
|
from_time = self.diff_branched_from_time
|
|
683
680
|
diff_relationship = DiffRelationshipIntermediate(
|
|
684
681
|
name=relationship_schema.name,
|
|
@@ -700,8 +697,8 @@ class DiffQueryParser:
|
|
|
700
697
|
branch_diff_root = self._diff_root_by_branch.get(branch)
|
|
701
698
|
if not branch_diff_root:
|
|
702
699
|
continue
|
|
703
|
-
for
|
|
704
|
-
base_diff_node = base_diff_root.
|
|
700
|
+
for identifier, diff_node in branch_diff_root.nodes_by_identifier.items():
|
|
701
|
+
base_diff_node = base_diff_root.nodes_by_identifier.get(identifier)
|
|
705
702
|
if not base_diff_node:
|
|
706
703
|
continue
|
|
707
704
|
self._apply_attribute_previous_values(diff_node=diff_node, base_diff_node=base_diff_node)
|
|
@@ -771,7 +768,7 @@ class DiffQueryParser:
|
|
|
771
768
|
base_diff_root = self._diff_root_by_branch.get(self.base_branch_name)
|
|
772
769
|
if not base_diff_root:
|
|
773
770
|
return
|
|
774
|
-
for node_diff in base_diff_root.
|
|
771
|
+
for node_diff in base_diff_root.nodes_by_identifier.values():
|
|
775
772
|
for attribute_diff in node_diff.attributes_by_name.values():
|
|
776
773
|
for property_diff in attribute_diff.properties_by_type.values():
|
|
777
774
|
ordered_diff_values = property_diff.get_ordered_values_asc()
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
1
3
|
from neo4j.graph import Node as Neo4jNode
|
|
2
4
|
from neo4j.graph import Path as Neo4jPath
|
|
3
5
|
|
|
@@ -16,6 +18,7 @@ from ..model.path import (
|
|
|
16
18
|
EnrichedDiffRoot,
|
|
17
19
|
EnrichedDiffRootMetadata,
|
|
18
20
|
EnrichedDiffSingleRelationship,
|
|
21
|
+
NodeIdentifier,
|
|
19
22
|
deserialize_tracking_id,
|
|
20
23
|
)
|
|
21
24
|
from ..parent_node_adder import DiffParentNodeAdder, ParentNodeAddRequest
|
|
@@ -25,13 +28,15 @@ class EnrichedDiffDeserializer:
|
|
|
25
28
|
def __init__(self, parent_adder: DiffParentNodeAdder) -> None:
|
|
26
29
|
self.parent_adder = parent_adder
|
|
27
30
|
self._diff_root_map: dict[str, EnrichedDiffRoot] = {}
|
|
28
|
-
self._diff_node_map: dict[tuple[str,
|
|
29
|
-
self._diff_node_attr_map: dict[tuple[str,
|
|
30
|
-
self._diff_node_rel_group_map: dict[tuple[str,
|
|
31
|
-
self._diff_node_rel_element_map: dict[tuple[str,
|
|
32
|
-
self._diff_prop_map: dict[
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
self._diff_node_map: dict[tuple[str, NodeIdentifier], EnrichedDiffNode] = {}
|
|
32
|
+
self._diff_node_attr_map: dict[tuple[str, NodeIdentifier, str], EnrichedDiffAttribute] = {}
|
|
33
|
+
self._diff_node_rel_group_map: dict[tuple[str, NodeIdentifier, str], EnrichedDiffRelationship] = {}
|
|
34
|
+
self._diff_node_rel_element_map: dict[tuple[str, NodeIdentifier, str, str], EnrichedDiffSingleRelationship] = {}
|
|
35
|
+
self._diff_prop_map: dict[
|
|
36
|
+
tuple[str, NodeIdentifier, str, str] | tuple[str, str, str, str, str], EnrichedDiffProperty
|
|
37
|
+
] = {}
|
|
38
|
+
# {EnrichedDiffRoot: [(NodeIdentifier, parents_path: Neo4jPath), ...]}
|
|
39
|
+
self._parents_path_map: dict[EnrichedDiffRoot, list[tuple[NodeIdentifier, Neo4jPath]]] = {}
|
|
35
40
|
|
|
36
41
|
def initialize(self) -> None:
|
|
37
42
|
self._diff_root_map = {}
|
|
@@ -42,10 +47,12 @@ class EnrichedDiffDeserializer:
|
|
|
42
47
|
self._diff_prop_map = {}
|
|
43
48
|
self._parents_path_map = {}
|
|
44
49
|
|
|
45
|
-
def _track_parents_path(
|
|
50
|
+
def _track_parents_path(
|
|
51
|
+
self, enriched_root: EnrichedDiffRoot, node_identifier: NodeIdentifier, parents_path: Neo4jPath
|
|
52
|
+
) -> None:
|
|
46
53
|
if enriched_root not in self._parents_path_map:
|
|
47
54
|
self._parents_path_map[enriched_root] = []
|
|
48
|
-
self._parents_path_map[enriched_root].append((
|
|
55
|
+
self._parents_path_map[enriched_root].append((node_identifier, parents_path))
|
|
49
56
|
|
|
50
57
|
async def read_result(self, result: QueryResult, include_parents: bool) -> None:
|
|
51
58
|
enriched_root = self._deserialize_diff_root(root_node=result.get_node("diff_root"))
|
|
@@ -58,7 +65,7 @@ class EnrichedDiffDeserializer:
|
|
|
58
65
|
parents_path = result.get("parents_path")
|
|
59
66
|
if parents_path and isinstance(parents_path, Neo4jPath):
|
|
60
67
|
self._track_parents_path(
|
|
61
|
-
enriched_root=enriched_root,
|
|
68
|
+
enriched_root=enriched_root, node_identifier=enriched_node.identifier, parents_path=parents_path
|
|
62
69
|
)
|
|
63
70
|
|
|
64
71
|
node_conflict_node = result.get(label="diff_node_conflict")
|
|
@@ -130,17 +137,21 @@ class EnrichedDiffDeserializer:
|
|
|
130
137
|
def _deserialize_parents(self) -> None:
|
|
131
138
|
for enriched_root, node_path_tuples in self._parents_path_map.items():
|
|
132
139
|
self.parent_adder.initialize(enriched_diff_root=enriched_root)
|
|
133
|
-
for
|
|
140
|
+
for node_identifier, parents_path in node_path_tuples:
|
|
134
141
|
# Remove the node itself from the path
|
|
135
142
|
parents_path_slice = parents_path.nodes[1:]
|
|
136
143
|
|
|
137
144
|
# TODO Ensure the list is even
|
|
138
|
-
|
|
145
|
+
current_node_identifier = node_identifier
|
|
139
146
|
for rel, parent in zip(parents_path_slice[::2], parents_path_slice[1::2], strict=False):
|
|
147
|
+
parent_identifier = NodeIdentifier(
|
|
148
|
+
uuid=parent.get("uuid"),
|
|
149
|
+
kind=parent.get("kind"),
|
|
150
|
+
labels=frozenset(json.loads(parent.get("db_labels"))),
|
|
151
|
+
)
|
|
140
152
|
parent_request = ParentNodeAddRequest(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
parent_kind=parent.get("kind"),
|
|
153
|
+
node_identifier=current_node_identifier,
|
|
154
|
+
parent_identifier=parent_identifier,
|
|
144
155
|
parent_label=parent.get("label"),
|
|
145
156
|
parent_rel_name=rel.get("name"),
|
|
146
157
|
parent_rel_identifier=rel.get("identifier"),
|
|
@@ -148,7 +159,7 @@ class EnrichedDiffDeserializer:
|
|
|
148
159
|
parent_rel_label=rel.get("label"),
|
|
149
160
|
)
|
|
150
161
|
self.parent_adder.add_parent(parent_request=parent_request)
|
|
151
|
-
|
|
162
|
+
current_node_identifier = parent_identifier
|
|
152
163
|
|
|
153
164
|
@classmethod
|
|
154
165
|
def _get_str_or_none_property_value(cls, node: Neo4jNode, property_name: str) -> str | None:
|
|
@@ -189,14 +200,16 @@ class EnrichedDiffDeserializer:
|
|
|
189
200
|
|
|
190
201
|
def _deserialize_diff_node(self, node_node: Neo4jNode, enriched_root: EnrichedDiffRoot) -> EnrichedDiffNode:
|
|
191
202
|
node_uuid = str(node_node.get("uuid"))
|
|
192
|
-
|
|
203
|
+
node_kind = str(node_node.get("kind"))
|
|
204
|
+
node_db_labels = frozenset(json.loads(node_node.get("db_labels")))
|
|
205
|
+
node_identifier = NodeIdentifier(uuid=node_uuid, kind=node_kind, labels=node_db_labels)
|
|
206
|
+
node_key = (enriched_root.uuid, node_identifier)
|
|
193
207
|
if node_key in self._diff_node_map:
|
|
194
208
|
return self._diff_node_map[node_key]
|
|
195
209
|
|
|
196
210
|
timestamp_str = self._get_str_or_none_property_value(node=node_node, property_name="changed_at")
|
|
197
211
|
enriched_node = EnrichedDiffNode(
|
|
198
|
-
|
|
199
|
-
kind=str(node_node.get("kind")),
|
|
212
|
+
identifier=node_identifier,
|
|
200
213
|
label=str(node_node.get("label")),
|
|
201
214
|
changed_at=Timestamp(timestamp_str) if timestamp_str else None,
|
|
202
215
|
action=DiffAction(str(node_node.get("action"))),
|
|
@@ -215,7 +228,7 @@ class EnrichedDiffDeserializer:
|
|
|
215
228
|
self, diff_attr_node: Neo4jNode, enriched_root: EnrichedDiffRoot, enriched_node: EnrichedDiffNode
|
|
216
229
|
) -> EnrichedDiffAttribute:
|
|
217
230
|
attr_name = str(diff_attr_node.get("name"))
|
|
218
|
-
attr_key = (enriched_root.uuid, enriched_node.
|
|
231
|
+
attr_key = (enriched_root.uuid, enriched_node.identifier, attr_name)
|
|
219
232
|
if attr_key in self._diff_node_attr_map:
|
|
220
233
|
return self._diff_node_attr_map[attr_key]
|
|
221
234
|
|
|
@@ -238,7 +251,7 @@ class EnrichedDiffDeserializer:
|
|
|
238
251
|
self, relationship_group_node: Neo4jNode, enriched_root: EnrichedDiffRoot, enriched_node: EnrichedDiffNode
|
|
239
252
|
) -> EnrichedDiffRelationship:
|
|
240
253
|
diff_rel_name = str(relationship_group_node.get("name"))
|
|
241
|
-
rel_key = (enriched_root.uuid, enriched_node.
|
|
254
|
+
rel_key = (enriched_root.uuid, enriched_node.identifier, diff_rel_name)
|
|
242
255
|
if rel_key in self._diff_node_rel_group_map:
|
|
243
256
|
return self._diff_node_rel_group_map[rel_key]
|
|
244
257
|
|
|
@@ -272,7 +285,7 @@ class EnrichedDiffDeserializer:
|
|
|
272
285
|
diff_element_peer_id = str(relationship_element_node.get("peer_id"))
|
|
273
286
|
rel_element_key = (
|
|
274
287
|
enriched_root.uuid,
|
|
275
|
-
enriched_node.
|
|
288
|
+
enriched_node.identifier,
|
|
276
289
|
enriched_relationship_group.name,
|
|
277
290
|
diff_element_peer_id,
|
|
278
291
|
)
|
|
@@ -320,7 +333,7 @@ class EnrichedDiffDeserializer:
|
|
|
320
333
|
enriched_root: EnrichedDiffRoot,
|
|
321
334
|
) -> EnrichedDiffProperty:
|
|
322
335
|
diff_prop_type = str(diff_attr_property_node.get("property_type"))
|
|
323
|
-
attr_property_key = (enriched_root.uuid, enriched_node.
|
|
336
|
+
attr_property_key = (enriched_root.uuid, enriched_node.identifier, enriched_attr.name, diff_prop_type)
|
|
324
337
|
if attr_property_key in self._diff_prop_map:
|
|
325
338
|
return self._diff_prop_map[attr_property_key]
|
|
326
339
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
from collections import defaultdict
|
|
2
1
|
from typing import AsyncGenerator, Generator, Iterable
|
|
3
2
|
|
|
4
3
|
from neo4j.exceptions import TransientError
|
|
5
4
|
|
|
6
5
|
from infrahub import config
|
|
7
6
|
from infrahub.core import registry
|
|
7
|
+
from infrahub.core.diff.query.drop_nodes import EnrichedDiffDropNodesQuery
|
|
8
8
|
from infrahub.core.diff.query.field_summary import EnrichedDiffNodeFieldSummaryQuery
|
|
9
9
|
from infrahub.core.diff.query.summary_counts_enricher import (
|
|
10
10
|
DiffFieldsSummaryCountsEnricherQuery,
|
|
@@ -16,6 +16,7 @@ from infrahub.database import InfrahubDatabase, retry_db_transaction
|
|
|
16
16
|
from infrahub.exceptions import ResourceNotFoundError
|
|
17
17
|
from infrahub.log import get_logger
|
|
18
18
|
|
|
19
|
+
from ..model.field_specifiers_map import NodeFieldSpecifierMap
|
|
19
20
|
from ..model.path import (
|
|
20
21
|
ConflictSelection,
|
|
21
22
|
EnrichedDiffConflict,
|
|
@@ -26,6 +27,7 @@ from ..model.path import (
|
|
|
26
27
|
EnrichedDiffsMetadata,
|
|
27
28
|
EnrichedNodeCreateRequest,
|
|
28
29
|
NodeDiffFieldSummary,
|
|
30
|
+
NodeIdentifier,
|
|
29
31
|
TimeRange,
|
|
30
32
|
TrackingId,
|
|
31
33
|
)
|
|
@@ -108,7 +110,7 @@ class DiffRepository:
|
|
|
108
110
|
diff_branch_names: list[str],
|
|
109
111
|
from_time: Timestamp | None = None,
|
|
110
112
|
to_time: Timestamp | None = None,
|
|
111
|
-
filters:
|
|
113
|
+
filters: EnrichedDiffQueryFilters | None = None,
|
|
112
114
|
include_parents: bool = True,
|
|
113
115
|
limit: int | None = None,
|
|
114
116
|
offset: int | None = None,
|
|
@@ -180,11 +182,11 @@ class DiffRepository:
|
|
|
180
182
|
async def hydrate_diff_pair(
|
|
181
183
|
self,
|
|
182
184
|
enriched_diffs_metadata: EnrichedDiffsMetadata,
|
|
183
|
-
|
|
185
|
+
node_identifiers: Iterable[NodeIdentifier] | None = None,
|
|
184
186
|
) -> EnrichedDiffs:
|
|
185
|
-
filters =
|
|
186
|
-
if
|
|
187
|
-
filters =
|
|
187
|
+
filters = EnrichedDiffQueryFilters()
|
|
188
|
+
if node_identifiers:
|
|
189
|
+
filters.identifiers = list(node_identifiers)
|
|
188
190
|
hydrated_base_diff = await self.get_one(
|
|
189
191
|
diff_branch_name=enriched_diffs_metadata.base_branch_name,
|
|
190
192
|
diff_id=enriched_diffs_metadata.base_branch_diff.uuid,
|
|
@@ -207,7 +209,7 @@ class DiffRepository:
|
|
|
207
209
|
diff_branch_name: str,
|
|
208
210
|
tracking_id: TrackingId | None = None,
|
|
209
211
|
diff_id: str | None = None,
|
|
210
|
-
filters:
|
|
212
|
+
filters: EnrichedDiffQueryFilters | None = None,
|
|
211
213
|
include_parents: bool = True,
|
|
212
214
|
) -> EnrichedDiffRoot:
|
|
213
215
|
enriched_diffs = await self.get(
|
|
@@ -274,6 +276,12 @@ class DiffRepository:
|
|
|
274
276
|
)
|
|
275
277
|
await single_node_query.execute(db=self.db)
|
|
276
278
|
|
|
279
|
+
async def _drop_nodes(self, diff_root: EnrichedDiffRoot, node_identifiers: list[NodeIdentifier]) -> None:
|
|
280
|
+
drop_node_query = await EnrichedDiffDropNodesQuery.init(
|
|
281
|
+
db=self.db, enriched_diff_uuid=diff_root.uuid, node_identifiers=node_identifiers
|
|
282
|
+
)
|
|
283
|
+
await drop_node_query.execute(db=self.db)
|
|
284
|
+
|
|
277
285
|
@retry_db_transaction(name="enriched_diff_hierarchy_update")
|
|
278
286
|
async def _run_hierarchy_links_update_query(self, diff_root_uuid: str, diff_nodes: list[EnrichedDiffNode]) -> None:
|
|
279
287
|
log.info(f"Updating diff hierarchy links, num_nodes={len(diff_nodes)}")
|
|
@@ -321,7 +329,12 @@ class DiffRepository:
|
|
|
321
329
|
node_uuids=node_uuids,
|
|
322
330
|
)
|
|
323
331
|
|
|
324
|
-
async def save(
|
|
332
|
+
async def save(
|
|
333
|
+
self,
|
|
334
|
+
enriched_diffs: EnrichedDiffs | EnrichedDiffsMetadata,
|
|
335
|
+
do_summary_counts: bool = True,
|
|
336
|
+
node_identifiers_to_drop: list[NodeIdentifier] | None = None,
|
|
337
|
+
) -> None:
|
|
325
338
|
# metadata-only update
|
|
326
339
|
if not isinstance(enriched_diffs, EnrichedDiffs):
|
|
327
340
|
await self._save_root_metadata(enriched_diffs=enriched_diffs)
|
|
@@ -336,6 +349,8 @@ class DiffRepository:
|
|
|
336
349
|
await self._save_node_batch(node_create_batch=node_create_batch)
|
|
337
350
|
count_nodes_remaining -= len(node_create_batch)
|
|
338
351
|
log.info(f"Batch saved. {count_nodes_remaining=}")
|
|
352
|
+
if node_identifiers_to_drop:
|
|
353
|
+
await self._drop_nodes(diff_root=enriched_diffs.diff_branch_diff, node_identifiers=node_identifiers_to_drop)
|
|
339
354
|
await self._update_hierarchy_links(enriched_diffs=enriched_diffs)
|
|
340
355
|
if do_summary_counts:
|
|
341
356
|
await self._update_summary_counts(diff_root=enriched_diffs.diff_branch_diff)
|
|
@@ -501,21 +516,25 @@ class DiffRepository:
|
|
|
501
516
|
await query.execute(db=self.db)
|
|
502
517
|
return query.get_num_changes_by_branch()
|
|
503
518
|
|
|
504
|
-
async def get_node_field_specifiers(self, diff_id: str) ->
|
|
519
|
+
async def get_node_field_specifiers(self, diff_id: str) -> NodeFieldSpecifierMap:
|
|
505
520
|
limit = config.SETTINGS.database.query_size_limit
|
|
506
521
|
offset = 0
|
|
507
|
-
|
|
522
|
+
specifiers_map = NodeFieldSpecifierMap()
|
|
508
523
|
while True:
|
|
509
524
|
query = await EnrichedDiffFieldSpecifiersQuery.init(db=self.db, diff_id=diff_id, offset=offset, limit=limit)
|
|
510
525
|
await query.execute(db=self.db)
|
|
511
526
|
has_data = False
|
|
512
527
|
for field_specifier_tuple in query.get_node_field_specifier_tuples():
|
|
513
|
-
|
|
528
|
+
specifiers_map.add_entry(
|
|
529
|
+
node_uuid=field_specifier_tuple[0],
|
|
530
|
+
kind=field_specifier_tuple[1],
|
|
531
|
+
field_name=field_specifier_tuple[2],
|
|
532
|
+
)
|
|
514
533
|
has_data = True
|
|
515
534
|
if not has_data:
|
|
516
535
|
break
|
|
517
536
|
offset += limit
|
|
518
|
-
return
|
|
537
|
+
return specifiers_map
|
|
519
538
|
|
|
520
539
|
async def add_summary_counts(
|
|
521
540
|
self,
|
infrahub/core/graph/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
GRAPH_VERSION =
|
|
1
|
+
GRAPH_VERSION = 27
|
infrahub/core/initialization.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import importlib
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
2
3
|
from uuid import uuid4
|
|
3
4
|
|
|
4
5
|
from infrahub import config, lock
|
|
6
|
+
from infrahub.constants.database import DatabaseType
|
|
5
7
|
from infrahub.core import registry
|
|
6
8
|
from infrahub.core.branch import Branch
|
|
7
9
|
from infrahub.core.constants import (
|
|
@@ -13,6 +15,7 @@ from infrahub.core.constants import (
|
|
|
13
15
|
PermissionDecision,
|
|
14
16
|
)
|
|
15
17
|
from infrahub.core.graph import GRAPH_VERSION
|
|
18
|
+
from infrahub.core.graph.index import attr_value_index, node_indexes, rel_indexes
|
|
16
19
|
from infrahub.core.manager import NodeManager
|
|
17
20
|
from infrahub.core.node import Node
|
|
18
21
|
from infrahub.core.node.ipam import BuiltinIPPrefix
|
|
@@ -25,6 +28,8 @@ from infrahub.core.root import Root
|
|
|
25
28
|
from infrahub.core.schema import SchemaRoot, core_models, internal_schema
|
|
26
29
|
from infrahub.core.schema.manager import SchemaManager
|
|
27
30
|
from infrahub.database import InfrahubDatabase
|
|
31
|
+
from infrahub.database.memgraph import IndexManagerMemgraph
|
|
32
|
+
from infrahub.database.neo4j import IndexManagerNeo4j
|
|
28
33
|
from infrahub.exceptions import DatabaseError
|
|
29
34
|
from infrahub.graphql.manager import GraphQLSchemaManager
|
|
30
35
|
from infrahub.log import get_logger
|
|
@@ -32,6 +37,9 @@ from infrahub.menu.utils import create_default_menu
|
|
|
32
37
|
from infrahub.permissions import PermissionBackend
|
|
33
38
|
from infrahub.storage import InfrahubObjectStorage
|
|
34
39
|
|
|
40
|
+
if TYPE_CHECKING:
|
|
41
|
+
from infrahub.database.index import IndexManagerBase
|
|
42
|
+
|
|
35
43
|
log = get_logger()
|
|
36
44
|
|
|
37
45
|
|
|
@@ -115,7 +123,19 @@ async def initialize_registry(db: InfrahubDatabase, initialize: bool = False) ->
|
|
|
115
123
|
registry.permission_backends = initialize_permission_backends()
|
|
116
124
|
|
|
117
125
|
|
|
118
|
-
async def
|
|
126
|
+
async def add_indexes(db: InfrahubDatabase) -> None:
|
|
127
|
+
if db.db_type is DatabaseType.MEMGRAPH:
|
|
128
|
+
index_manager: IndexManagerBase = IndexManagerMemgraph(db=db)
|
|
129
|
+
index_manager = IndexManagerNeo4j(db=db)
|
|
130
|
+
|
|
131
|
+
if config.SETTINGS.experimental_features.value_db_index:
|
|
132
|
+
node_indexes.append(attr_value_index)
|
|
133
|
+
index_manager.init(nodes=node_indexes, rels=rel_indexes)
|
|
134
|
+
log.debug("Loading database indexes ..")
|
|
135
|
+
await index_manager.add()
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
async def initialization(db: InfrahubDatabase, add_database_indexes: bool = False) -> None:
|
|
119
139
|
if config.SETTINGS.database.db_type == config.DatabaseType.MEMGRAPH:
|
|
120
140
|
session = await db.session()
|
|
121
141
|
await session.run(query="SET DATABASE SETTING 'log.level' TO 'INFO'")
|
|
@@ -129,12 +149,8 @@ async def initialization(db: InfrahubDatabase) -> None:
|
|
|
129
149
|
log.debug("Checking Root Node")
|
|
130
150
|
await initialize_registry(db=db, initialize=True)
|
|
131
151
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
log.debug("Loading database indexes ..")
|
|
135
|
-
await db.manager.index.add()
|
|
136
|
-
else:
|
|
137
|
-
log.warning("The database index manager hasn't been initialized.")
|
|
152
|
+
if add_database_indexes:
|
|
153
|
+
await add_indexes(db=db)
|
|
138
154
|
|
|
139
155
|
# ---------------------------------------------------
|
|
140
156
|
# Load all schema in the database into the registry
|
|
@@ -28,6 +28,7 @@ from .m023_deduplicate_cardinality_one_relationships import Migration023
|
|
|
28
28
|
from .m024_missing_hierarchy_backfill import Migration024
|
|
29
29
|
from .m025_uniqueness_nulls import Migration025
|
|
30
30
|
from .m026_0000_prefix_fix import Migration026
|
|
31
|
+
from .m027_delete_isolated_nodes import Migration027
|
|
31
32
|
|
|
32
33
|
if TYPE_CHECKING:
|
|
33
34
|
from infrahub.core.root import Root
|
|
@@ -61,6 +62,7 @@ MIGRATIONS: list[type[GraphMigration | InternalSchemaMigration | ArbitraryMigrat
|
|
|
61
62
|
Migration024,
|
|
62
63
|
Migration025,
|
|
63
64
|
Migration026,
|
|
65
|
+
Migration027,
|
|
64
66
|
]
|
|
65
67
|
|
|
66
68
|
|
|
@@ -7,6 +7,7 @@ from infrahub.core.migrations.shared import MigrationResult
|
|
|
7
7
|
from infrahub.core.query import Query # noqa: TC001
|
|
8
8
|
from infrahub.database import DatabaseType
|
|
9
9
|
from infrahub.database.index import IndexItem
|
|
10
|
+
from infrahub.database.neo4j import IndexManagerNeo4j
|
|
10
11
|
|
|
11
12
|
from ..shared import GraphMigration
|
|
12
13
|
|
|
@@ -30,8 +31,9 @@ class Migration014(GraphMigration):
|
|
|
30
31
|
return result
|
|
31
32
|
|
|
32
33
|
try:
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
index_manager = IndexManagerNeo4j(db=db)
|
|
35
|
+
index_manager.init(nodes=[INDEX_TO_DELETE], rels=[])
|
|
36
|
+
await index_manager.drop()
|
|
35
37
|
except Exception as exc:
|
|
36
38
|
result.errors.append(str(exc))
|
|
37
39
|
return result
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Sequence
|
|
4
|
+
|
|
5
|
+
from infrahub.core.migrations.shared import GraphMigration, MigrationResult
|
|
6
|
+
from infrahub.log import get_logger
|
|
7
|
+
|
|
8
|
+
from ...query import Query, QueryType
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from infrahub.database import InfrahubDatabase
|
|
12
|
+
|
|
13
|
+
log = get_logger()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DeleteIsolatedNodesQuery(Query):
|
|
17
|
+
name = "delete_isolated_nodes_query"
|
|
18
|
+
type = QueryType.WRITE
|
|
19
|
+
insert_return = False
|
|
20
|
+
|
|
21
|
+
async def query_init(self, db: InfrahubDatabase, **kwargs: dict[str, Any]) -> None: # noqa: ARG002
|
|
22
|
+
query = """
|
|
23
|
+
MATCH p = (s: Node)-[r]-(d)
|
|
24
|
+
WHERE NOT exists((s)-[:IS_PART_OF]-(:Root))
|
|
25
|
+
DELETE r
|
|
26
|
+
|
|
27
|
+
WITH p
|
|
28
|
+
UNWIND nodes(p) AS n
|
|
29
|
+
MATCH (n)
|
|
30
|
+
WHERE NOT exists((n)--())
|
|
31
|
+
DELETE n
|
|
32
|
+
"""
|
|
33
|
+
self.add_to_query(query)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Migration027(GraphMigration):
|
|
37
|
+
"""
|
|
38
|
+
While deleting a branch containing some allocated nodes from a resource pool, relationship
|
|
39
|
+
between pool node and resource node might be agnostic (eg: for IPPrefixPool) and incorrectly deleted,
|
|
40
|
+
resulting in a node still linked to the resource pool but not linked to Root anymore.
|
|
41
|
+
This query deletes nodes not linked to Root and their relationships (supposed to be agnostic).
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
name: str = "027_deleted_isolated_nodes"
|
|
45
|
+
minimum_version: int = 26
|
|
46
|
+
queries: Sequence[type[Query]] = [DeleteIsolatedNodesQuery]
|
|
47
|
+
|
|
48
|
+
async def validate_migration(self, db: InfrahubDatabase) -> MigrationResult: # noqa: ARG002
|
|
49
|
+
result = MigrationResult()
|
|
50
|
+
return result
|