infrahub-server 1.2.9rc0__py3-none-any.whl → 1.2.11__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/computed_attribute/models.py +13 -0
- infrahub/computed_attribute/tasks.py +48 -26
- infrahub/config.py +9 -0
- infrahub/core/attribute.py +43 -2
- infrahub/core/branch/models.py +8 -9
- infrahub/core/branch/tasks.py +0 -2
- infrahub/core/constants/infrahubkind.py +1 -0
- infrahub/core/constraint/node/runner.py +1 -1
- infrahub/core/diff/calculator.py +65 -11
- infrahub/core/diff/combiner.py +38 -31
- infrahub/core/diff/coordinator.py +44 -28
- infrahub/core/diff/data_check_synchronizer.py +3 -2
- infrahub/core/diff/enricher/hierarchy.py +36 -27
- infrahub/core/diff/ipam_diff_parser.py +5 -4
- infrahub/core/diff/merger/merger.py +46 -16
- infrahub/core/diff/merger/serializer.py +1 -0
- infrahub/core/diff/model/field_specifiers_map.py +64 -0
- infrahub/core/diff/model/path.py +58 -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/merge.py +264 -28
- infrahub/core/diff/query/save.py +6 -2
- infrahub/core/diff/query_parser.py +55 -65
- infrahub/core/diff/repository/deserializer.py +38 -24
- infrahub/core/diff/repository/repository.py +31 -12
- infrahub/core/diff/tasks.py +3 -3
- infrahub/core/graph/__init__.py +1 -1
- infrahub/core/migrations/graph/__init__.py +2 -0
- infrahub/core/migrations/graph/m027_delete_isolated_nodes.py +50 -0
- infrahub/core/migrations/graph/m028_delete_diffs.py +38 -0
- infrahub/core/node/resource_manager/ip_address_pool.py +6 -2
- infrahub/core/node/resource_manager/ip_prefix_pool.py +6 -2
- infrahub/core/protocols.py +4 -0
- infrahub/core/query/branch.py +27 -17
- infrahub/core/query/diff.py +169 -51
- infrahub/core/query/node.py +39 -5
- infrahub/core/query/relationship.py +105 -30
- infrahub/core/query/subquery.py +2 -2
- infrahub/core/relationship/model.py +1 -1
- infrahub/core/schema/definitions/core/__init__.py +8 -1
- infrahub/core/schema/definitions/core/resource_pool.py +20 -0
- infrahub/core/schema/schema_branch.py +3 -0
- infrahub/core/validators/tasks.py +1 -1
- infrahub/core/validators/uniqueness/query.py +7 -0
- infrahub/database/__init__.py +5 -4
- infrahub/graphql/app.py +1 -1
- infrahub/graphql/loaders/node.py +1 -1
- infrahub/graphql/loaders/peers.py +1 -1
- infrahub/graphql/mutations/proposed_change.py +1 -1
- infrahub/graphql/queries/diff/tree.py +2 -1
- infrahub/graphql/queries/relationship.py +1 -1
- infrahub/graphql/queries/task.py +10 -0
- infrahub/graphql/resolvers/many_relationship.py +4 -4
- infrahub/graphql/resolvers/resolver.py +4 -4
- infrahub/graphql/resolvers/single_relationship.py +2 -2
- infrahub/graphql/subscription/graphql_query.py +2 -2
- infrahub/graphql/types/branch.py +1 -1
- infrahub/graphql/types/task_log.py +3 -2
- infrahub/message_bus/operations/refresh/registry.py +1 -1
- infrahub/task_manager/task.py +44 -4
- infrahub/telemetry/database.py +1 -1
- infrahub/telemetry/tasks.py +1 -1
- infrahub/trigger/models.py +11 -1
- infrahub/trigger/setup.py +51 -15
- infrahub/trigger/tasks.py +1 -4
- infrahub/types.py +1 -1
- infrahub/webhook/models.py +2 -1
- infrahub/workflows/catalogue.py +9 -0
- infrahub/workflows/initialization.py +1 -3
- infrahub_sdk/timestamp.py +2 -2
- {infrahub_server-1.2.9rc0.dist-info → infrahub_server-1.2.11.dist-info}/METADATA +3 -3
- {infrahub_server-1.2.9rc0.dist-info → infrahub_server-1.2.11.dist-info}/RECORD +79 -75
- infrahub_testcontainers/docker-compose.test.yml +3 -3
- infrahub_testcontainers/performance_test.py +6 -3
- {infrahub_server-1.2.9rc0.dist-info → infrahub_server-1.2.11.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.2.9rc0.dist-info → infrahub_server-1.2.11.dist-info}/WHEEL +0 -0
- {infrahub_server-1.2.9rc0.dist-info → infrahub_server-1.2.11.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,51 +497,40 @@ 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
|
+
try:
|
|
530
|
+
path = query_result.get_path(label="diff_path")
|
|
531
|
+
except ValueError:
|
|
532
|
+
# the path was null, so nothing to read
|
|
533
|
+
return
|
|
538
534
|
database_path = DatabasePath.from_cypher_path(cypher_path=path)
|
|
539
535
|
self._parse_path(database_path=database_path)
|
|
540
536
|
self._current_node_field_specifiers = None
|
|
@@ -565,11 +561,12 @@ class DiffQueryParser:
|
|
|
565
561
|
return self._diff_root_by_branch[branch]
|
|
566
562
|
|
|
567
563
|
def _get_diff_node(self, database_path: DatabasePath, diff_root: DiffRootIntermediate) -> DiffNodeIntermediate:
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
564
|
+
identifier = NodeIdentifier(
|
|
565
|
+
uuid=database_path.node_id, kind=database_path.node_kind, db_id=database_path.node_db_id
|
|
566
|
+
)
|
|
567
|
+
if identifier not in diff_root.nodes_by_identifier:
|
|
568
|
+
diff_root.nodes_by_identifier[identifier] = DiffNodeIntermediate(
|
|
569
|
+
identifier=identifier,
|
|
573
570
|
db_id=database_path.node_db_id,
|
|
574
571
|
from_time=database_path.node_changed_at,
|
|
575
572
|
status=database_path.node_status,
|
|
@@ -577,20 +574,7 @@ class DiffQueryParser:
|
|
|
577
574
|
if database_path.node_branch_support is BranchSupportType.AGNOSTIC
|
|
578
575
|
else None,
|
|
579
576
|
)
|
|
580
|
-
diff_node = diff_root.
|
|
581
|
-
# special handling for nodes that have their kind updated, which results in 2 nodes with the same uuid
|
|
582
|
-
if diff_node.db_id != database_path.node_db_id and (
|
|
583
|
-
database_path.node_changed_at > diff_node.from_time
|
|
584
|
-
or (
|
|
585
|
-
database_path.node_changed_at >= diff_node.from_time
|
|
586
|
-
and (diff_node.status, database_path.node_status)
|
|
587
|
-
== (RelationshipStatus.DELETED, RelationshipStatus.ACTIVE)
|
|
588
|
-
)
|
|
589
|
-
):
|
|
590
|
-
diff_node.kind = database_path.node_kind
|
|
591
|
-
diff_node.db_id = database_path.node_db_id
|
|
592
|
-
diff_node.from_time = database_path.node_changed_at
|
|
593
|
-
diff_node.status = database_path.node_status
|
|
577
|
+
diff_node = diff_root.nodes_by_identifier[identifier]
|
|
594
578
|
diff_node.track_database_path(database_path=database_path)
|
|
595
579
|
return diff_node
|
|
596
580
|
|
|
@@ -634,7 +618,9 @@ class DiffQueryParser:
|
|
|
634
618
|
from_time = self.from_time
|
|
635
619
|
if branch_name == self.base_branch_name:
|
|
636
620
|
new_node_field_specifiers = self.get_new_node_field_specifiers()
|
|
637
|
-
if
|
|
621
|
+
if new_node_field_specifiers.has_entry(
|
|
622
|
+
node_uuid=diff_node.uuid, kind=diff_node.kind, field_name=attribute_name
|
|
623
|
+
):
|
|
638
624
|
from_time = self.diff_branched_from_time
|
|
639
625
|
if attribute_name not in diff_node.attributes_by_name:
|
|
640
626
|
diff_node.attributes_by_name[attribute_name] = DiffAttributeIntermediate(
|
|
@@ -678,7 +664,9 @@ class DiffQueryParser:
|
|
|
678
664
|
from_time = self.from_time
|
|
679
665
|
if branch_name == self.base_branch_name:
|
|
680
666
|
new_node_field_specifiers = self.get_new_node_field_specifiers()
|
|
681
|
-
if
|
|
667
|
+
if new_node_field_specifiers.has_entry(
|
|
668
|
+
node_uuid=diff_node.uuid, kind=diff_node.kind, field_name=relationship_schema.get_identifier()
|
|
669
|
+
):
|
|
682
670
|
from_time = self.diff_branched_from_time
|
|
683
671
|
diff_relationship = DiffRelationshipIntermediate(
|
|
684
672
|
name=relationship_schema.name,
|
|
@@ -700,8 +688,10 @@ class DiffQueryParser:
|
|
|
700
688
|
branch_diff_root = self._diff_root_by_branch.get(branch)
|
|
701
689
|
if not branch_diff_root:
|
|
702
690
|
continue
|
|
703
|
-
for
|
|
704
|
-
|
|
691
|
+
base_diff_nodes_by_uuid = {n.uuid: n for n in base_diff_root.nodes_by_identifier.values()}
|
|
692
|
+
for identifier, diff_node in branch_diff_root.nodes_by_identifier.items():
|
|
693
|
+
# changes on a base branch node with a given UUID should apply to all diff branch nodes with that UUID
|
|
694
|
+
base_diff_node = base_diff_nodes_by_uuid.get(identifier.uuid)
|
|
705
695
|
if not base_diff_node:
|
|
706
696
|
continue
|
|
707
697
|
self._apply_attribute_previous_values(diff_node=diff_node, base_diff_node=base_diff_node)
|
|
@@ -771,7 +761,7 @@ class DiffQueryParser:
|
|
|
771
761
|
base_diff_root = self._diff_root_by_branch.get(self.base_branch_name)
|
|
772
762
|
if not base_diff_root:
|
|
773
763
|
return
|
|
774
|
-
for node_diff in base_diff_root.
|
|
764
|
+
for node_diff in base_diff_root.nodes_by_identifier.values():
|
|
775
765
|
for attribute_diff in node_diff.attributes_by_name.values():
|
|
776
766
|
for property_diff in attribute_diff.properties_by_type.values():
|
|
777
767
|
ordered_diff_values = property_diff.get_ordered_values_asc()
|
|
@@ -16,6 +16,7 @@ from ..model.path import (
|
|
|
16
16
|
EnrichedDiffRoot,
|
|
17
17
|
EnrichedDiffRootMetadata,
|
|
18
18
|
EnrichedDiffSingleRelationship,
|
|
19
|
+
NodeIdentifier,
|
|
19
20
|
deserialize_tracking_id,
|
|
20
21
|
)
|
|
21
22
|
from ..parent_node_adder import DiffParentNodeAdder, ParentNodeAddRequest
|
|
@@ -25,13 +26,15 @@ class EnrichedDiffDeserializer:
|
|
|
25
26
|
def __init__(self, parent_adder: DiffParentNodeAdder) -> None:
|
|
26
27
|
self.parent_adder = parent_adder
|
|
27
28
|
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
|
-
|
|
29
|
+
self._diff_node_map: dict[tuple[str, NodeIdentifier], EnrichedDiffNode] = {}
|
|
30
|
+
self._diff_node_attr_map: dict[tuple[str, NodeIdentifier, str], EnrichedDiffAttribute] = {}
|
|
31
|
+
self._diff_node_rel_group_map: dict[tuple[str, NodeIdentifier, str], EnrichedDiffRelationship] = {}
|
|
32
|
+
self._diff_node_rel_element_map: dict[tuple[str, NodeIdentifier, str, str], EnrichedDiffSingleRelationship] = {}
|
|
33
|
+
self._diff_prop_map: dict[
|
|
34
|
+
tuple[str, NodeIdentifier, str, str] | tuple[str, str, str, str, str], EnrichedDiffProperty
|
|
35
|
+
] = {}
|
|
36
|
+
# {EnrichedDiffRoot: [(NodeIdentifier, parents_path: Neo4jPath), ...]}
|
|
37
|
+
self._parents_path_map: dict[EnrichedDiffRoot, list[tuple[NodeIdentifier, Neo4jPath]]] = {}
|
|
35
38
|
|
|
36
39
|
def initialize(self) -> None:
|
|
37
40
|
self._diff_root_map = {}
|
|
@@ -42,10 +45,12 @@ class EnrichedDiffDeserializer:
|
|
|
42
45
|
self._diff_prop_map = {}
|
|
43
46
|
self._parents_path_map = {}
|
|
44
47
|
|
|
45
|
-
def _track_parents_path(
|
|
48
|
+
def _track_parents_path(
|
|
49
|
+
self, enriched_root: EnrichedDiffRoot, node_identifier: NodeIdentifier, parents_path: Neo4jPath
|
|
50
|
+
) -> None:
|
|
46
51
|
if enriched_root not in self._parents_path_map:
|
|
47
52
|
self._parents_path_map[enriched_root] = []
|
|
48
|
-
self._parents_path_map[enriched_root].append((
|
|
53
|
+
self._parents_path_map[enriched_root].append((node_identifier, parents_path))
|
|
49
54
|
|
|
50
55
|
async def read_result(self, result: QueryResult, include_parents: bool) -> None:
|
|
51
56
|
enriched_root = self._deserialize_diff_root(root_node=result.get_node("diff_root"))
|
|
@@ -58,7 +63,7 @@ class EnrichedDiffDeserializer:
|
|
|
58
63
|
parents_path = result.get("parents_path")
|
|
59
64
|
if parents_path and isinstance(parents_path, Neo4jPath):
|
|
60
65
|
self._track_parents_path(
|
|
61
|
-
enriched_root=enriched_root,
|
|
66
|
+
enriched_root=enriched_root, node_identifier=enriched_node.identifier, parents_path=parents_path
|
|
62
67
|
)
|
|
63
68
|
|
|
64
69
|
node_conflict_node = result.get(label="diff_node_conflict")
|
|
@@ -79,11 +84,13 @@ class EnrichedDiffDeserializer:
|
|
|
79
84
|
) -> None:
|
|
80
85
|
for attribute_result in result.get_nested_node_collection("diff_attributes"):
|
|
81
86
|
diff_attr_node, diff_attr_property_node, diff_attr_property_conflict = attribute_result
|
|
82
|
-
if diff_attr_node is None
|
|
87
|
+
if diff_attr_node is None:
|
|
83
88
|
continue
|
|
84
89
|
enriched_attribute = self._deserialize_diff_attr(
|
|
85
90
|
diff_attr_node=diff_attr_node, enriched_root=enriched_root, enriched_node=enriched_node
|
|
86
91
|
)
|
|
92
|
+
if diff_attr_property_node is None:
|
|
93
|
+
continue
|
|
87
94
|
enriched_property = self._deserialize_diff_attr_property(
|
|
88
95
|
diff_attr_property_node=diff_attr_property_node,
|
|
89
96
|
enriched_attr=enriched_attribute,
|
|
@@ -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
|
+
db_id=parent.get("db_id"),
|
|
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,17 +200,20 @@ 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_id = node_node.get("db_id")
|
|
205
|
+
node_identifier = NodeIdentifier(uuid=node_uuid, kind=node_kind, db_id=node_db_id)
|
|
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"))),
|
|
216
|
+
is_node_kind_migration=bool(node_node.get("is_node_kind_migration")),
|
|
203
217
|
path_identifier=str(node_node.get("path_identifier")),
|
|
204
218
|
num_added=int(node_node.get("num_added", 0)),
|
|
205
219
|
num_updated=int(node_node.get("num_updated", 0)),
|
|
@@ -215,7 +229,7 @@ class EnrichedDiffDeserializer:
|
|
|
215
229
|
self, diff_attr_node: Neo4jNode, enriched_root: EnrichedDiffRoot, enriched_node: EnrichedDiffNode
|
|
216
230
|
) -> EnrichedDiffAttribute:
|
|
217
231
|
attr_name = str(diff_attr_node.get("name"))
|
|
218
|
-
attr_key = (enriched_root.uuid, enriched_node.
|
|
232
|
+
attr_key = (enriched_root.uuid, enriched_node.identifier, attr_name)
|
|
219
233
|
if attr_key in self._diff_node_attr_map:
|
|
220
234
|
return self._diff_node_attr_map[attr_key]
|
|
221
235
|
|
|
@@ -238,7 +252,7 @@ class EnrichedDiffDeserializer:
|
|
|
238
252
|
self, relationship_group_node: Neo4jNode, enriched_root: EnrichedDiffRoot, enriched_node: EnrichedDiffNode
|
|
239
253
|
) -> EnrichedDiffRelationship:
|
|
240
254
|
diff_rel_name = str(relationship_group_node.get("name"))
|
|
241
|
-
rel_key = (enriched_root.uuid, enriched_node.
|
|
255
|
+
rel_key = (enriched_root.uuid, enriched_node.identifier, diff_rel_name)
|
|
242
256
|
if rel_key in self._diff_node_rel_group_map:
|
|
243
257
|
return self._diff_node_rel_group_map[rel_key]
|
|
244
258
|
|
|
@@ -272,7 +286,7 @@ class EnrichedDiffDeserializer:
|
|
|
272
286
|
diff_element_peer_id = str(relationship_element_node.get("peer_id"))
|
|
273
287
|
rel_element_key = (
|
|
274
288
|
enriched_root.uuid,
|
|
275
|
-
enriched_node.
|
|
289
|
+
enriched_node.identifier,
|
|
276
290
|
enriched_relationship_group.name,
|
|
277
291
|
diff_element_peer_id,
|
|
278
292
|
)
|
|
@@ -320,7 +334,7 @@ class EnrichedDiffDeserializer:
|
|
|
320
334
|
enriched_root: EnrichedDiffRoot,
|
|
321
335
|
) -> EnrichedDiffProperty:
|
|
322
336
|
diff_prop_type = str(diff_attr_property_node.get("property_type"))
|
|
323
|
-
attr_property_key = (enriched_root.uuid, enriched_node.
|
|
337
|
+
attr_property_key = (enriched_root.uuid, enriched_node.identifier, enriched_attr.name, diff_prop_type)
|
|
324
338
|
if attr_property_key in self._diff_prop_map:
|
|
325
339
|
return self._diff_prop_map[attr_property_key]
|
|
326
340
|
|
|
@@ -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/diff/tasks.py
CHANGED
|
@@ -20,7 +20,7 @@ log = get_logger()
|
|
|
20
20
|
async def update_diff(model: RequestDiffUpdate, service: InfrahubServices) -> None:
|
|
21
21
|
await add_tags(branches=[model.branch_name])
|
|
22
22
|
|
|
23
|
-
async with service.database.start_session() as db:
|
|
23
|
+
async with service.database.start_session(read_only=False) as db:
|
|
24
24
|
component_registry = get_component_registry()
|
|
25
25
|
base_branch = await registry.get_branch(db=db, branch=registry.default_branch)
|
|
26
26
|
diff_branch = await registry.get_branch(db=db, branch=model.branch_name)
|
|
@@ -40,7 +40,7 @@ async def update_diff(model: RequestDiffUpdate, service: InfrahubServices) -> No
|
|
|
40
40
|
async def refresh_diff(branch_name: str, diff_id: str, service: InfrahubServices) -> None:
|
|
41
41
|
await add_tags(branches=[branch_name])
|
|
42
42
|
|
|
43
|
-
async with service.database.start_session() as db:
|
|
43
|
+
async with service.database.start_session(read_only=False) as db:
|
|
44
44
|
component_registry = get_component_registry()
|
|
45
45
|
base_branch = await registry.get_branch(db=db, branch=registry.default_branch)
|
|
46
46
|
diff_branch = await registry.get_branch(db=db, branch=branch_name)
|
|
@@ -53,7 +53,7 @@ async def refresh_diff(branch_name: str, diff_id: str, service: InfrahubServices
|
|
|
53
53
|
async def refresh_diff_all(branch_name: str, context: InfrahubContext, service: InfrahubServices) -> None:
|
|
54
54
|
await add_tags(branches=[branch_name])
|
|
55
55
|
|
|
56
|
-
async with service.database.start_session() as db:
|
|
56
|
+
async with service.database.start_session(read_only=True) as db:
|
|
57
57
|
component_registry = get_component_registry()
|
|
58
58
|
default_branch = registry.get_branch_from_registry()
|
|
59
59
|
diff_repository = await component_registry.get_component(DiffRepository, db=db, branch=default_branch)
|
infrahub/core/graph/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
GRAPH_VERSION =
|
|
1
|
+
GRAPH_VERSION = 28
|
|
@@ -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
|
|
|
@@ -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
|