From a27eedb6bd5d5657222aa52d6e95884f87fb234d Mon Sep 17 00:00:00 2001 From: steiler Date: Wed, 18 Feb 2026 16:16:30 +0100 Subject: [PATCH 1/8] move entry and related to api package --- mocks/mocktreeentry/entry.go | 384 +++++++++--------- pkg/datastore/intent_rpc.go | 3 +- pkg/datastore/sync.go | 9 +- pkg/datastore/sync_test.go | 15 +- pkg/datastore/target/gnmi/get.go | 5 +- pkg/datastore/target/gnmi/stream.go | 7 +- pkg/datastore/target/netconf/nc.go | 4 +- pkg/datastore/transaction_rpc.go | 24 +- pkg/datastore/transaction_rpc_test.go | 3 +- pkg/datastore/tree_operation_test.go | 67 +-- pkg/datastore/types/transaction.go | 6 +- pkg/server/transaction.go | 6 +- pkg/tree/api/delete_path_prio.go | 43 ++ .../delete_path_set.go} | 40 +- pkg/tree/{ => api}/entry.go | 64 ++- pkg/tree/{ => api}/entry_map.go | 2 +- pkg/tree/{ => api}/leaf_entry.go | 13 +- pkg/tree/{ => api}/leaf_entry_filter.go | 2 +- pkg/tree/{ => api}/leaf_variant_slice.go | 15 +- pkg/tree/{ => api}/leaf_variants.go | 25 +- pkg/tree/{ => api}/leaf_variants_test.go | 7 +- pkg/tree/api/non_revertive_info.go | 49 +++ pkg/tree/api/non_revertive_infos.go | 48 +++ pkg/tree/api/sdcpb_path.go | 7 + pkg/tree/api/tree_context.go | 14 + pkg/tree/childMap.go | 22 +- pkg/tree/childMap_test.go | 16 +- pkg/tree/consts/consts.go | 13 + pkg/tree/default_value.go | 3 +- pkg/tree/default_value_test.go | 6 +- pkg/tree/entry_list.go | 4 +- pkg/tree/entry_test.go | 120 +++--- pkg/tree/json.go | 23 +- pkg/tree/json_test.go | 3 +- pkg/tree/ops/get_by_owner/get_by_owner.go | 22 + pkg/tree/processor_blame_config.go | 18 +- pkg/tree/processor_blame_config_test.go | 5 +- pkg/tree/processor_explicit_delete.go | 19 +- pkg/tree/processor_explicit_delete_test.go | 32 +- pkg/tree/processor_importer.go | 31 +- pkg/tree/processor_mark_owner_delete.go | 15 +- pkg/tree/processor_remove_deleted.go | 15 +- pkg/tree/processor_reset_flags.go | 9 +- pkg/tree/processor_reset_flags_test.go | 3 +- pkg/tree/processor_validate.go | 7 +- pkg/tree/root_entry.go | 55 +-- pkg/tree/root_entry_test.go | 37 +- pkg/tree/sharedEntryAttributes.go | 185 +++++---- pkg/tree/sharedEntryAttributes_test.go | 41 +- pkg/tree/sorter.go | 13 +- pkg/tree/tree_context.go | 38 +- pkg/tree/types/update.go | 2 +- pkg/tree/types/update_slice.go | 8 +- pkg/tree/utils.go | 2 +- pkg/tree/validation_entry_leafref.go | 33 +- pkg/tree/xml.go | 33 +- pkg/tree/xml_test.go | 3 +- pkg/tree/yang-parser-adapter.go | 7 +- 58 files changed, 949 insertions(+), 756 deletions(-) create mode 100644 pkg/tree/api/delete_path_prio.go rename pkg/tree/{intent_path_mapping.go => api/delete_path_set.go} (60%) rename pkg/tree/{ => api}/entry.go (84%) rename pkg/tree/{ => api}/entry_map.go (94%) rename pkg/tree/{ => api}/leaf_entry.go (94%) rename pkg/tree/{ => api}/leaf_entry_filter.go (98%) rename pkg/tree/{ => api}/leaf_variant_slice.go (85%) rename pkg/tree/{ => api}/leaf_variants.go (95%) rename pkg/tree/{ => api}/leaf_variants_test.go (98%) create mode 100644 pkg/tree/api/non_revertive_info.go create mode 100644 pkg/tree/api/non_revertive_infos.go create mode 100644 pkg/tree/api/sdcpb_path.go create mode 100644 pkg/tree/api/tree_context.go create mode 100644 pkg/tree/consts/consts.go create mode 100644 pkg/tree/ops/get_by_owner/get_by_owner.go diff --git a/mocks/mocktreeentry/entry.go b/mocks/mocktreeentry/entry.go index ff0b493b..11f96f53 100644 --- a/mocks/mocktreeentry/entry.go +++ b/mocks/mocktreeentry/entry.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: pkg/tree/entry.go +// Source: pkg/tree/api/entry.go // // Generated by this command: // -// mockgen -package=mockTreeEntry -source=pkg/tree/entry.go -destination=./mocks/mocktreeentry/entry.go +// mockgen -package=mockTreeEntry -source=pkg/tree/api/entry.go -destination=./mocks/mocktreeentry/entry.go // // Package mockTreeEntry is a generated GoMock package. @@ -15,7 +15,7 @@ import ( etree "github.com/beevik/etree" config "github.com/sdcio/data-server/pkg/config" - tree "github.com/sdcio/data-server/pkg/tree" + api "github.com/sdcio/data-server/pkg/tree/api" types "github.com/sdcio/data-server/pkg/tree/types" sdcpb "github.com/sdcio/sdc-protos/sdcpb" tree_persist "github.com/sdcio/sdc-protos/tree_persist" @@ -47,7 +47,7 @@ func (m *MockEntry) EXPECT() *MockEntryMockRecorder { } // AddChild mocks base method. -func (m *MockEntry) AddChild(arg0 context.Context, arg1 tree.Entry) error { +func (m *MockEntry) AddChild(arg0 context.Context, arg1 api.Entry) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AddChild", arg0, arg1) ret0, _ := ret[0].(error) @@ -61,10 +61,10 @@ func (mr *MockEntryMockRecorder) AddChild(arg0, arg1 any) *gomock.Call { } // AddUpdateRecursive mocks base method. -func (m *MockEntry) AddUpdateRecursive(ctx context.Context, relativePath *sdcpb.Path, u *types.Update, flags *types.UpdateInsertFlags) (tree.Entry, error) { +func (m *MockEntry) AddUpdateRecursive(ctx context.Context, relativePath *sdcpb.Path, u *types.Update, flags *types.UpdateInsertFlags) (api.Entry, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AddUpdateRecursive", ctx, relativePath, u, flags) - ret0, _ := ret[0].(tree.Entry) + ret0, _ := ret[0].(api.Entry) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -75,11 +75,26 @@ func (mr *MockEntryMockRecorder) AddUpdateRecursive(ctx, relativePath, u, flags return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddUpdateRecursive", reflect.TypeOf((*MockEntry)(nil).AddUpdateRecursive), ctx, relativePath, u, flags) } +// AddUpdateRecursiveInternal mocks base method. +func (m *MockEntry) AddUpdateRecursiveInternal(ctx context.Context, path *sdcpb.Path, idx int, u *types.Update, flags *types.UpdateInsertFlags) (api.Entry, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddUpdateRecursiveInternal", ctx, path, idx, u, flags) + ret0, _ := ret[0].(api.Entry) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AddUpdateRecursiveInternal indicates an expected call of AddUpdateRecursiveInternal. +func (mr *MockEntryMockRecorder) AddUpdateRecursiveInternal(ctx, path, idx, u, flags any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddUpdateRecursiveInternal", reflect.TypeOf((*MockEntry)(nil).AddUpdateRecursiveInternal), ctx, path, idx, u, flags) +} + // BreadthSearch mocks base method. -func (m *MockEntry) BreadthSearch(ctx context.Context, path *sdcpb.Path) ([]tree.Entry, error) { +func (m *MockEntry) BreadthSearch(ctx context.Context, path *sdcpb.Path) ([]api.Entry, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "BreadthSearch", ctx, path) - ret0, _ := ret[0].([]tree.Entry) + ret0, _ := ret[0].([]api.Entry) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -90,6 +105,20 @@ func (mr *MockEntryMockRecorder) BreadthSearch(ctx, path any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BreadthSearch", reflect.TypeOf((*MockEntry)(nil).BreadthSearch), ctx, path) } +// CanDelete mocks base method. +func (m *MockEntry) CanDelete() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CanDelete") + ret0, _ := ret[0].(bool) + return ret0 +} + +// CanDelete indicates an expected call of CanDelete. +func (mr *MockEntryMockRecorder) CanDelete() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CanDelete", reflect.TypeOf((*MockEntry)(nil).CanDelete)) +} + // CanDeleteBranch mocks base method. func (m *MockEntry) CanDeleteBranch(keepDefault bool) bool { m.ctrl.T.Helper() @@ -105,10 +134,10 @@ func (mr *MockEntryMockRecorder) CanDeleteBranch(keepDefault any) *gomock.Call { } // DeepCopy mocks base method. -func (m *MockEntry) DeepCopy(tc *tree.TreeContext, parent tree.Entry) (tree.Entry, error) { +func (m *MockEntry) DeepCopy(tc api.TreeContext, parent api.Entry) (api.Entry, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "DeepCopy", tc, parent) - ret0, _ := ret[0].(tree.Entry) + ret0, _ := ret[0].(api.Entry) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -146,10 +175,10 @@ func (mr *MockEntryMockRecorder) DeleteCanDeleteChilds(keepDefault any) *gomock. } // FilterChilds mocks base method. -func (m *MockEntry) FilterChilds(keys map[string]string) ([]tree.Entry, error) { +func (m *MockEntry) FilterChilds(keys map[string]string) ([]api.Entry, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "FilterChilds", keys) - ret0, _ := ret[0].([]tree.Entry) + ret0, _ := ret[0].([]api.Entry) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -175,10 +204,10 @@ func (mr *MockEntryMockRecorder) FinishInsertionPhase(ctx any) *gomock.Call { } // GetByOwner mocks base method. -func (m *MockEntry) GetByOwner(owner string, result []*tree.LeafEntry) tree.LeafVariantSlice { +func (m *MockEntry) GetByOwner(owner string, result []*api.LeafEntry) api.LeafVariantSlice { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetByOwner", owner, result) - ret0, _ := ret[0].(tree.LeafVariantSlice) + ret0, _ := ret[0].(api.LeafVariantSlice) return ret0 } @@ -189,10 +218,10 @@ func (mr *MockEntryMockRecorder) GetByOwner(owner, result any) *gomock.Call { } // GetChild mocks base method. -func (m *MockEntry) GetChild(name string) (tree.Entry, bool) { +func (m *MockEntry) GetChild(name string) (api.Entry, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetChild", name) - ret0, _ := ret[0].(tree.Entry) + ret0, _ := ret[0].(api.Entry) ret1, _ := ret[1].(bool) return ret0, ret1 } @@ -204,10 +233,10 @@ func (mr *MockEntryMockRecorder) GetChild(name any) *gomock.Call { } // GetChilds mocks base method. -func (m *MockEntry) GetChilds(arg0 types.DescendMethod) tree.EntryMap { +func (m *MockEntry) GetChilds(arg0 types.DescendMethod) api.EntryMap { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetChilds", arg0) - ret0, _ := ret[0].(tree.EntryMap) + ret0, _ := ret[0].(api.EntryMap) return ret0 } @@ -245,10 +274,10 @@ func (mr *MockEntryMockRecorder) GetDeviations(ctx, ch, activeCase any) *gomock. } // GetFirstAncestorWithSchema mocks base method. -func (m *MockEntry) GetFirstAncestorWithSchema() (tree.Entry, int) { +func (m *MockEntry) GetFirstAncestorWithSchema() (api.Entry, int) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetFirstAncestorWithSchema") - ret0, _ := ret[0].(tree.Entry) + ret0, _ := ret[0].(api.Entry) ret1, _ := ret[1].(int) return ret0, ret1 } @@ -260,10 +289,10 @@ func (mr *MockEntryMockRecorder) GetFirstAncestorWithSchema() *gomock.Call { } // GetHighestPrecedence mocks base method. -func (m *MockEntry) GetHighestPrecedence(result tree.LeafVariantSlice, onlyNewOrUpdated, includeDefaults, includeExplicitDelete bool) tree.LeafVariantSlice { +func (m *MockEntry) GetHighestPrecedence(result api.LeafVariantSlice, onlyNewOrUpdated, includeDefaults, includeExplicitDelete bool) api.LeafVariantSlice { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetHighestPrecedence", result, onlyNewOrUpdated, includeDefaults, includeExplicitDelete) - ret0, _ := ret[0].(tree.LeafVariantSlice) + ret0, _ := ret[0].(api.LeafVariantSlice) return ret0 } @@ -273,18 +302,47 @@ func (mr *MockEntryMockRecorder) GetHighestPrecedence(result, onlyNewOrUpdated, return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHighestPrecedence", reflect.TypeOf((*MockEntry)(nil).GetHighestPrecedence), result, onlyNewOrUpdated, includeDefaults, includeExplicitDelete) } -// GetLeafVariantEntries mocks base method. -func (m *MockEntry) GetLeafVariantEntries() tree.LeafVariantEntries { +// GetHighestPrecedenceLeafValue mocks base method. +func (m *MockEntry) GetHighestPrecedenceLeafValue(arg0 context.Context) (*api.LeafEntry, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetLeafVariantEntries") - ret0, _ := ret[0].(tree.LeafVariantEntries) + ret := m.ctrl.Call(m, "GetHighestPrecedenceLeafValue", arg0) + ret0, _ := ret[0].(*api.LeafEntry) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetHighestPrecedenceLeafValue indicates an expected call of GetHighestPrecedenceLeafValue. +func (mr *MockEntryMockRecorder) GetHighestPrecedenceLeafValue(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHighestPrecedenceLeafValue", reflect.TypeOf((*MockEntry)(nil).GetHighestPrecedenceLeafValue), arg0) +} + +// GetHighestPrecedenceValueOfBranch mocks base method. +func (m *MockEntry) GetHighestPrecedenceValueOfBranch(filter api.HighestPrecedenceFilter) int32 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetHighestPrecedenceValueOfBranch", filter) + ret0, _ := ret[0].(int32) return ret0 } -// GetLeafVariantEntries indicates an expected call of GetLeafVariantEntries. -func (mr *MockEntryMockRecorder) GetLeafVariantEntries() *gomock.Call { +// GetHighestPrecedenceValueOfBranch indicates an expected call of GetHighestPrecedenceValueOfBranch. +func (mr *MockEntryMockRecorder) GetHighestPrecedenceValueOfBranch(filter any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLeafVariantEntries", reflect.TypeOf((*MockEntry)(nil).GetLeafVariantEntries)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHighestPrecedenceValueOfBranch", reflect.TypeOf((*MockEntry)(nil).GetHighestPrecedenceValueOfBranch), filter) +} + +// GetLeafVariants mocks base method. +func (m *MockEntry) GetLeafVariants() *api.LeafVariants { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLeafVariants") + ret0, _ := ret[0].(*api.LeafVariants) + return ret0 +} + +// GetLeafVariants indicates an expected call of GetLeafVariants. +func (mr *MockEntryMockRecorder) GetLeafVariants() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLeafVariants", reflect.TypeOf((*MockEntry)(nil).GetLeafVariants)) } // GetLevel mocks base method. @@ -302,10 +360,10 @@ func (mr *MockEntryMockRecorder) GetLevel() *gomock.Call { } // GetListChilds mocks base method. -func (m *MockEntry) GetListChilds() ([]tree.Entry, error) { +func (m *MockEntry) GetListChilds() ([]api.Entry, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetListChilds") - ret0, _ := ret[0].([]tree.Entry) + ret0, _ := ret[0].([]api.Entry) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -316,11 +374,26 @@ func (mr *MockEntryMockRecorder) GetListChilds() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetListChilds", reflect.TypeOf((*MockEntry)(nil).GetListChilds)) } +// GetOrCreateChilds mocks base method. +func (m *MockEntry) GetOrCreateChilds(ctx context.Context, path *sdcpb.Path) (api.Entry, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetOrCreateChilds", ctx, path) + ret0, _ := ret[0].(api.Entry) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetOrCreateChilds indicates an expected call of GetOrCreateChilds. +func (mr *MockEntryMockRecorder) GetOrCreateChilds(ctx, path any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOrCreateChilds", reflect.TypeOf((*MockEntry)(nil).GetOrCreateChilds), ctx, path) +} + // GetParent mocks base method. -func (m *MockEntry) GetParent() tree.Entry { +func (m *MockEntry) GetParent() api.Entry { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetParent") - ret0, _ := ret[0].(tree.Entry) + ret0, _ := ret[0].(api.Entry) return ret0 } @@ -331,10 +404,10 @@ func (mr *MockEntryMockRecorder) GetParent() *gomock.Call { } // GetRoot mocks base method. -func (m *MockEntry) GetRoot() tree.Entry { +func (m *MockEntry) GetRoot() api.Entry { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetRoot") - ret0, _ := ret[0].(tree.Entry) + ret0, _ := ret[0].(api.Entry) return ret0 } @@ -345,10 +418,10 @@ func (mr *MockEntryMockRecorder) GetRoot() *gomock.Call { } // GetRootBasedEntryChain mocks base method. -func (m *MockEntry) GetRootBasedEntryChain() []tree.Entry { +func (m *MockEntry) GetRootBasedEntryChain() []api.Entry { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetRootBasedEntryChain") - ret0, _ := ret[0].([]tree.Entry) + ret0, _ := ret[0].([]api.Entry) return ret0 } @@ -387,10 +460,10 @@ func (mr *MockEntryMockRecorder) GetSchemaKeys() *gomock.Call { } // GetTreeContext mocks base method. -func (m *MockEntry) GetTreeContext() *tree.TreeContext { +func (m *MockEntry) GetTreeContext() api.TreeContext { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetTreeContext") - ret0, _ := ret[0].(*tree.TreeContext) + ret0, _ := ret[0].(api.TreeContext) return ret0 } @@ -429,10 +502,10 @@ func (mr *MockEntryMockRecorder) IsRoot() *gomock.Call { } // NavigateLeafRef mocks base method. -func (m *MockEntry) NavigateLeafRef(ctx context.Context) ([]tree.Entry, error) { +func (m *MockEntry) NavigateLeafRef(ctx context.Context) ([]api.Entry, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "NavigateLeafRef", ctx) - ret0, _ := ret[0].([]tree.Entry) + ret0, _ := ret[0].([]api.Entry) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -444,10 +517,10 @@ func (mr *MockEntryMockRecorder) NavigateLeafRef(ctx any) *gomock.Call { } // NavigateSdcpbPath mocks base method. -func (m *MockEntry) NavigateSdcpbPath(ctx context.Context, path *sdcpb.Path) (tree.Entry, error) { +func (m *MockEntry) NavigateSdcpbPath(ctx context.Context, path *sdcpb.Path) (api.Entry, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "NavigateSdcpbPath", ctx, path) - ret0, _ := ret[0].(tree.Entry) + ret0, _ := ret[0].(api.Entry) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -500,6 +573,20 @@ func (mr *MockEntryMockRecorder) SdcpbPath() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SdcpbPath", reflect.TypeOf((*MockEntry)(nil).SdcpbPath)) } +// ShouldDelete mocks base method. +func (m *MockEntry) ShouldDelete() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ShouldDelete") + ret0, _ := ret[0].(bool) + return ret0 +} + +// ShouldDelete indicates an expected call of ShouldDelete. +func (mr *MockEntryMockRecorder) ShouldDelete() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ShouldDelete", reflect.TypeOf((*MockEntry)(nil).ShouldDelete)) +} + // StringIndent mocks base method. func (m *MockEntry) StringIndent(result []string) []string { m.ctrl.T.Helper() @@ -544,6 +631,21 @@ func (mr *MockEntryMockRecorder) ToJsonIETF(onlyNewOrUpdated any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToJsonIETF", reflect.TypeOf((*MockEntry)(nil).ToJsonIETF), onlyNewOrUpdated) } +// ToJsonInternal mocks base method. +func (m *MockEntry) ToJsonInternal(onlyNewOrUpdated, ietf bool) (any, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ToJsonInternal", onlyNewOrUpdated, ietf) + ret0, _ := ret[0].(any) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ToJsonInternal indicates an expected call of ToJsonInternal. +func (mr *MockEntryMockRecorder) ToJsonInternal(onlyNewOrUpdated, ietf any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToJsonInternal", reflect.TypeOf((*MockEntry)(nil).ToJsonInternal), onlyNewOrUpdated, ietf) +} + // ToXML mocks base method. func (m *MockEntry) ToXML(onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove bool) (*etree.Document, error) { m.ctrl.T.Helper() @@ -559,6 +661,21 @@ func (mr *MockEntryMockRecorder) ToXML(onlyNewOrUpdated, honorNamespace, operati return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToXML", reflect.TypeOf((*MockEntry)(nil).ToXML), onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove) } +// ToXmlInternal mocks base method. +func (m *MockEntry) ToXmlInternal(parent *etree.Element, onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove bool) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ToXmlInternal", parent, onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ToXmlInternal indicates an expected call of ToXmlInternal. +func (mr *MockEntryMockRecorder) ToXmlInternal(parent, onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToXmlInternal", reflect.TypeOf((*MockEntry)(nil).ToXmlInternal), parent, onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove) +} + // TreeExport mocks base method. func (m *MockEntry) TreeExport(owner string) ([]*tree_persist.TreeElement, error) { m.ctrl.T.Helper() @@ -586,145 +703,28 @@ func (mr *MockEntryMockRecorder) ValidateLevel(ctx, resultChan, stats, vCfg any) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateLevel", reflect.TypeOf((*MockEntry)(nil).ValidateLevel), ctx, resultChan, stats, vCfg) } -// addUpdateRecursiveInternal mocks base method. -func (m *MockEntry) addUpdateRecursiveInternal(ctx context.Context, path *sdcpb.Path, idx int, u *types.Update, flags *types.UpdateInsertFlags) (tree.Entry, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "addUpdateRecursiveInternal", ctx, path, idx, u, flags) - ret0, _ := ret[0].(tree.Entry) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// addUpdateRecursiveInternal indicates an expected call of addUpdateRecursiveInternal. -func (mr *MockEntryMockRecorder) addUpdateRecursiveInternal(ctx, path, idx, u, flags any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "addUpdateRecursiveInternal", reflect.TypeOf((*MockEntry)(nil).addUpdateRecursiveInternal), ctx, path, idx, u, flags) -} - -// canDelete mocks base method. -func (m *MockEntry) canDelete() bool { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "canDelete") - ret0, _ := ret[0].(bool) - return ret0 -} - -// canDelete indicates an expected call of canDelete. -func (mr *MockEntryMockRecorder) canDelete() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "canDelete", reflect.TypeOf((*MockEntry)(nil).canDelete)) -} - -// getHighestPrecedenceLeafValue mocks base method. -func (m *MockEntry) getHighestPrecedenceLeafValue(arg0 context.Context) (*tree.LeafEntry, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "getHighestPrecedenceLeafValue", arg0) - ret0, _ := ret[0].(*tree.LeafEntry) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// getHighestPrecedenceLeafValue indicates an expected call of getHighestPrecedenceLeafValue. -func (mr *MockEntryMockRecorder) getHighestPrecedenceLeafValue(arg0 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getHighestPrecedenceLeafValue", reflect.TypeOf((*MockEntry)(nil).getHighestPrecedenceLeafValue), arg0) -} - -// getHighestPrecedenceValueOfBranch mocks base method. -func (m *MockEntry) getHighestPrecedenceValueOfBranch(filter tree.HighestPrecedenceFilter) int32 { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "getHighestPrecedenceValueOfBranch", filter) - ret0, _ := ret[0].(int32) - return ret0 -} - -// getHighestPrecedenceValueOfBranch indicates an expected call of getHighestPrecedenceValueOfBranch. -func (mr *MockEntryMockRecorder) getHighestPrecedenceValueOfBranch(filter any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getHighestPrecedenceValueOfBranch", reflect.TypeOf((*MockEntry)(nil).getHighestPrecedenceValueOfBranch), filter) -} - -// getOrCreateChilds mocks base method. -func (m *MockEntry) getOrCreateChilds(ctx context.Context, path *sdcpb.Path) (tree.Entry, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "getOrCreateChilds", ctx, path) - ret0, _ := ret[0].(tree.Entry) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// getOrCreateChilds indicates an expected call of getOrCreateChilds. -func (mr *MockEntryMockRecorder) getOrCreateChilds(ctx, path any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getOrCreateChilds", reflect.TypeOf((*MockEntry)(nil).getOrCreateChilds), ctx, path) -} - -// shouldDelete mocks base method. -func (m *MockEntry) shouldDelete() bool { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "shouldDelete") - ret0, _ := ret[0].(bool) - return ret0 -} - -// shouldDelete indicates an expected call of shouldDelete. -func (mr *MockEntryMockRecorder) shouldDelete() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "shouldDelete", reflect.TypeOf((*MockEntry)(nil).shouldDelete)) -} - -// toJsonInternal mocks base method. -func (m *MockEntry) toJsonInternal(onlyNewOrUpdated, ietf bool) (any, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "toJsonInternal", onlyNewOrUpdated, ietf) - ret0, _ := ret[0].(any) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// toJsonInternal indicates an expected call of toJsonInternal. -func (mr *MockEntryMockRecorder) toJsonInternal(onlyNewOrUpdated, ietf any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "toJsonInternal", reflect.TypeOf((*MockEntry)(nil).toJsonInternal), onlyNewOrUpdated, ietf) -} - -// toXmlInternal mocks base method. -func (m *MockEntry) toXmlInternal(parent *etree.Element, onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove bool) (bool, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "toXmlInternal", parent, onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove) - ret0, _ := ret[0].(bool) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// toXmlInternal indicates an expected call of toXmlInternal. -func (mr *MockEntryMockRecorder) toXmlInternal(parent, onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "toXmlInternal", reflect.TypeOf((*MockEntry)(nil).toXmlInternal), parent, onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove) -} - -// validateMandatory mocks base method. -func (m *MockEntry) validateMandatory(ctx context.Context, resultChan chan<- *types.ValidationResultEntry, stats *types.ValidationStats) { +// ValidateMandatory mocks base method. +func (m *MockEntry) ValidateMandatory(ctx context.Context, resultChan chan<- *types.ValidationResultEntry, stats *types.ValidationStats) { m.ctrl.T.Helper() - m.ctrl.Call(m, "validateMandatory", ctx, resultChan, stats) + m.ctrl.Call(m, "ValidateMandatory", ctx, resultChan, stats) } -// validateMandatory indicates an expected call of validateMandatory. -func (mr *MockEntryMockRecorder) validateMandatory(ctx, resultChan, stats any) *gomock.Call { +// ValidateMandatory indicates an expected call of ValidateMandatory. +func (mr *MockEntryMockRecorder) ValidateMandatory(ctx, resultChan, stats any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "validateMandatory", reflect.TypeOf((*MockEntry)(nil).validateMandatory), ctx, resultChan, stats) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateMandatory", reflect.TypeOf((*MockEntry)(nil).ValidateMandatory), ctx, resultChan, stats) } -// validateMandatoryWithKeys mocks base method. -func (m *MockEntry) validateMandatoryWithKeys(ctx context.Context, level int, attributes []string, choiceName string, resultChan chan<- *types.ValidationResultEntry) { +// ValidateMandatoryWithKeys mocks base method. +func (m *MockEntry) ValidateMandatoryWithKeys(ctx context.Context, level int, attributes []string, choiceName string, resultChan chan<- *types.ValidationResultEntry) { m.ctrl.T.Helper() - m.ctrl.Call(m, "validateMandatoryWithKeys", ctx, level, attributes, choiceName, resultChan) + m.ctrl.Call(m, "ValidateMandatoryWithKeys", ctx, level, attributes, choiceName, resultChan) } -// validateMandatoryWithKeys indicates an expected call of validateMandatoryWithKeys. -func (mr *MockEntryMockRecorder) validateMandatoryWithKeys(ctx, level, attributes, choiceName, resultChan any) *gomock.Call { +// ValidateMandatoryWithKeys indicates an expected call of ValidateMandatoryWithKeys. +func (mr *MockEntryMockRecorder) ValidateMandatoryWithKeys(ctx, level, attributes, choiceName, resultChan any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "validateMandatoryWithKeys", reflect.TypeOf((*MockEntry)(nil).validateMandatoryWithKeys), ctx, level, attributes, choiceName, resultChan) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateMandatoryWithKeys", reflect.TypeOf((*MockEntry)(nil).ValidateMandatoryWithKeys), ctx, level, attributes, choiceName, resultChan) } // MockLeafVariantEntry is a mock of LeafVariantEntry interface. @@ -752,10 +752,10 @@ func (m *MockLeafVariantEntry) EXPECT() *MockLeafVariantEntryMockRecorder { } // GetEntry mocks base method. -func (m *MockLeafVariantEntry) GetEntry() tree.Entry { +func (m *MockLeafVariantEntry) GetEntry() api.Entry { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetEntry") - ret0, _ := ret[0].(tree.Entry) + ret0, _ := ret[0].(api.Entry) return ret0 } @@ -766,10 +766,10 @@ func (mr *MockLeafVariantEntryMockRecorder) GetEntry() *gomock.Call { } // MarkDelete mocks base method. -func (m *MockLeafVariantEntry) MarkDelete(onlyIntended bool) *tree.LeafEntry { +func (m *MockLeafVariantEntry) MarkDelete(onlyIntended bool) *api.LeafEntry { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "MarkDelete", onlyIntended) - ret0, _ := ret[0].(*tree.LeafEntry) + ret0, _ := ret[0].(*api.LeafEntry) return ret0 } @@ -818,7 +818,7 @@ func (m *MockLeafVariantEntries) EXPECT() *MockLeafVariantEntriesMockRecorder { } // Add mocks base method. -func (m *MockLeafVariantEntries) Add(l *tree.LeafEntry) { +func (m *MockLeafVariantEntries) Add(l *api.LeafEntry) { m.ctrl.T.Helper() m.ctrl.Call(m, "Add", l) } @@ -830,10 +830,10 @@ func (mr *MockLeafVariantEntriesMockRecorder) Add(l any) *gomock.Call { } // AddExplicitDeleteEntry mocks base method. -func (m *MockLeafVariantEntries) AddExplicitDeleteEntry(owner string, priority int32) *tree.LeafEntry { +func (m *MockLeafVariantEntries) AddExplicitDeleteEntry(owner string, priority int32) *api.LeafEntry { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AddExplicitDeleteEntry", owner, priority) - ret0, _ := ret[0].(*tree.LeafEntry) + ret0, _ := ret[0].(*api.LeafEntry) return ret0 } @@ -844,7 +844,7 @@ func (mr *MockLeafVariantEntriesMockRecorder) AddExplicitDeleteEntry(owner, prio } // AddWithStats mocks base method. -func (m *MockLeafVariantEntries) AddWithStats(l *tree.LeafEntry, stats *types.ImportStats) { +func (m *MockLeafVariantEntries) AddWithStats(l *api.LeafEntry, stats *types.ImportStats) { m.ctrl.T.Helper() m.ctrl.Call(m, "AddWithStats", l, stats) } @@ -856,10 +856,10 @@ func (mr *MockLeafVariantEntriesMockRecorder) AddWithStats(l, stats any) *gomock } // DeleteByOwner mocks base method. -func (m *MockLeafVariantEntries) DeleteByOwner(owner string) *tree.LeafEntry { +func (m *MockLeafVariantEntries) DeleteByOwner(owner string) *api.LeafEntry { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "DeleteByOwner", owner) - ret0, _ := ret[0].(*tree.LeafEntry) + ret0, _ := ret[0].(*api.LeafEntry) return ret0 } @@ -870,10 +870,10 @@ func (mr *MockLeafVariantEntriesMockRecorder) DeleteByOwner(owner any) *gomock.C } // GetByOwner mocks base method. -func (m *MockLeafVariantEntries) GetByOwner(owner string) *tree.LeafEntry { +func (m *MockLeafVariantEntries) GetByOwner(owner string) *api.LeafEntry { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetByOwner", owner) - ret0, _ := ret[0].(*tree.LeafEntry) + ret0, _ := ret[0].(*api.LeafEntry) return ret0 } @@ -884,10 +884,10 @@ func (mr *MockLeafVariantEntriesMockRecorder) GetByOwner(owner any) *gomock.Call } // GetHighestPrecedence mocks base method. -func (m *MockLeafVariantEntries) GetHighestPrecedence(onlyNewOrUpdated, includeDefaults, includeExplicitDeletes bool) *tree.LeafEntry { +func (m *MockLeafVariantEntries) GetHighestPrecedence(onlyNewOrUpdated, includeDefaults, includeExplicitDeletes bool) *api.LeafEntry { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetHighestPrecedence", onlyNewOrUpdated, includeDefaults, includeExplicitDeletes) - ret0, _ := ret[0].(*tree.LeafEntry) + ret0, _ := ret[0].(*api.LeafEntry) return ret0 } @@ -898,10 +898,10 @@ func (mr *MockLeafVariantEntriesMockRecorder) GetHighestPrecedence(onlyNewOrUpda } // GetRunning mocks base method. -func (m *MockLeafVariantEntries) GetRunning() *tree.LeafEntry { +func (m *MockLeafVariantEntries) GetRunning() *api.LeafEntry { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetRunning") - ret0, _ := ret[0].(*tree.LeafEntry) + ret0, _ := ret[0].(*api.LeafEntry) return ret0 } @@ -926,10 +926,10 @@ func (mr *MockLeafVariantEntriesMockRecorder) Length() *gomock.Call { } // MarkOwnerForDeletion mocks base method. -func (m *MockLeafVariantEntries) MarkOwnerForDeletion(owner string, onlyIntended bool) *tree.LeafEntry { +func (m *MockLeafVariantEntries) MarkOwnerForDeletion(owner string, onlyIntended bool) *api.LeafEntry { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "MarkOwnerForDeletion", owner, onlyIntended) - ret0, _ := ret[0].(*tree.LeafEntry) + ret0, _ := ret[0].(*api.LeafEntry) return ret0 } @@ -940,10 +940,10 @@ func (mr *MockLeafVariantEntriesMockRecorder) MarkOwnerForDeletion(owner, onlyIn } // RemoveDeletedByOwner mocks base method. -func (m *MockLeafVariantEntries) RemoveDeletedByOwner(owner string) *tree.LeafEntry { +func (m *MockLeafVariantEntries) RemoveDeletedByOwner(owner string) *api.LeafEntry { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "RemoveDeletedByOwner", owner) - ret0, _ := ret[0].(*tree.LeafEntry) + ret0, _ := ret[0].(*api.LeafEntry) return ret0 } diff --git a/pkg/datastore/intent_rpc.go b/pkg/datastore/intent_rpc.go index 73cbed4e..60bfc800 100644 --- a/pkg/datastore/intent_rpc.go +++ b/pkg/datastore/intent_rpc.go @@ -23,6 +23,7 @@ import ( targettypes "github.com/sdcio/data-server/pkg/datastore/target/types" "github.com/sdcio/data-server/pkg/tree" + "github.com/sdcio/data-server/pkg/tree/consts" "github.com/sdcio/data-server/pkg/tree/importer/proto" "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils" @@ -54,7 +55,7 @@ func (d *Datastore) applyIntent(ctx context.Context, source targettypes.TargetSo func (d *Datastore) GetIntent(ctx context.Context, intentName string) (GetIntentResponse, error) { // serve running from synctree - if intentName == tree.RunningIntentName { + if intentName == consts.RunningIntentName { d.syncTreeMutex.RLock() defer d.syncTreeMutex.RUnlock() err := d.syncTree.FinishInsertionPhase(ctx) diff --git a/pkg/datastore/sync.go b/pkg/datastore/sync.go index 2c85cda2..c7ba8ecd 100644 --- a/pkg/datastore/sync.go +++ b/pkg/datastore/sync.go @@ -5,6 +5,7 @@ import ( "sync" "github.com/sdcio/data-server/pkg/tree" + "github.com/sdcio/data-server/pkg/tree/consts" "github.com/sdcio/data-server/pkg/tree/importer" treetypes "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/logger" @@ -30,7 +31,7 @@ func (d *Datastore) ApplyToRunning(ctx context.Context, deletes []*sdcpb.Path, i continue } // apply delete marker, setting owner delete flag on running intent - err = tree.NewOwnerDeleteMarker(tree.NewOwnerDeleteMarkerTaskConfig(tree.RunningIntentName, false)).Run(deleteRoot, d.taskPool) + err = tree.NewOwnerDeleteMarker(tree.NewOwnerDeleteMarkerTaskConfig(consts.RunningIntentName, false)).Run(deleteRoot, d.taskPool) if err != nil { log.Error(err, "failed applying delete to path", "path", delete.ToXPath(false)) continue @@ -46,7 +47,7 @@ func (d *Datastore) ApplyToRunning(ctx context.Context, deletes []*sdcpb.Path, i } // run remove deleted processor to clean up entries marked as deleted by owner - delProcessorParams := tree.NewRemoveDeletedProcessorParameters(tree.RunningIntentName) + delProcessorParams := tree.NewRemoveDeletedProcessorParameters(consts.RunningIntentName) err := tree.NewRemoveDeletedProcessor(delProcessorParams).Run(d.syncTree.GetRoot(), d.taskPool) if err != nil { return err @@ -54,7 +55,7 @@ func (d *Datastore) ApplyToRunning(ctx context.Context, deletes []*sdcpb.Path, i // delete entries that have zero-length leaf variant entries after remove deleted processing for _, e := range delProcessorParams.GetZeroLengthLeafVariantEntries() { - err := e.GetParent().DeleteBranch(ctx, &sdcpb.Path{Elem: []*sdcpb.PathElem{sdcpb.NewPathElem(e.PathName(), nil)}}, tree.RunningIntentName) + err := e.GetParent().DeleteBranch(ctx, &sdcpb.Path{Elem: []*sdcpb.PathElem{sdcpb.NewPathElem(e.PathName(), nil)}}, consts.RunningIntentName) if err != nil { return err } @@ -62,7 +63,7 @@ func (d *Datastore) ApplyToRunning(ctx context.Context, deletes []*sdcpb.Path, i // conditional trace logging if log := log.V(logger.VTrace); log.Enabled() { - treeExport, err := d.syncTree.TreeExport(tree.RunningIntentName, tree.RunningValuesPrio) + treeExport, err := d.syncTree.TreeExport(consts.RunningIntentName, consts.RunningValuesPrio) if err == nil { json, err := protojson.MarshalOptions{Multiline: false}.Marshal(treeExport) if err == nil { diff --git a/pkg/datastore/sync_test.go b/pkg/datastore/sync_test.go index f6faf4d7..23c202d4 100644 --- a/pkg/datastore/sync_test.go +++ b/pkg/datastore/sync_test.go @@ -16,6 +16,7 @@ import ( "github.com/sdcio/data-server/pkg/datastore/target" "github.com/sdcio/data-server/pkg/pool" "github.com/sdcio/data-server/pkg/tree" + "github.com/sdcio/data-server/pkg/tree/consts" "github.com/sdcio/data-server/pkg/tree/importer" jsonImporter "github.com/sdcio/data-server/pkg/tree/importer/json" "github.com/sdcio/data-server/pkg/tree/types" @@ -81,7 +82,7 @@ func TestApplyToRunning(t *testing.T) { json.Unmarshal([]byte(confStr), &v) vpf := pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0)) - _, err = root.ImportConfig(ctx, &sdcpb.Path{}, jsonImporter.NewJsonTreeImporter(v, tree.RunningIntentName, tree.RunningValuesPrio, false), types.NewUpdateInsertFlags(), vpf) + _, err = root.ImportConfig(ctx, &sdcpb.Path{}, jsonImporter.NewJsonTreeImporter(v, consts.RunningIntentName, consts.RunningValuesPrio, false), types.NewUpdateInsertFlags(), vpf) if err != nil { t.Fatalf("failed to import test config: %v", err) } @@ -109,7 +110,7 @@ func TestApplyToRunning(t *testing.T) { var v any json.Unmarshal([]byte(confStr), &v) - return jsonImporter.NewJsonTreeImporter(v, tree.RunningIntentName, tree.RunningValuesPrio, false) + return jsonImporter.NewJsonTreeImporter(v, consts.RunningIntentName, consts.RunningValuesPrio, false) }, resultFunc: func() any { d := &sdcio_schema.Device{ @@ -187,7 +188,7 @@ func TestApplyToRunning(t *testing.T) { json.Unmarshal([]byte(confStr), &v) vpf := pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0)) - _, err = root.ImportConfig(ctx, &sdcpb.Path{}, jsonImporter.NewJsonTreeImporter(v, tree.RunningIntentName, tree.RunningValuesPrio, false), types.NewUpdateInsertFlags(), vpf) + _, err = root.ImportConfig(ctx, &sdcpb.Path{}, jsonImporter.NewJsonTreeImporter(v, consts.RunningIntentName, consts.RunningValuesPrio, false), types.NewUpdateInsertFlags(), vpf) if err != nil { t.Fatalf("failed to import test config: %v", err) } @@ -214,7 +215,7 @@ func TestApplyToRunning(t *testing.T) { var v any json.Unmarshal([]byte(confStr), &v) - return jsonImporter.NewJsonTreeImporter(v, tree.RunningIntentName, tree.RunningValuesPrio, false) + return jsonImporter.NewJsonTreeImporter(v, consts.RunningIntentName, consts.RunningValuesPrio, false) }, resultFunc: func() any { d := &sdcio_schema.Device{ @@ -291,7 +292,7 @@ func TestApplyToRunning(t *testing.T) { json.Unmarshal([]byte(confStr), &v) vpf := pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0)) - _, err = root.ImportConfig(ctx, &sdcpb.Path{}, jsonImporter.NewJsonTreeImporter(v, tree.RunningIntentName, tree.RunningValuesPrio, false), types.NewUpdateInsertFlags(), vpf) + _, err = root.ImportConfig(ctx, &sdcpb.Path{}, jsonImporter.NewJsonTreeImporter(v, consts.RunningIntentName, consts.RunningValuesPrio, false), types.NewUpdateInsertFlags(), vpf) if err != nil { t.Fatalf("failed to import test config: %v", err) } @@ -319,7 +320,7 @@ func TestApplyToRunning(t *testing.T) { var v any json.Unmarshal([]byte(confStr), &v) - return jsonImporter.NewJsonTreeImporter(v, tree.RunningIntentName, tree.RunningValuesPrio, false) + return jsonImporter.NewJsonTreeImporter(v, consts.RunningIntentName, consts.RunningValuesPrio, false) }, resultFunc: func() any { d := &sdcio_schema.Device{ @@ -400,7 +401,7 @@ func TestApplyToRunning(t *testing.T) { d := tt.resultFunc() vpf := pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0)) - _, err = resultRoot.ImportConfig(ctx, &sdcpb.Path{}, jsonImporter.NewJsonTreeImporter(d, tree.RunningIntentName, tree.RunningValuesPrio, false), types.NewUpdateInsertFlags(), vpf) + _, err = resultRoot.ImportConfig(ctx, &sdcpb.Path{}, jsonImporter.NewJsonTreeImporter(d, consts.RunningIntentName, consts.RunningValuesPrio, false), types.NewUpdateInsertFlags(), vpf) if err != nil { t.Fatalf("failed to import test config: %v", err) } diff --git a/pkg/datastore/target/gnmi/get.go b/pkg/datastore/target/gnmi/get.go index ab5900e1..7779679c 100644 --- a/pkg/datastore/target/gnmi/get.go +++ b/pkg/datastore/target/gnmi/get.go @@ -9,6 +9,7 @@ import ( "github.com/sdcio/data-server/pkg/datastore/target/gnmi/utils" "github.com/sdcio/data-server/pkg/datastore/target/types" "github.com/sdcio/data-server/pkg/tree" + "github.com/sdcio/data-server/pkg/tree/consts" "github.com/sdcio/data-server/pkg/tree/importer/proto" treetypes "github.com/sdcio/data-server/pkg/tree/types" dsutils "github.com/sdcio/data-server/pkg/utils" @@ -142,7 +143,7 @@ func (s *GetSync) internalGetSync(req *sdcpb.GetDataRequest) { return } - result, err := s.syncTree.TreeExport(tree.RunningIntentName, tree.RunningValuesPrio) + result, err := s.syncTree.TreeExport(consts.RunningIntentName, consts.RunningValuesPrio) if err != nil { log.Error(err, "failure exporting synctree") return @@ -171,7 +172,7 @@ func (s *GetSync) processNotifications(n []*sdcpb.Notification) error { for _, noti := range n { // updates - upds, err := treetypes.ExpandAndConvertIntent(s.ctx, s.schemaClient, tree.RunningIntentName, tree.RunningValuesPrio, noti.Update, noti.GetTimestamp()) + upds, err := treetypes.ExpandAndConvertIntent(s.ctx, s.schemaClient, consts.RunningIntentName, consts.RunningValuesPrio, noti.Update, noti.GetTimestamp()) if err != nil { log.Error(err, "failure expanding and converting notification") continue diff --git a/pkg/datastore/target/gnmi/stream.go b/pkg/datastore/target/gnmi/stream.go index 546e2119..0fa71541 100644 --- a/pkg/datastore/target/gnmi/stream.go +++ b/pkg/datastore/target/gnmi/stream.go @@ -14,6 +14,7 @@ import ( "github.com/sdcio/data-server/pkg/datastore/target/types" "github.com/sdcio/data-server/pkg/pool" "github.com/sdcio/data-server/pkg/tree" + "github.com/sdcio/data-server/pkg/tree/consts" "github.com/sdcio/data-server/pkg/tree/importer/proto" treetypes "github.com/sdcio/data-server/pkg/tree/types" dsutils "github.com/sdcio/data-server/pkg/utils" @@ -148,7 +149,7 @@ func (s *StreamSync) buildTreeSyncWithDatastore(cUS <-chan *NotificationData, sy if err != nil { log.Error(err, "failed adding update to synctree") } - syncTree.GetTreeContext().AddExplicitDeletes(tree.RunningIntentName, tree.RunningValuesPrio, noti.deletes) + syncTree.GetTreeContext().ExplicitDeletes().Add(consts.RunningIntentName, consts.RunningValuesPrio, noti.deletes) case <-syncResponse: syncTree, err = s.syncToRunning(syncTree, syncTreeMutex, true) tickerActive = true @@ -221,7 +222,7 @@ func (s *StreamSync) syncToRunning(syncTree *tree.RootEntry, m *sync.Mutex, logC defer m.Unlock() startTime := time.Now() - result, err := syncTree.TreeExport(tree.RunningIntentName, tree.RunningValuesPrio) + result, err := syncTree.TreeExport(consts.RunningIntentName, consts.RunningValuesPrio) log.V(logger.VTrace).Info("exported tree", "tree", result.String()) if err != nil { @@ -290,7 +291,7 @@ func (t *notificationProcessorTask) Run(ctx context.Context, _ func(pool.Task) e log := logger.FromContext(ctx) sn := dsutils.ToSchemaNotification(ctx, t.item) // updates - upds, err := treetypes.ExpandAndConvertIntent(ctx, t.params.schemaClientBound, tree.RunningIntentName, tree.RunningValuesPrio, sn.GetUpdate(), t.item.GetTimestamp()) + upds, err := treetypes.ExpandAndConvertIntent(ctx, t.params.schemaClientBound, consts.RunningIntentName, consts.RunningValuesPrio, sn.GetUpdate(), t.item.GetTimestamp()) if err != nil { log.Error(err, "expansion and conversion failed") } diff --git a/pkg/datastore/target/netconf/nc.go b/pkg/datastore/target/netconf/nc.go index fea9b7ab..376b4853 100644 --- a/pkg/datastore/target/netconf/nc.go +++ b/pkg/datastore/target/netconf/nc.go @@ -30,7 +30,7 @@ import ( "github.com/sdcio/data-server/pkg/datastore/target/netconf/driver/scrapligo" nctypes "github.com/sdcio/data-server/pkg/datastore/target/netconf/types" "github.com/sdcio/data-server/pkg/datastore/target/types" - "github.com/sdcio/data-server/pkg/tree" + "github.com/sdcio/data-server/pkg/tree/consts" "github.com/sdcio/data-server/pkg/tree/importer" "github.com/sdcio/data-server/pkg/tree/importer/xml" ) @@ -89,7 +89,7 @@ func (t *ncTarget) GetImportAdapter(ctx context.Context, req *sdcpb.GetDataReque return nil, err } - xmlImport := xml.NewXmlTreeImporter(ncResponse.Doc.Root(), tree.RunningIntentName, tree.RunningValuesPrio, false) + xmlImport := xml.NewXmlTreeImporter(ncResponse.Doc.Root(), consts.RunningIntentName, consts.RunningValuesPrio, false) return xmlImport, nil } diff --git a/pkg/datastore/transaction_rpc.go b/pkg/datastore/transaction_rpc.go index 9e5c7fd7..7792db43 100644 --- a/pkg/datastore/transaction_rpc.go +++ b/pkg/datastore/transaction_rpc.go @@ -10,6 +10,8 @@ import ( "github.com/sdcio/data-server/pkg/datastore/types" "github.com/sdcio/data-server/pkg/tree" + "github.com/sdcio/data-server/pkg/tree/api" + "github.com/sdcio/data-server/pkg/tree/consts" treeproto "github.com/sdcio/data-server/pkg/tree/importer/proto" treetypes "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils" @@ -81,7 +83,7 @@ func (d *Datastore) replaceIntent(ctx context.Context, transaction *types.Transa } // store the actual / old running in the transaction - runningProto, err := d.cacheClient.IntentGet(ctx, tree.RunningIntentName) + runningProto, err := d.cacheClient.IntentGet(ctx, consts.RunningIntentName) if err != nil { return nil, err } @@ -214,7 +216,7 @@ func (d *Datastore) lowlevelTransactionSet(ctx context.Context, transaction *typ // iterate through all the intents for _, intent := range transaction.GetNewIntents() { // update the TreeContext to reflect the actual owner (intent name) - lvs := tree.LeafVariantSlice{} + lvs := api.LeafVariantSlice{} lvs = root.GetByOwner(intent.GetName(), lvs) oldIntentContent := lvs.ToPathAndUpdateSlice() @@ -227,7 +229,7 @@ func (d *Datastore) lowlevelTransactionSet(ctx context.Context, transaction *typ } // clear the owners existing explicit delete entries, retrieving the old entries for storing in the transaction for possible rollback - oldExplicitDeletes := root.GetTreeContext().RemoveExplicitDeletes(intent.GetName()) + oldExplicitDeletes := root.GetTreeContext().ExplicitDeletes().Remove(intent.GetName()) priority := int32(math.MaxInt32) if len(oldIntentContent) > 0 { @@ -254,14 +256,14 @@ func (d *Datastore) lowlevelTransactionSet(ctx context.Context, transaction *typ } // add the explicit delete entries - root.GetTreeContext().AddExplicitDeletes(intent.GetName(), intent.GetPriority(), intent.GetDeletes()) + root.GetTreeContext().ExplicitDeletes().Add(intent.GetName(), intent.GetPriority(), intent.GetDeletes()) } root.SetNonRevertiveIntent(intent.GetName(), intent.NonRevertive()) } - les := tree.LeafVariantSlice{} - les = root.GetByOwner(tree.RunningIntentName, les) + les := api.LeafVariantSlice{} + les = root.GetByOwner(consts.RunningIntentName, les) transaction.GetOldRunning().AddUpdates(les.ToPathAndUpdateSlice()) @@ -396,9 +398,9 @@ func (d *Datastore) lowlevelTransactionSet(ctx context.Context, transaction *typ } // writeBackSyncTree applies the provided changes to the syncTree and applies to the running cache intent -func (d *Datastore) writeBackSyncTree(ctx context.Context, updates tree.LeafVariantSlice, deletes treetypes.DeleteEntriesList) error { +func (d *Datastore) writeBackSyncTree(ctx context.Context, updates api.LeafVariantSlice, deletes treetypes.DeleteEntriesList) error { log := logger.FromContext(ctx) - runningUpdates := updates.ToUpdateSlice().CopyWithNewOwnerAndPrio(tree.RunningIntentName, tree.RunningValuesPrio) + runningUpdates := updates.ToUpdateSlice().CopyWithNewOwnerAndPrio(consts.RunningIntentName, consts.RunningValuesPrio) // wrap the lock in an anonymous function to be able to utilize defer for the unlock err := func() error { @@ -407,7 +409,7 @@ func (d *Datastore) writeBackSyncTree(ctx context.Context, updates tree.LeafVari defer d.syncTreeMutex.Unlock() // perform deletes - err := d.syncTree.DeleteBranchPaths(ctx, deletes, tree.RunningIntentName) + err := d.syncTree.DeleteBranchPaths(ctx, deletes, consts.RunningIntentName) if err != nil { return err } @@ -424,7 +426,7 @@ func (d *Datastore) writeBackSyncTree(ctx context.Context, updates tree.LeafVari } // export the synctree - newRunningIntent, err := d.syncTree.TreeExport(tree.RunningIntentName, tree.RunningValuesPrio) + newRunningIntent, err := d.syncTree.TreeExport(consts.RunningIntentName, consts.RunningValuesPrio) if err != nil && err != tree.ErrorIntentNotPresent { return err } @@ -543,7 +545,7 @@ func (d *Datastore) TransactionSet(ctx context.Context, transactionId string, tr return response, err } -func updateToSdcpbUpdate(lvs tree.LeafVariantSlice) ([]*sdcpb.Update, error) { +func updateToSdcpbUpdate(lvs api.LeafVariantSlice) ([]*sdcpb.Update, error) { result := make([]*sdcpb.Update, 0, len(lvs)) for _, lv := range lvs { path := lv.GetEntry().SdcpbPath() diff --git a/pkg/datastore/transaction_rpc_test.go b/pkg/datastore/transaction_rpc_test.go index 2dc0ee12..4f4d4227 100644 --- a/pkg/datastore/transaction_rpc_test.go +++ b/pkg/datastore/transaction_rpc_test.go @@ -16,6 +16,7 @@ import ( "github.com/sdcio/data-server/pkg/datastore/types" "github.com/sdcio/data-server/pkg/pool" "github.com/sdcio/data-server/pkg/tree" + "github.com/sdcio/data-server/pkg/tree/consts" jsonImporter "github.com/sdcio/data-server/pkg/tree/importer/json" treetypes "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils/testhelper" @@ -147,7 +148,7 @@ func TestTransactionSet_PreviouslyApplied(t *testing.T) { } vpf := pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0)) // Populate SyncTree with Running config - _, err = syncTreeRoot.ImportConfig(ctx, &sdcpb.Path{}, jsonImporter.NewJsonTreeImporter(runningAny, tree.RunningIntentName, tree.RunningValuesPrio, false), treetypes.NewUpdateInsertFlags(), vpf) + _, err = syncTreeRoot.ImportConfig(ctx, &sdcpb.Path{}, jsonImporter.NewJsonTreeImporter(runningAny, consts.RunningIntentName, consts.RunningValuesPrio, false), treetypes.NewUpdateInsertFlags(), vpf) if err != nil { t.Fatalf("failed to import running config: %v", err) } diff --git a/pkg/datastore/tree_operation_test.go b/pkg/datastore/tree_operation_test.go index a3e3be45..a3cb10b1 100644 --- a/pkg/datastore/tree_operation_test.go +++ b/pkg/datastore/tree_operation_test.go @@ -27,6 +27,7 @@ import ( schemaClient "github.com/sdcio/data-server/pkg/datastore/clients/schema" "github.com/sdcio/data-server/pkg/pool" "github.com/sdcio/data-server/pkg/tree" + "github.com/sdcio/data-server/pkg/tree/consts" jsonImporter "github.com/sdcio/data-server/pkg/tree/importer/json" "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils/testhelper" @@ -122,98 +123,98 @@ func TestDatastore_populateTree(t *testing.T) { sdcpb.NewPathElem("doublekey", map[string]string{"key1": "k11.1", "key2": "k21.2"}), sdcpb.NewPathElem("key1", nil), }, IsRootBased: true}, - types.NewUpdate(nil, testhelper.GetStringTvProto("k11.1"), tree.RunningValuesPrio, tree.RunningIntentName, 0), + types.NewUpdate(nil, testhelper.GetStringTvProto("k11.1"), consts.RunningValuesPrio, consts.RunningIntentName, 0), ), types.NewPathAndUpdate(&sdcpb.Path{Elem: []*sdcpb.PathElem{ sdcpb.NewPathElem("doublekey", map[string]string{"key1": "k12.1", "key2": "k21.2"}), sdcpb.NewPathElem("key2", nil), }, IsRootBased: true}, - types.NewUpdate(nil, testhelper.GetStringTvProto("k21.2"), tree.RunningValuesPrio, tree.RunningIntentName, 0)), + types.NewUpdate(nil, testhelper.GetStringTvProto("k21.2"), consts.RunningValuesPrio, consts.RunningIntentName, 0)), types.NewPathAndUpdate(&sdcpb.Path{Elem: []*sdcpb.PathElem{ sdcpb.NewPathElem("doublekey", map[string]string{"key1": "k11.1", "key2": "k21.2"}), sdcpb.NewPathElem("mandato", nil), }, IsRootBased: true}, - types.NewUpdate(nil, testhelper.GetStringTvProto("TheMandatoryValue1"), tree.RunningValuesPrio, tree.RunningIntentName, 0)), + types.NewUpdate(nil, testhelper.GetStringTvProto("TheMandatoryValue1"), consts.RunningValuesPrio, consts.RunningIntentName, 0)), types.NewPathAndUpdate(&sdcpb.Path{Elem: []*sdcpb.PathElem{ sdcpb.NewPathElem("doublekey", map[string]string{"key1": "k11.1", "key2": "k21.2"}), sdcpb.NewPathElem("cont", nil), sdcpb.NewPathElem("value1", nil), }, IsRootBased: true}, - types.NewUpdate(nil, testhelper.GetStringTvProto("containerval1.1"), tree.RunningValuesPrio, tree.RunningIntentName, 0)), + types.NewUpdate(nil, testhelper.GetStringTvProto("containerval1.1"), consts.RunningValuesPrio, consts.RunningIntentName, 0)), types.NewPathAndUpdate(&sdcpb.Path{Elem: []*sdcpb.PathElem{ sdcpb.NewPathElem("doublekey", map[string]string{"key1": "k11.1", "key2": "k21.2"}), sdcpb.NewPathElem("cont", nil), sdcpb.NewPathElem("value2", nil), }, IsRootBased: true}, - types.NewUpdate(nil, testhelper.GetStringTvProto("containerval1.2"), tree.RunningValuesPrio, tree.RunningIntentName, 0)), + types.NewUpdate(nil, testhelper.GetStringTvProto("containerval1.2"), consts.RunningValuesPrio, consts.RunningIntentName, 0)), types.NewPathAndUpdate(&sdcpb.Path{Elem: []*sdcpb.PathElem{ sdcpb.NewPathElem("doublekey", map[string]string{"key1": "k11.1", "key2": "k21.3"}), sdcpb.NewPathElem("key1", nil), }, IsRootBased: true}, - types.NewUpdate(nil, testhelper.GetStringTvProto("k11.1"), tree.RunningValuesPrio, tree.RunningIntentName, 0)), + types.NewUpdate(nil, testhelper.GetStringTvProto("k11.1"), consts.RunningValuesPrio, consts.RunningIntentName, 0)), types.NewPathAndUpdate(&sdcpb.Path{Elem: []*sdcpb.PathElem{ sdcpb.NewPathElem("doublekey", map[string]string{"key1": "k11.1", "key2": "k21.3"}), sdcpb.NewPathElem("key2", nil), }, IsRootBased: true}, - types.NewUpdate(nil, testhelper.GetStringTvProto("k21.3"), tree.RunningValuesPrio, tree.RunningIntentName, 0)), + types.NewUpdate(nil, testhelper.GetStringTvProto("k21.3"), consts.RunningValuesPrio, consts.RunningIntentName, 0)), types.NewPathAndUpdate(&sdcpb.Path{Elem: []*sdcpb.PathElem{ sdcpb.NewPathElem("doublekey", map[string]string{"key1": "k11.1", "key2": "k21.3"}), sdcpb.NewPathElem("mandato", nil), }, IsRootBased: true}, - types.NewUpdate(nil, testhelper.GetStringTvProto("TheMandatoryValue1"), tree.RunningValuesPrio, tree.RunningIntentName, 0)), + types.NewUpdate(nil, testhelper.GetStringTvProto("TheMandatoryValue1"), consts.RunningValuesPrio, consts.RunningIntentName, 0)), types.NewPathAndUpdate(&sdcpb.Path{Elem: []*sdcpb.PathElem{ sdcpb.NewPathElem("doublekey", map[string]string{"key1": "k11.1", "key2": "k21.3"}), sdcpb.NewPathElem("cont", nil), sdcpb.NewPathElem("value1", nil), }, IsRootBased: true}, - types.NewUpdate(nil, testhelper.GetStringTvProto("containerval1.1"), tree.RunningValuesPrio, tree.RunningIntentName, 0)), + types.NewUpdate(nil, testhelper.GetStringTvProto("containerval1.1"), consts.RunningValuesPrio, consts.RunningIntentName, 0)), types.NewPathAndUpdate(&sdcpb.Path{Elem: []*sdcpb.PathElem{ sdcpb.NewPathElem("doublekey", map[string]string{"key1": "k11.1", "key2": "k21.3"}), sdcpb.NewPathElem("cont", nil), sdcpb.NewPathElem("value2", nil), }, IsRootBased: true}, - types.NewUpdate(nil, testhelper.GetStringTvProto("containerval1.2"), tree.RunningValuesPrio, tree.RunningIntentName, 0)), + types.NewUpdate(nil, testhelper.GetStringTvProto("containerval1.2"), consts.RunningValuesPrio, consts.RunningIntentName, 0)), types.NewPathAndUpdate(&sdcpb.Path{Elem: []*sdcpb.PathElem{ sdcpb.NewPathElem("doublekey", map[string]string{"key1": "k12.1", "key2": "k22.2"}), sdcpb.NewPathElem("key1", nil), }, IsRootBased: true}, - types.NewUpdate(nil, testhelper.GetStringTvProto("k12.1"), tree.RunningValuesPrio, tree.RunningIntentName, 0)), + types.NewUpdate(nil, testhelper.GetStringTvProto("k12.1"), consts.RunningValuesPrio, consts.RunningIntentName, 0)), types.NewPathAndUpdate(&sdcpb.Path{Elem: []*sdcpb.PathElem{ sdcpb.NewPathElem("doublekey", map[string]string{"key1": "k12.1", "key2": "k22.2"}), sdcpb.NewPathElem("key2", nil), }, IsRootBased: true}, - types.NewUpdate(nil, testhelper.GetStringTvProto("k22.2"), tree.RunningValuesPrio, tree.RunningIntentName, 0)), + types.NewUpdate(nil, testhelper.GetStringTvProto("k22.2"), consts.RunningValuesPrio, consts.RunningIntentName, 0)), types.NewPathAndUpdate(&sdcpb.Path{Elem: []*sdcpb.PathElem{ sdcpb.NewPathElem("doublekey", map[string]string{"key1": "k12.1", "key2": "k22.2"}), sdcpb.NewPathElem("mandato", nil), }, IsRootBased: true}, - types.NewUpdate(nil, testhelper.GetStringTvProto("TheMandatoryValue2"), tree.RunningValuesPrio, tree.RunningIntentName, 0)), + types.NewUpdate(nil, testhelper.GetStringTvProto("TheMandatoryValue2"), consts.RunningValuesPrio, consts.RunningIntentName, 0)), types.NewPathAndUpdate(&sdcpb.Path{Elem: []*sdcpb.PathElem{ sdcpb.NewPathElem("doublekey", map[string]string{"key1": "k12.1", "key2": "k22.2"}), sdcpb.NewPathElem("cont", nil), sdcpb.NewPathElem("value1", nil), }, IsRootBased: true}, - types.NewUpdate(nil, testhelper.GetStringTvProto("containerval2.1"), tree.RunningValuesPrio, tree.RunningIntentName, 0)), + types.NewUpdate(nil, testhelper.GetStringTvProto("containerval2.1"), consts.RunningValuesPrio, consts.RunningIntentName, 0)), types.NewPathAndUpdate(&sdcpb.Path{Elem: []*sdcpb.PathElem{ sdcpb.NewPathElem("doublekey", map[string]string{"key1": "k12.1", "key2": "k22.2"}), sdcpb.NewPathElem("cont", nil), sdcpb.NewPathElem("value2", nil), }, IsRootBased: true}, - types.NewUpdate(nil, testhelper.GetStringTvProto("containerval2.2"), tree.RunningValuesPrio, tree.RunningIntentName, 0)), + types.NewUpdate(nil, testhelper.GetStringTvProto("containerval2.2"), consts.RunningValuesPrio, consts.RunningIntentName, 0)), }, expectedDeletes: []string{ @@ -837,9 +838,9 @@ func TestDatastore_populateTree(t *testing.T) { }, runningStoreUpdates: []*types.PathAndUpdate{ types.NewPathAndUpdate(&sdcpb.Path{Elem: []*sdcpb.PathElem{sdcpb.NewPathElem("interface", map[string]string{"name": "ethernet-1/1"}), sdcpb.NewPathElem("name", nil)}, IsRootBased: true}, - types.NewUpdate(nil, testhelper.GetStringTvProto("ethernet-1/1"), tree.RunningValuesPrio, tree.RunningIntentName, 0)), + types.NewUpdate(nil, testhelper.GetStringTvProto("ethernet-1/1"), consts.RunningValuesPrio, consts.RunningIntentName, 0)), types.NewPathAndUpdate(&sdcpb.Path{Elem: []*sdcpb.PathElem{sdcpb.NewPathElem("interface", map[string]string{"name": "ethernet-1/1"}), sdcpb.NewPathElem("description", nil)}, IsRootBased: true}, - types.NewUpdate(nil, testhelper.GetStringTvProto("MyDescription"), tree.RunningValuesPrio, tree.RunningIntentName, 0)), + types.NewUpdate(nil, testhelper.GetStringTvProto("MyDescription"), consts.RunningValuesPrio, consts.RunningIntentName, 0)), }, expectedOwnerUpdates: []*types.PathAndUpdate{ types.NewPathAndUpdate(&sdcpb.Path{Elem: []*sdcpb.PathElem{sdcpb.NewPathElem("interface", map[string]string{"name": "ethernet-1/1"}), sdcpb.NewPathElem("name", nil)}, IsRootBased: true}, @@ -937,34 +938,34 @@ func TestDatastore_populateTree(t *testing.T) { sdcpb.NewPathElem("interface", map[string]string{"name": "ethernet-1/1"}), sdcpb.NewPathElem("name", nil), }, IsRootBased: true}, - types.NewUpdate(nil, testhelper.GetStringTvProto("ethernet-1/1"), tree.RunningValuesPrio, tree.RunningIntentName, 0)), + types.NewUpdate(nil, testhelper.GetStringTvProto("ethernet-1/1"), consts.RunningValuesPrio, consts.RunningIntentName, 0)), types.NewPathAndUpdate(&sdcpb.Path{Elem: []*sdcpb.PathElem{ sdcpb.NewPathElem("interface", map[string]string{"name": "ethernet-1/1"}), sdcpb.NewPathElem("description", nil), }, IsRootBased: true}, - types.NewUpdate(nil, testhelper.GetStringTvProto("MyDescription"), tree.RunningValuesPrio, tree.RunningIntentName, 0)), + types.NewUpdate(nil, testhelper.GetStringTvProto("MyDescription"), consts.RunningValuesPrio, consts.RunningIntentName, 0)), types.NewPathAndUpdate(&sdcpb.Path{Elem: []*sdcpb.PathElem{ sdcpb.NewPathElem("interface", map[string]string{"name": "ethernet-1/2"}), sdcpb.NewPathElem("name", nil), }, IsRootBased: true}, - types.NewUpdate(nil, testhelper.GetStringTvProto("ethernet-1/2"), tree.RunningValuesPrio, tree.RunningIntentName, 0)), + types.NewUpdate(nil, testhelper.GetStringTvProto("ethernet-1/2"), consts.RunningValuesPrio, consts.RunningIntentName, 0)), types.NewPathAndUpdate(&sdcpb.Path{Elem: []*sdcpb.PathElem{ sdcpb.NewPathElem("interface", map[string]string{"name": "ethernet-1/2"}), sdcpb.NewPathElem("description", nil), }, IsRootBased: true}, - types.NewUpdate(nil, testhelper.GetStringTvProto("Owner3 Description"), tree.RunningValuesPrio, tree.RunningIntentName, 0)), + types.NewUpdate(nil, testhelper.GetStringTvProto("Owner3 Description"), consts.RunningValuesPrio, consts.RunningIntentName, 0)), types.NewPathAndUpdate(&sdcpb.Path{Elem: []*sdcpb.PathElem{ sdcpb.NewPathElem("interface", map[string]string{"name": "ethernet-1/2"}), sdcpb.NewPathElem("subinterface", map[string]string{"index": "2"}), sdcpb.NewPathElem("index", nil), }, IsRootBased: true}, - types.NewUpdate(nil, testhelper.GetUIntTvProto(2), tree.RunningValuesPrio, tree.RunningIntentName, 0)), + types.NewUpdate(nil, testhelper.GetUIntTvProto(2), consts.RunningValuesPrio, consts.RunningIntentName, 0)), types.NewPathAndUpdate(&sdcpb.Path{Elem: []*sdcpb.PathElem{ sdcpb.NewPathElem("interface", map[string]string{"name": "ethernet-1/2"}), sdcpb.NewPathElem("subinterface", map[string]string{"index": "2"}), sdcpb.NewPathElem("description", nil), }, IsRootBased: true}, - types.NewUpdate(nil, testhelper.GetStringTvProto("Subinterface Desc"), tree.RunningValuesPrio, tree.RunningIntentName, 0)), + types.NewUpdate(nil, testhelper.GetStringTvProto("Subinterface Desc"), consts.RunningValuesPrio, consts.RunningIntentName, 0)), }, expectedModify: []*types.PathAndUpdate{ types.NewPathAndUpdate(&sdcpb.Path{Elem: []*sdcpb.PathElem{ @@ -1124,34 +1125,34 @@ func TestDatastore_populateTree(t *testing.T) { sdcpb.NewPathElem("interface", map[string]string{"name": "ethernet-1/1"}), sdcpb.NewPathElem("name", nil), }, IsRootBased: true}, - types.NewUpdate(nil, testhelper.GetStringTvProto("ethernet-1/1"), tree.RunningValuesPrio, tree.RunningIntentName, 0)), + types.NewUpdate(nil, testhelper.GetStringTvProto("ethernet-1/1"), consts.RunningValuesPrio, consts.RunningIntentName, 0)), types.NewPathAndUpdate(&sdcpb.Path{Elem: []*sdcpb.PathElem{ sdcpb.NewPathElem("interface", map[string]string{"name": "ethernet-1/1"}), sdcpb.NewPathElem("description", nil), }, IsRootBased: true}, - types.NewUpdate(nil, testhelper.GetStringTvProto("MyDescription"), tree.RunningValuesPrio, tree.RunningIntentName, 0)), + types.NewUpdate(nil, testhelper.GetStringTvProto("MyDescription"), consts.RunningValuesPrio, consts.RunningIntentName, 0)), types.NewPathAndUpdate(&sdcpb.Path{Elem: []*sdcpb.PathElem{ sdcpb.NewPathElem("interface", map[string]string{"name": "ethernet-1/2"}), sdcpb.NewPathElem("name", nil), }, IsRootBased: true}, - types.NewUpdate(nil, testhelper.GetStringTvProto("ethernet-1/2"), tree.RunningValuesPrio, tree.RunningIntentName, 0)), + types.NewUpdate(nil, testhelper.GetStringTvProto("ethernet-1/2"), consts.RunningValuesPrio, consts.RunningIntentName, 0)), types.NewPathAndUpdate(&sdcpb.Path{Elem: []*sdcpb.PathElem{ sdcpb.NewPathElem("interface", map[string]string{"name": "ethernet-1/2"}), sdcpb.NewPathElem("description", nil), }, IsRootBased: true}, - types.NewUpdate(nil, testhelper.GetStringTvProto("Owner3 Description"), tree.RunningValuesPrio, tree.RunningIntentName, 0)), + types.NewUpdate(nil, testhelper.GetStringTvProto("Owner3 Description"), consts.RunningValuesPrio, consts.RunningIntentName, 0)), types.NewPathAndUpdate(&sdcpb.Path{Elem: []*sdcpb.PathElem{ sdcpb.NewPathElem("interface", map[string]string{"name": "ethernet-1/2"}), sdcpb.NewPathElem("subinterface", map[string]string{"index": "2"}), sdcpb.NewPathElem("index", nil), }, IsRootBased: true}, - types.NewUpdate(nil, testhelper.GetUIntTvProto(1), tree.RunningValuesPrio, tree.RunningIntentName, 0)), + types.NewUpdate(nil, testhelper.GetUIntTvProto(1), consts.RunningValuesPrio, consts.RunningIntentName, 0)), types.NewPathAndUpdate(&sdcpb.Path{Elem: []*sdcpb.PathElem{ sdcpb.NewPathElem("interface", map[string]string{"name": "ethernet-1/2"}), sdcpb.NewPathElem("subinterface", map[string]string{"index": "2"}), sdcpb.NewPathElem("description", nil), }, IsRootBased: true}, - types.NewUpdate(nil, testhelper.GetStringTvProto("Subinterface Desc"), tree.RunningValuesPrio, tree.RunningIntentName, 0)), + types.NewUpdate(nil, testhelper.GetStringTvProto("Subinterface Desc"), consts.RunningValuesPrio, consts.RunningIntentName, 0)), }, expectedModify: []*types.PathAndUpdate{ types.NewPathAndUpdate(&sdcpb.Path{Elem: []*sdcpb.PathElem{ @@ -1351,12 +1352,12 @@ func TestDatastore_populateTree(t *testing.T) { sdcpb.NewPathElem("case-elem", nil), sdcpb.NewPathElem("elem", nil), }, IsRootBased: true}, - types.NewUpdate(nil, testhelper.GetStringTvProto("case1-content"), tree.RunningValuesPrio, tree.RunningIntentName, 0)), + types.NewUpdate(nil, testhelper.GetStringTvProto("case1-content"), consts.RunningValuesPrio, consts.RunningIntentName, 0)), types.NewPathAndUpdate(&sdcpb.Path{Elem: []*sdcpb.PathElem{ sdcpb.NewPathElem("choices", nil), sdcpb.NewPathElem("case1", nil), sdcpb.NewPathElem("log", nil), - }, IsRootBased: true}, types.NewUpdate(nil, TypedValueFalse, tree.RunningValuesPrio, tree.RunningIntentName, 0)), + }, IsRootBased: true}, types.NewUpdate(nil, TypedValueFalse, consts.RunningValuesPrio, consts.RunningIntentName, 0)), }, }, { @@ -1410,13 +1411,13 @@ func TestDatastore_populateTree(t *testing.T) { sdcpb.NewPathElem("case-elem", nil), sdcpb.NewPathElem("elem", nil), }, IsRootBased: true}, - types.NewUpdate(nil, testhelper.GetStringTvProto("case1-content"), tree.RunningValuesPrio, tree.RunningIntentName, 0)), + types.NewUpdate(nil, testhelper.GetStringTvProto("case1-content"), consts.RunningValuesPrio, consts.RunningIntentName, 0)), types.NewPathAndUpdate(&sdcpb.Path{Elem: []*sdcpb.PathElem{ sdcpb.NewPathElem("choices", nil), sdcpb.NewPathElem("case1", nil), sdcpb.NewPathElem("log", nil), }, IsRootBased: true}, - types.NewUpdate(nil, TypedValueFalse, tree.RunningValuesPrio, tree.RunningIntentName, 0)), + types.NewUpdate(nil, TypedValueFalse, consts.RunningValuesPrio, consts.RunningIntentName, 0)), }, }, { diff --git a/pkg/datastore/types/transaction.go b/pkg/datastore/types/transaction.go index 0b80ea25..afdd2e84 100644 --- a/pkg/datastore/types/transaction.go +++ b/pkg/datastore/types/transaction.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - "github.com/sdcio/data-server/pkg/tree" + "github.com/sdcio/data-server/pkg/tree/consts" treetypes "github.com/sdcio/data-server/pkg/tree/types" sdcpb "github.com/sdcio/sdc-protos/sdcpb" ) @@ -28,8 +28,8 @@ func NewTransaction(id string, tm *TransactionManager) *Transaction { transactionManager: tm, newIntents: map[string]*TransactionIntent{}, oldIntents: map[string]*TransactionIntent{}, - oldRunning: NewTransactionIntent(tree.RunningIntentName, 600), - replace: NewTransactionIntent(tree.ReplaceIntentName, tree.ReplaceValuesPrio), + oldRunning: NewTransactionIntent(consts.RunningIntentName, 600), + replace: NewTransactionIntent(consts.ReplaceIntentName, consts.ReplaceValuesPrio), } } diff --git a/pkg/server/transaction.go b/pkg/server/transaction.go index 78c1d6e4..481925ed 100644 --- a/pkg/server/transaction.go +++ b/pkg/server/transaction.go @@ -7,7 +7,7 @@ import ( "github.com/sdcio/data-server/pkg/datastore" "github.com/sdcio/data-server/pkg/datastore/types" - "github.com/sdcio/data-server/pkg/tree" + "github.com/sdcio/data-server/pkg/tree/consts" "github.com/sdcio/data-server/pkg/utils" logf "github.com/sdcio/logger" sdcpb "github.com/sdcio/sdc-protos/sdcpb" @@ -69,8 +69,8 @@ func (s *Server) TransactionSet(ctx context.Context, req *sdcpb.TransactionSetRe var replaceIntent *types.TransactionIntent if req.GetReplaceIntent() != nil { // overwrite replace priority and name with specific value - req.ReplaceIntent.Priority = tree.ReplaceValuesPrio - req.ReplaceIntent.Intent = tree.ReplaceIntentName + req.ReplaceIntent.Priority = consts.ReplaceValuesPrio + req.ReplaceIntent.Intent = consts.ReplaceIntentName replaceIntent, err = ds.SdcpbTransactionIntentToInternalTI(ctx, req.GetReplaceIntent()) if err != nil { diff --git a/pkg/tree/api/delete_path_prio.go b/pkg/tree/api/delete_path_prio.go new file mode 100644 index 00000000..297cfdd6 --- /dev/null +++ b/pkg/tree/api/delete_path_prio.go @@ -0,0 +1,43 @@ +package api + +import ( + "iter" + + sdcpb "github.com/sdcio/sdc-protos/sdcpb" +) + +type DeletePathPrio struct { + owner string + prio int32 + paths *sdcpb.PathSet +} + +func NewDeletePathPrio(owner string, prio int32) *DeletePathPrio { + return &DeletePathPrio{ + prio: prio, + owner: owner, + paths: sdcpb.NewPathSet(), + } +} + +func (ddp *DeletePathPrio) DeepCopy() *DeletePathPrio { + result := NewDeletePathPrio(ddp.GetOwner(), ddp.GetPrio()) + result.paths = ddp.paths.DeepCopy() + return result +} + +func (dpp *DeletePathPrio) GetPathSet() *sdcpb.PathSet { + return dpp.paths +} + +func (dpp *DeletePathPrio) PathItems() iter.Seq[*sdcpb.Path] { + return dpp.paths.Items() +} + +func (dpp *DeletePathPrio) GetPrio() int32 { + return dpp.prio +} + +func (dpp *DeletePathPrio) GetOwner() string { + return dpp.owner +} diff --git a/pkg/tree/intent_path_mapping.go b/pkg/tree/api/delete_path_set.go similarity index 60% rename from pkg/tree/intent_path_mapping.go rename to pkg/tree/api/delete_path_set.go index 14c9bf27..192ce805 100644 --- a/pkg/tree/intent_path_mapping.go +++ b/pkg/tree/api/delete_path_set.go @@ -1,4 +1,4 @@ -package tree +package api import ( "iter" @@ -25,7 +25,7 @@ func (dp *DeletePathSet) DeepCopy() *DeletePathSet { return result } -func (dp *DeletePathSet) RemoveIntentDeletes(intentName string) *sdcpb.PathSet { +func (dp *DeletePathSet) Remove(intentName string) *sdcpb.PathSet { if data, exists := dp.data[intentName]; exists { result := data.GetPathSet() delete(dp.data, intentName) @@ -63,39 +63,3 @@ func (dp *DeletePathSet) Items() iter.Seq[*DeletePathPrio] { } } } - -type DeletePathPrio struct { - owner string - prio int32 - paths *sdcpb.PathSet -} - -func NewDeletePathPrio(owner string, prio int32) *DeletePathPrio { - return &DeletePathPrio{ - prio: prio, - owner: owner, - paths: sdcpb.NewPathSet(), - } -} - -func (ddp *DeletePathPrio) DeepCopy() *DeletePathPrio { - result := NewDeletePathPrio(ddp.GetOwner(), ddp.GetPrio()) - result.paths = ddp.paths.DeepCopy() - return result -} - -func (dpp *DeletePathPrio) GetPathSet() *sdcpb.PathSet { - return dpp.paths -} - -func (dpp *DeletePathPrio) PathItems() iter.Seq[*sdcpb.Path] { - return dpp.paths.Items() -} - -func (dpp *DeletePathPrio) GetPrio() int32 { - return dpp.prio -} - -func (dpp *DeletePathPrio) GetOwner() string { - return dpp.owner -} diff --git a/pkg/tree/entry.go b/pkg/tree/api/entry.go similarity index 84% rename from pkg/tree/entry.go rename to pkg/tree/api/entry.go index 6dedbe03..84785343 100644 --- a/pkg/tree/entry.go +++ b/pkg/tree/api/entry.go @@ -1,8 +1,7 @@ -package tree +package api import ( "context" - "math" "github.com/beevik/etree" "github.com/sdcio/data-server/pkg/config" @@ -11,29 +10,6 @@ import ( "github.com/sdcio/sdc-protos/tree_persist" ) -const ( - KeysIndexSep = "_" - DefaultValuesPrio = int32(math.MaxInt32 - 90) - DefaultsIntentName = "default" - RunningValuesPrio = int32(math.MaxInt32 - 100) - RunningIntentName = "running" - ReplaceValuesPrio = int32(math.MaxInt32 - 110) - ReplaceIntentName = "replace" -) - -// NewEntry constructor for Entries -func NewEntry(ctx context.Context, parent Entry, pathElemName string, tc *TreeContext) (*sharedEntryAttributes, error) { - // create a new sharedEntryAttributes instance - sea, err := newSharedEntryAttributes(ctx, parent, pathElemName, tc) - if err != nil { - return nil, err - } - - // add the Entry as a child to the parent Entry - err = parent.AddChild(ctx, sea) - return sea, err -} - // Entry is the primary Element of the Tree. type Entry interface { // PathName returns the last Path element, the name of the Entry @@ -45,10 +21,10 @@ type Entry interface { // getOrCreateChilds retrieves the sub-child pointed at by the path. // if the path does not exist in its full extend, the entries will be added along the way // if the path does not point to a schema defined path an error will be raise - getOrCreateChilds(ctx context.Context, path *sdcpb.Path) (Entry, error) + GetOrCreateChilds(ctx context.Context, path *sdcpb.Path) (Entry, error) // AddUpdateRecursive Add the given cache.Update to the tree AddUpdateRecursive(ctx context.Context, relativePath *sdcpb.Path, u *types.Update, flags *types.UpdateInsertFlags) (Entry, error) - addUpdateRecursiveInternal(ctx context.Context, path *sdcpb.Path, idx int, u *types.Update, flags *types.UpdateInsertFlags) (Entry, error) + AddUpdateRecursiveInternal(ctx context.Context, path *sdcpb.Path, idx int, u *types.Update, flags *types.UpdateInsertFlags) (Entry, error) // StringIndent debug tree struct as indented string slice StringIndent(result []string) []string // GetHighesPrio return the new cache.Update entried from the tree that are the highes priority. @@ -57,7 +33,7 @@ type Entry interface { GetHighestPrecedence(result LeafVariantSlice, onlyNewOrUpdated bool, includeDefaults bool, includeExplicitDelete bool) LeafVariantSlice // getHighestPrecedenceLeafValue returns the highest LeafValue of the Entry at hand // will return an error if the Entry is not a Leaf - getHighestPrecedenceLeafValue(context.Context) (*LeafEntry, error) + GetHighestPrecedenceLeafValue(context.Context) (*LeafEntry, error) // GetByOwner returns the branches Updates by owner GetByOwner(owner string, result []*LeafEntry) LeafVariantSlice // // markOwnerDelete Sets the delete flag on all the LeafEntries belonging to the given owner. @@ -67,12 +43,12 @@ type Entry interface { // Validate kicks off validation ValidateLevel(ctx context.Context, resultChan chan<- *types.ValidationResultEntry, stats *types.ValidationStats, vCfg *config.Validation) // validateMandatory the Mandatory schema field - validateMandatory(ctx context.Context, resultChan chan<- *types.ValidationResultEntry, stats *types.ValidationStats) + ValidateMandatory(ctx context.Context, resultChan chan<- *types.ValidationResultEntry, stats *types.ValidationStats) // validateMandatoryWithKeys is an internally used function that us called by validateMandatory in case // the container has keys defined that need to be skipped before the mandatory attributes can be checked - validateMandatoryWithKeys(ctx context.Context, level int, attributes []string, choiceName string, resultChan chan<- *types.ValidationResultEntry) + ValidateMandatoryWithKeys(ctx context.Context, level int, attributes []string, choiceName string, resultChan chan<- *types.ValidationResultEntry) // getHighestPrecedenceValueOfBranch returns the highes Precedence Value (lowest Priority value) of the brach that starts at this Entry - getHighestPrecedenceValueOfBranch(filter HighestPrecedenceFilter) int32 + GetHighestPrecedenceValueOfBranch(filter HighestPrecedenceFilter) int32 // GetSchema returns the *sdcpb.SchemaElem of the Entry GetSchema() *sdcpb.SchemaElem // IsRoot returns true if the Entry is the root of the tree @@ -104,13 +80,13 @@ type Entry interface { // Or will disappear from device (running) as part of the update action. RemainsToExist() bool // shouldDelete returns true if an explicit delete should be issued for the given branch - shouldDelete() bool + ShouldDelete() bool // canDelete checks if the entry can be Deleted. // This is e.g. to cover e.g. defaults and running. They can be deleted, but should not, they are basically implicitly existing. // In caomparison to // - remainsToExists() returns true, because they remain to exist even though implicitly. // - shouldDelete() returns false, because no explicit delete should be issued for them. - canDelete() bool + CanDelete() bool GetChilds(types.DescendMethod) EntryMap GetChild(name string) (Entry, bool) // entry, exists FilterChilds(keys map[string]string) ([]Entry, error) @@ -122,10 +98,10 @@ type Entry interface { ToJsonIETF(onlyNewOrUpdated bool) (any, error) // toJsonInternal the internal function that produces JSON and JSON_IETF // Not for external usage - toJsonInternal(onlyNewOrUpdated bool, ietf bool) (j any, err error) + ToJsonInternal(onlyNewOrUpdated bool, ietf bool) (j any, err error) // ToXML returns the tree and its current state in the XML representation used by netconf ToXML(onlyNewOrUpdated bool, honorNamespace bool, operationWithNamespace bool, useOperationRemove bool) (*etree.Document, error) - toXmlInternal(parent *etree.Element, onlyNewOrUpdated bool, honorNamespace bool, operationWithNamespace bool, useOperationRemove bool) (doAdd bool, err error) + ToXmlInternal(parent *etree.Element, onlyNewOrUpdated bool, honorNamespace bool, operationWithNamespace bool, useOperationRemove bool) (doAdd bool, err error) TreeExport(owner string) ([]*tree_persist.TreeElement, error) // DeleteBranch Deletes from the tree, all elements of the PathSlice defined branch of the given owner DeleteBranch(ctx context.Context, path *sdcpb.Path, owner string) (err error) @@ -134,14 +110,14 @@ type Entry interface { // this is collecting all the last level key entries. GetListChilds() ([]Entry, error) BreadthSearch(ctx context.Context, path *sdcpb.Path) ([]Entry, error) - DeepCopy(tc *TreeContext, parent Entry) (Entry, error) - GetLeafVariantEntries() LeafVariantEntries + DeepCopy(tc TreeContext, parent Entry) (Entry, error) + GetLeafVariants() *LeafVariants // returns true if the Entry contains leafvariants (presence container, field or leaflist) HoldsLeafvariants() bool CanDeleteBranch(keepDefault bool) bool DeleteCanDeleteChilds(keepDefault bool) - GetTreeContext() *TreeContext + GetTreeContext() TreeContext } type LeafVariantEntry interface { @@ -170,3 +146,15 @@ const ( DescendMethodAll DescendMethod = iota DescendMethodActiveChilds ) + +type HighestPrecedenceFilter func(le *LeafEntry) bool + +func HighestPrecedenceFilterAll(le *LeafEntry) bool { + return true +} +func HighestPrecedenceFilterWithoutNew(le *LeafEntry) bool { + return !le.IsNew +} +func HighestPrecedenceFilterWithoutDeleted(le *LeafEntry) bool { + return !le.Delete +} diff --git a/pkg/tree/entry_map.go b/pkg/tree/api/entry_map.go similarity index 94% rename from pkg/tree/entry_map.go rename to pkg/tree/api/entry_map.go index 59fa6832..e8a8baac 100644 --- a/pkg/tree/entry_map.go +++ b/pkg/tree/api/entry_map.go @@ -1,4 +1,4 @@ -package tree +package api import ( "sort" diff --git a/pkg/tree/leaf_entry.go b/pkg/tree/api/leaf_entry.go similarity index 94% rename from pkg/tree/leaf_entry.go rename to pkg/tree/api/leaf_entry.go index 8abab2ba..5c8d4a9f 100644 --- a/pkg/tree/leaf_entry.go +++ b/pkg/tree/api/leaf_entry.go @@ -1,4 +1,4 @@ -package tree +package api import ( "fmt" @@ -68,13 +68,6 @@ func (l *LeafEntry) MarkNew() { l.IsNew = true } -func (l *LeafEntry) RemoveDeleteFlag() *LeafEntry { - l.mu.Lock() - defer l.mu.Unlock() - l.Delete = false - return l -} - func (l *LeafEntry) GetDeleteFlag() bool { l.mu.RLock() defer l.mu.RUnlock() @@ -146,7 +139,7 @@ func (l *LeafEntry) NonRevertive() bool { if l.parentEntry == nil { return false } - return l.parentEntry.GetTreeContext().IsNonRevertiveIntent(l.Owner()) + return l.parentEntry.GetTreeContext().NonRevertiveInfo().IsNonRevertive(l.Owner(), l) } // String returns a string representation of the LeafEntry @@ -156,7 +149,7 @@ func (l *LeafEntry) String() string { // Compare used for slices.SortFunc. Sorts by path and if equal paths then by owner as the second criteria func (l *LeafEntry) Compare(other *LeafEntry) int { - result := sdcpb.ComparePath(l.Path(), other.Path()) + result := sdcpb.ComparePath(l.SdcpbPath(), other.SdcpbPath()) if result != 0 { return result } diff --git a/pkg/tree/leaf_entry_filter.go b/pkg/tree/api/leaf_entry_filter.go similarity index 98% rename from pkg/tree/leaf_entry_filter.go rename to pkg/tree/api/leaf_entry_filter.go index b237c54f..5b7e831b 100644 --- a/pkg/tree/leaf_entry_filter.go +++ b/pkg/tree/api/leaf_entry_filter.go @@ -1,4 +1,4 @@ -package tree +package api import "github.com/sdcio/data-server/pkg/tree/types" diff --git a/pkg/tree/leaf_variant_slice.go b/pkg/tree/api/leaf_variant_slice.go similarity index 85% rename from pkg/tree/leaf_variant_slice.go rename to pkg/tree/api/leaf_variant_slice.go index 770abe4b..b3ee1aea 100644 --- a/pkg/tree/leaf_variant_slice.go +++ b/pkg/tree/api/leaf_variant_slice.go @@ -1,4 +1,4 @@ -package tree +package api import ( "fmt" @@ -19,10 +19,19 @@ func (lvs LeafVariantSlice) ToUpdateSlice() types.UpdateSlice { return result } +func (lvs LeafVariantSlice) GetByOwner(owner string) *LeafEntry { + for _, le := range lvs { + if le.Owner() == owner { + return le + } + } + return nil +} + func (lvs LeafVariantSlice) ToPathAndUpdateSlice() []*types.PathAndUpdate { result := make([]*types.PathAndUpdate, 0, len(lvs)) for _, x := range lvs { - result = append(result, types.NewPathAndUpdate(x.Path(), x.GetUpdate())) + result = append(result, types.NewPathAndUpdate(x.SdcpbPath(), x.GetUpdate())) } return result } @@ -68,7 +77,7 @@ func (lvs LeafVariantSlice) String() string { sep := "" for _, item := range lvs { sb.WriteString(sep) - sb.WriteString(item.Path().ToXPath(false)) + sb.WriteString(item.SdcpbPath().ToXPath(false)) sb.WriteString(" -> ") sb.WriteString(item.String()) if first { diff --git a/pkg/tree/leaf_variants.go b/pkg/tree/api/leaf_variants.go similarity index 95% rename from pkg/tree/leaf_variants.go rename to pkg/tree/api/leaf_variants.go index d3d8f17a..8bce7bb0 100644 --- a/pkg/tree/leaf_variants.go +++ b/pkg/tree/api/leaf_variants.go @@ -1,4 +1,4 @@ -package tree +package api import ( "context" @@ -6,6 +6,7 @@ import ( "math" "sync" + . "github.com/sdcio/data-server/pkg/tree/consts" "github.com/sdcio/data-server/pkg/tree/types" sdcpb "github.com/sdcio/sdc-protos/sdcpb" ) @@ -13,11 +14,11 @@ import ( type LeafVariants struct { les LeafVariantSlice lesMutex sync.RWMutex - tc *TreeContext + tc TreeContext parentEntry Entry } -func newLeafVariants(tc *TreeContext, parentEnty Entry) *LeafVariants { +func NewLeafVariants(tc TreeContext, parentEnty Entry) *LeafVariants { return &LeafVariants{ les: make(LeafVariantSlice, 0, 2), tc: tc, @@ -68,7 +69,7 @@ func (lv *LeafVariants) Length() int { return len(lv.les) } -func (lv *LeafVariants) canDeleteBranch(keepDefault bool) bool { +func (lv *LeafVariants) CanDeleteBranch(keepDefault bool) bool { lv.lesMutex.RLock() defer lv.lesMutex.RUnlock() @@ -135,8 +136,8 @@ func (lv *LeafVariants) checkOnlyRunningAndMaybeDefault() bool { return hasRunning && hasDefault } -// canDelete returns true if leafValues exist that are not owned by default or running that do not have the DeleteFlag set [or if delete is set, also the DeleteOnlyIntendedFlag set] -func (lv *LeafVariants) canDelete() bool { +// CanDelete returns true if leafValues exist that are not owned by default or running that do not have the DeleteFlag set [or if delete is set, also the DeleteOnlyIntendedFlag set] +func (lv *LeafVariants) CanDelete() bool { lv.lesMutex.RLock() defer lv.lesMutex.RUnlock() // only procede if we have leave variants @@ -169,10 +170,10 @@ func (lv *LeafVariants) canDelete() bool { return true } -// shouldDelete evaluates the LeafVariants and indicates if the overall result is, that the Entry referencing these +// ShouldDelete evaluates the LeafVariants and indicates if the overall result is, that the Entry referencing these // LeafVariants is explicitly to be deleted. Meaning there are no other LeafVariants remaining after the pending action, that // any LeafVariant other then Running or Defaults exist. -func (lv *LeafVariants) shouldDelete() bool { +func (lv *LeafVariants) ShouldDelete() bool { lv.lesMutex.RLock() defer lv.lesMutex.RUnlock() // only procede if we have leave variants @@ -210,7 +211,7 @@ func (lv *LeafVariants) shouldDelete() bool { return foundOtherThenRunningAndDefault } -func (lv *LeafVariants) remainsToExist() bool { +func (lv *LeafVariants) RemainsToExist() bool { lv.lesMutex.RLock() defer lv.lesMutex.RUnlock() // only procede if we have leave variants @@ -258,7 +259,7 @@ func (lv *LeafVariants) GetHighestPrecedenceValue(filter HighestPrecedenceFilter return result } -func (lv *LeafVariants) DeepCopy(tc *TreeContext, parent Entry) *LeafVariants { +func (lv *LeafVariants) DeepCopy(tc TreeContext, parent Entry) *LeafVariants { result := &LeafVariants{ lesMutex: sync.RWMutex{}, tc: tc, @@ -311,7 +312,7 @@ func (lv *LeafVariants) GetHighestPrecedence(onlyNewOrUpdated bool, includeDefau if len(lv.les) == 0 { return nil } - if onlyNewOrUpdated && lv.canDelete() { + if onlyNewOrUpdated && lv.CanDelete() { return nil } @@ -381,7 +382,7 @@ func (lv *LeafVariants) highestIsUnequalRunning(highest *LeafEntry) bool { } // if highest is not new or updated and highest is non-revertive - if !highest.IsNew && !highest.IsUpdated && lv.tc.IsNonRevertiveIntent(highest.Update.Owner()) { + if !highest.IsNew && !highest.IsUpdated && lv.tc.NonRevertiveInfo().IsGenerallyNonRevertive(highest.Update.Owner()) { return false } diff --git a/pkg/tree/leaf_variants_test.go b/pkg/tree/api/leaf_variants_test.go similarity index 98% rename from pkg/tree/leaf_variants_test.go rename to pkg/tree/api/leaf_variants_test.go index 1fcdfec6..4e8b865d 100644 --- a/pkg/tree/leaf_variants_test.go +++ b/pkg/tree/api/leaf_variants_test.go @@ -1,8 +1,9 @@ -package tree +package api import ( "testing" + . "github.com/sdcio/data-server/pkg/tree/consts" "github.com/sdcio/data-server/pkg/tree/types" sdcpb "github.com/sdcio/sdc-protos/sdcpb" ) @@ -278,7 +279,7 @@ func TestLeafVariants_remainsToExist(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { lv := tt.setup() - if got := lv.remainsToExist(); got != tt.expected { + if got := lv.RemainsToExist(); got != tt.expected { t.Errorf("LeafVariants.remainsToExist() = %v, want %v", got, tt.expected) } }) @@ -467,7 +468,7 @@ func TestLeafVariants_canDelete(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { lv := tt.setup() - if got := lv.canDelete(); got != tt.expected { + if got := lv.CanDelete(); got != tt.expected { t.Errorf("LeafVariants.canDelete() = %v, want %v", got, tt.expected) } }) diff --git a/pkg/tree/api/non_revertive_info.go b/pkg/tree/api/non_revertive_info.go new file mode 100644 index 00000000..f6e4ad10 --- /dev/null +++ b/pkg/tree/api/non_revertive_info.go @@ -0,0 +1,49 @@ +package api + +import ( + sdcpb "github.com/sdcio/sdc-protos/sdcpb" +) + +type NonRevertiveInfo struct { + intentName string + nonRevertive bool + revertPaths sdcpb.Paths +} + +func NewNonRevertiveInfo(intentName string, nonRevertive bool) *NonRevertiveInfo { + return &NonRevertiveInfo{ + intentName: intentName, + nonRevertive: nonRevertive, + revertPaths: sdcpb.Paths{}, + } +} + +func (n *NonRevertiveInfo) AddPath(path *sdcpb.Path) { + n.revertPaths = append(n.revertPaths, path) +} + +// IsGenerallyNonRevertive returns the general non-revertive state of the intent, which is true if the intent is non-revertive for all paths, false otherwise. +func (n *NonRevertiveInfo) IsGenerallyNonRevertive() bool { + return n.nonRevertive +} + +// IsNonRevertive returns true if the intent is non-revertive for the given path, false otherwise. +// If no paths are set for the intent, the general non-revertive state is returned. +func (n *NonRevertiveInfo) IsNonRevertive(p SdcpbPath) bool { + if len(n.revertPaths) == 0 { + return n.nonRevertive + } + + // checks if any path in the Paths slice is a parent path of the given path. If such a path is found, return true else false. + return !n.revertPaths.ContainsParentPath(p.SdcpbPath()) +} + +func (n *NonRevertiveInfo) DeepCopy() *NonRevertiveInfo { + copy := &NonRevertiveInfo{ + intentName: n.intentName, + nonRevertive: n.nonRevertive, + revertPaths: make(sdcpb.Paths, len(n.revertPaths)), + } + copy.revertPaths = n.revertPaths.DeepCopy() + return copy +} diff --git a/pkg/tree/api/non_revertive_infos.go b/pkg/tree/api/non_revertive_infos.go new file mode 100644 index 00000000..60ce9cb1 --- /dev/null +++ b/pkg/tree/api/non_revertive_infos.go @@ -0,0 +1,48 @@ +package api + +type NonRevertiveInfos map[string]*NonRevertiveInfo + +func NewNonRevertiveInfos() NonRevertiveInfos { + return make(map[string]*NonRevertiveInfo) +} + +func (n NonRevertiveInfos) Add(owner string, nonRevertive bool) { + info, ok := n[owner] + if !ok { + info = NewNonRevertiveInfo(owner, nonRevertive) + n[owner] = info + } +} + +func (n NonRevertiveInfos) AddNonRevertivePath(owner string, path SdcpbPath) { + info, ok := n[owner] + if !ok { + info = NewNonRevertiveInfo(owner, false) + n[owner] = info + } + info.AddPath(path.SdcpbPath()) +} + +func (n NonRevertiveInfos) IsNonRevertive(owner string, path SdcpbPath) bool { + info, ok := n[owner] + if !ok { + return false + } + return info.IsNonRevertive(path) +} + +func (n NonRevertiveInfos) IsGenerallyNonRevertive(owner string) bool { + info, ok := n[owner] + if !ok { + return false + } + return info.IsGenerallyNonRevertive() +} + +func (n NonRevertiveInfos) DeepCopy() NonRevertiveInfos { + m := make(NonRevertiveInfos, len(n)) + for k, v := range n { + m[k] = v.DeepCopy() + } + return m +} diff --git a/pkg/tree/api/sdcpb_path.go b/pkg/tree/api/sdcpb_path.go new file mode 100644 index 00000000..0d02cc47 --- /dev/null +++ b/pkg/tree/api/sdcpb_path.go @@ -0,0 +1,7 @@ +package api + +import "github.com/sdcio/sdc-protos/sdcpb" + +type SdcpbPath interface { + SdcpbPath() *sdcpb.Path +} diff --git a/pkg/tree/api/tree_context.go b/pkg/tree/api/tree_context.go new file mode 100644 index 00000000..d14b59fc --- /dev/null +++ b/pkg/tree/api/tree_context.go @@ -0,0 +1,14 @@ +package api + +import ( + schemaClient "github.com/sdcio/data-server/pkg/datastore/clients/schema" + "github.com/sdcio/data-server/pkg/pool" +) + +type TreeContext interface { + PoolFactory() pool.VirtualPoolFactory + SchemaClient() schemaClient.SchemaClientBound + DeepCopy() TreeContext + ExplicitDeletes() *DeletePathSet + NonRevertiveInfo() NonRevertiveInfos +} diff --git a/pkg/tree/childMap.go b/pkg/tree/childMap.go index 187b96a0..36bd4897 100644 --- a/pkg/tree/childMap.go +++ b/pkg/tree/childMap.go @@ -4,21 +4,23 @@ import ( "iter" "slices" "sync" + + "github.com/sdcio/data-server/pkg/tree/api" ) type childMap struct { - c map[string]Entry + c map[string]api.Entry mu sync.RWMutex } func newChildMap() *childMap { return &childMap{ - c: map[string]Entry{}, + c: map[string]api.Entry{}, } } -func (c *childMap) Items() iter.Seq2[string, Entry] { - return func(yield func(string, Entry) bool) { +func (c *childMap) Items() iter.Seq2[string, api.Entry] { + return func(yield func(string, api.Entry) bool) { for i, v := range c.c { if !yield(i, v) { return @@ -41,20 +43,20 @@ func (c *childMap) DeleteChild(name string) { delete(c.c, name) } -func (c *childMap) Add(e Entry) { +func (c *childMap) Add(e api.Entry) { c.mu.Lock() defer c.mu.Unlock() c.c[e.PathName()] = e } -func (c *childMap) GetEntry(s string) (e Entry, exists bool) { +func (c *childMap) GetEntry(s string) (e api.Entry, exists bool) { c.mu.RLock() defer c.mu.RUnlock() e, exists = c.c[s] return e, exists } -func (c *childMap) GetAllSorted() []Entry { +func (c *childMap) GetAllSorted() []api.Entry { c.mu.RLock() defer c.mu.RUnlock() @@ -64,7 +66,7 @@ func (c *childMap) GetAllSorted() []Entry { } slices.Sort(childNames) - result := make([]Entry, 0, len(c.c)) + result := make([]api.Entry, 0, len(c.c)) // range over children for _, childName := range childNames { result = append(result, c.c[childName]) @@ -73,11 +75,11 @@ func (c *childMap) GetAllSorted() []Entry { return result } -func (c *childMap) GetAll() map[string]Entry { +func (c *childMap) GetAll() map[string]api.Entry { c.mu.RLock() defer c.mu.RUnlock() - result := make(map[string]Entry, len(c.c)) + result := make(map[string]api.Entry, len(c.c)) for k, v := range c.c { result[k] = v } diff --git a/pkg/tree/childMap_test.go b/pkg/tree/childMap_test.go index 2ce85af1..430290ee 100644 --- a/pkg/tree/childMap_test.go +++ b/pkg/tree/childMap_test.go @@ -3,11 +3,13 @@ package tree import ( "sync" "testing" + + "github.com/sdcio/data-server/pkg/tree/api" ) func Test_childMap_DeleteChilds(t *testing.T) { type fields struct { - c map[string]Entry + c map[string]api.Entry } type args struct { names []string @@ -21,7 +23,7 @@ func Test_childMap_DeleteChilds(t *testing.T) { { name: "Delete single entry", fields: fields{ - c: map[string]Entry{ + c: map[string]api.Entry{ "one": &sharedEntryAttributes{ pathElemName: "one", }, @@ -41,7 +43,7 @@ func Test_childMap_DeleteChilds(t *testing.T) { { name: "Delete two entries", fields: fields{ - c: map[string]Entry{ + c: map[string]api.Entry{ "one": &sharedEntryAttributes{ pathElemName: "one", }, @@ -61,7 +63,7 @@ func Test_childMap_DeleteChilds(t *testing.T) { { name: "Delete non-existing entry", fields: fields{ - c: map[string]Entry{ + c: map[string]api.Entry{ "one": &sharedEntryAttributes{ pathElemName: "one", }, @@ -95,7 +97,7 @@ func Test_childMap_DeleteChilds(t *testing.T) { func Test_childMap_DeleteChild(t *testing.T) { type fields struct { - c map[string]Entry + c map[string]api.Entry } type args struct { name string @@ -109,7 +111,7 @@ func Test_childMap_DeleteChild(t *testing.T) { { name: "Delete existing entry", fields: fields{ - c: map[string]Entry{ + c: map[string]api.Entry{ "one": &sharedEntryAttributes{ pathElemName: "one", }, @@ -129,7 +131,7 @@ func Test_childMap_DeleteChild(t *testing.T) { { name: "Delete non-existing entry", fields: fields{ - c: map[string]Entry{ + c: map[string]api.Entry{ "one": &sharedEntryAttributes{ pathElemName: "one", }, diff --git a/pkg/tree/consts/consts.go b/pkg/tree/consts/consts.go new file mode 100644 index 00000000..a757eba4 --- /dev/null +++ b/pkg/tree/consts/consts.go @@ -0,0 +1,13 @@ +package consts + +import "math" + +const ( + KeysIndexSep = "_" + DefaultValuesPrio = int32(math.MaxInt32 - 90) + DefaultsIntentName = "default" + RunningValuesPrio = int32(math.MaxInt32 - 100) + RunningIntentName = "running" + ReplaceValuesPrio = int32(math.MaxInt32 - 110) + ReplaceIntentName = "replace" +) diff --git a/pkg/tree/default_value.go b/pkg/tree/default_value.go index 62276ca4..eb151a8a 100644 --- a/pkg/tree/default_value.go +++ b/pkg/tree/default_value.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/sdcio/data-server/pkg/tree/consts" "github.com/sdcio/data-server/pkg/tree/types" sdcpb "github.com/sdcio/sdc-protos/sdcpb" ) @@ -55,5 +56,5 @@ func DefaultValueRetrieve(ctx context.Context, schema *sdcpb.SchemaElem, path *s return nil, fmt.Errorf("no defaults defined for schema path: %s", path.ToXPath(false)) } - return types.NewUpdate(nil, tv, DefaultValuesPrio, DefaultsIntentName, 0), nil + return types.NewUpdate(nil, tv, consts.DefaultValuesPrio, consts.DefaultsIntentName, 0), nil } diff --git a/pkg/tree/default_value_test.go b/pkg/tree/default_value_test.go index dad64fc9..c07872da 100644 --- a/pkg/tree/default_value_test.go +++ b/pkg/tree/default_value_test.go @@ -6,6 +6,8 @@ import ( "github.com/google/go-cmp/cmp" schemaClient "github.com/sdcio/data-server/pkg/datastore/clients/schema" + + "github.com/sdcio/data-server/pkg/tree/consts" "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils/testhelper" sdcpb "github.com/sdcio/sdc-protos/sdcpb" @@ -147,7 +149,7 @@ func TestDefaultValueRetrieve(t *testing.T) { return rsp.GetSchema() }, wanterr: false, - want: types.NewUpdate(nil, &sdcpb.TypedValue{Value: &sdcpb.TypedValue_BoolVal{BoolVal: false}}, DefaultValuesPrio, DefaultsIntentName, 0), + want: types.NewUpdate(nil, &sdcpb.TypedValue{Value: &sdcpb.TypedValue_BoolVal{BoolVal: false}}, consts.DefaultValuesPrio, consts.DefaultsIntentName, 0), }, { name: "leaflist default", @@ -170,7 +172,7 @@ func TestDefaultValueRetrieve(t *testing.T) { return rsp.GetSchema() }, wanterr: false, - want: types.NewUpdate(nil, &sdcpb.TypedValue{Value: &sdcpb.TypedValue_LeaflistVal{LeaflistVal: &sdcpb.ScalarArray{Element: []*sdcpb.TypedValue{{Value: &sdcpb.TypedValue_StringVal{StringVal: "foo"}}, {Value: &sdcpb.TypedValue_StringVal{StringVal: "bar"}}}}}}, DefaultValuesPrio, DefaultsIntentName, 0), + want: types.NewUpdate(nil, &sdcpb.TypedValue{Value: &sdcpb.TypedValue_LeaflistVal{LeaflistVal: &sdcpb.ScalarArray{Element: []*sdcpb.TypedValue{{Value: &sdcpb.TypedValue_StringVal{StringVal: "foo"}}, {Value: &sdcpb.TypedValue_StringVal{StringVal: "bar"}}}}}}, consts.DefaultValuesPrio, consts.DefaultsIntentName, 0), }, } for _, tt := range tests { diff --git a/pkg/tree/entry_list.go b/pkg/tree/entry_list.go index f636975d..7701aea2 100644 --- a/pkg/tree/entry_list.go +++ b/pkg/tree/entry_list.go @@ -1,3 +1,5 @@ package tree -type EntrySlice []Entry +import "github.com/sdcio/data-server/pkg/tree/api" + +type EntrySlice []api.Entry diff --git a/pkg/tree/entry_test.go b/pkg/tree/entry_test.go index c497b2d6..858a0e44 100644 --- a/pkg/tree/entry_test.go +++ b/pkg/tree/entry_test.go @@ -11,7 +11,8 @@ import ( "github.com/google/go-cmp/cmp" "github.com/sdcio/data-server/pkg/config" "github.com/sdcio/data-server/pkg/pool" - + "github.com/sdcio/data-server/pkg/tree/api" + . "github.com/sdcio/data-server/pkg/tree/consts" "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils/testhelper" sdcpb "github.com/sdcio/sdc-protos/sdcpb" @@ -21,6 +22,7 @@ import ( var ( flagsNew *types.UpdateInsertFlags flagsExisting *types.UpdateInsertFlags + flagsDelete *types.UpdateInsertFlags validationConfig = config.NewValidationConfig() ) @@ -28,6 +30,7 @@ func init() { flagsNew = types.NewUpdateInsertFlags() flagsNew.SetNewFlag() flagsExisting = types.NewUpdateInsertFlags() + flagsDelete = types.NewUpdateInsertFlags().SetDeleteFlag() validationConfig.SetDisableConcurrency(true) } @@ -206,9 +209,9 @@ func Test_Entry_One(t *testing.T) { t.Log(root.String()) t.Run("Test 1 - expected entries for owner1", func(t *testing.T) { - o1Le := []*LeafEntry{} + o1Le := []*api.LeafEntry{} o1Le = root.GetByOwner(owner1, o1Le) - o1 := LeafEntriesToUpdates(o1Le) + o1 := api.LeafEntriesToUpdates(o1Le) // diff the result with the expected // if diff := testhelper.DiffUpdates([]*types.Update{u0o1, u2, u2_1, u1, u1_1}, o1); diff != "" { if diff := testhelper.DiffUpdates([]*types.PathAndUpdate{ @@ -223,9 +226,9 @@ func Test_Entry_One(t *testing.T) { }) t.Run("Test 2 - expected entries for owner2", func(t *testing.T) { - o2Le := []*LeafEntry{} + o2Le := []*api.LeafEntry{} o2Le = root.GetByOwner(owner2, o2Le) - o2 := LeafEntriesToUpdates(o2Le) + o2 := api.LeafEntriesToUpdates(o2Le) // diff the result with the expected if diff := testhelper.DiffUpdates([]*types.PathAndUpdate{ types.NewPathAndUpdate(p0o2, u0o2), @@ -620,9 +623,9 @@ func Test_Entry_Three(t *testing.T) { // log the tree t.Log(root.String()) - highPriLe := root.getByOwnerFiltered(owner1, FilterNonDeleted) + highPriLe := root.getByOwnerFiltered(owner1, api.FilterNonDeleted) - highPri := LeafEntriesToUpdates(highPriLe) + highPri := api.LeafEntriesToUpdates(highPriLe) // diff the result with the expected if diff := testhelper.DiffUpdates([]*types.PathAndUpdate{ @@ -894,9 +897,9 @@ func Test_Entry_Four(t *testing.T) { // log the tree t.Log(root.String()) - highPriLe := root.getByOwnerFiltered(owner1, FilterNonDeleted) + highPriLe := root.getByOwnerFiltered(owner1, api.FilterNonDeleted) - highPri := LeafEntriesToUpdates(highPriLe) + highPri := api.LeafEntriesToUpdates(highPriLe) // diff the result with the expected if diff := testhelper.DiffUpdates([]*types.PathAndUpdate{ @@ -1293,18 +1296,19 @@ func TestLeafVariants_GetHighesPrio(t *testing.T) { // because thats an update. t.Run("Delete Non New", func(t *testing.T) { - lv := newLeafVariants(&TreeContext{}, nil) + lv := api.NewLeafVariants(&TreeContext{}, nil) + + le1 := api.NewLeafEntry(types.NewUpdate(nil, nil, 2, owner1, ts), flagsExisting, nil) + lv.Add(le1) - lv.Add(NewLeafEntry(types.NewUpdate(nil, nil, 2, owner1, ts), flagsExisting, nil)) - lv.Add(NewLeafEntry(types.NewUpdate(nil, nil, 1, owner2, ts), flagsExisting, nil)) - lv.les[1].MarkDelete(false) + le2 := api.NewLeafEntry(types.NewUpdate(nil, nil, 1, owner2, ts), flagsDelete, nil) + lv.Add(le2) le := lv.GetHighestPrecedence(true, false, false) - if le != lv.les[0] { - t.Errorf("expected to get entry %v, got %v", lv.les[0], le) + if le != le1 { + t.Errorf("expected to get entry %v, got %v", le1, le) } - }, ) @@ -1312,10 +1316,8 @@ func TestLeafVariants_GetHighesPrio(t *testing.T) { // should return nil t.Run("Single entry thats also marked for deletion", func(t *testing.T) { - lv := newLeafVariants(&TreeContext{}, nil) - lv.Add(NewLeafEntry(types.NewUpdate(nil, nil, 1, owner1, ts), flagsExisting, nil)) - lv.les[0].MarkDelete(false) - + lv := api.NewLeafVariants(&TreeContext{}, nil) + lv.Add(api.NewLeafEntry(types.NewUpdate(nil, nil, 1, owner1, ts), flagsDelete, nil)) le := lv.GetHighestPrecedence(true, false, false) if le != nil { @@ -1328,10 +1330,10 @@ func TestLeafVariants_GetHighesPrio(t *testing.T) { // on onlyIfPrioChanged == true we do not expect output. t.Run("New Low Prio IsUpdate OnlyChanged True", func(t *testing.T) { - lv := newLeafVariants(&TreeContext{}, nil) - lv.Add(NewLeafEntry(types.NewUpdate(nil, nil, 5, owner1, ts), flagsExisting, nil)) - lv.Add(NewLeafEntry(types.NewUpdate(nil, nil, 6, owner2, ts), flagsNew, nil)) - lv.Add(NewLeafEntry(types.NewUpdate(nil, nil, RunningValuesPrio, RunningIntentName, ts), flagsExisting, nil)) + lv := api.NewLeafVariants(&TreeContext{}, nil) + lv.Add(api.NewLeafEntry(types.NewUpdate(nil, nil, 5, owner1, ts), flagsExisting, nil)) + lv.Add(api.NewLeafEntry(types.NewUpdate(nil, nil, 6, owner2, ts), flagsNew, nil)) + lv.Add(api.NewLeafEntry(types.NewUpdate(nil, nil, RunningValuesPrio, RunningIntentName, ts), flagsExisting, nil)) le := lv.GetHighestPrecedence(true, false, false) @@ -1345,14 +1347,16 @@ func TestLeafVariants_GetHighesPrio(t *testing.T) { // on onlyIfPrioChanged == false we do not expect the highes prio update to be returned. t.Run("New Low Prio IsUpdate OnlyChanged False", func(t *testing.T) { - lv := newLeafVariants(&TreeContext{}, nil) - lv.Add(NewLeafEntry(types.NewUpdate(nil, nil, 5, owner1, ts), flagsExisting, nil)) - lv.Add(NewLeafEntry(types.NewUpdate(nil, nil, 6, owner1, ts), flagsNew, nil)) + lv := api.NewLeafVariants(&TreeContext{}, nil) + le1 := api.NewLeafEntry(types.NewUpdate(nil, nil, 5, owner1, ts), flagsExisting, nil) + lv.Add(le1) + le2 := api.NewLeafEntry(types.NewUpdate(nil, nil, 6, owner2, ts), flagsNew, nil) + lv.Add(le2) le := lv.GetHighestPrecedence(false, false, false) - if le != lv.les[0] { - t.Errorf("expected to get entry %v, got %v", lv.les[0], le) + if le != le1 { + t.Errorf("expected to get entry %v, got %v", le1, le) } }, ) @@ -1361,11 +1365,11 @@ func TestLeafVariants_GetHighesPrio(t *testing.T) { // // on onlyIfPrioChanged == true we do not expect output. t.Run("New Low Prio IsNew OnlyChanged == True", func(t *testing.T) { - lv := newLeafVariants(&TreeContext{}, nil) + lv := api.NewLeafVariants(&TreeContext{}, nil) - lv.Add(NewLeafEntry(types.NewUpdate(nil, nil, 5, owner1, ts), flagsExisting, nil)) - lv.Add(NewLeafEntry(types.NewUpdate(nil, nil, 6, owner2, ts), flagsNew, nil)) - lv.Add(NewLeafEntry(types.NewUpdate(nil, nil, RunningValuesPrio, RunningIntentName, ts), flagsExisting, nil)) + lv.Add(api.NewLeafEntry(types.NewUpdate(nil, nil, 5, owner1, ts), flagsExisting, nil)) + lv.Add(api.NewLeafEntry(types.NewUpdate(nil, nil, 6, owner2, ts), flagsNew, nil)) + lv.Add(api.NewLeafEntry(types.NewUpdate(nil, nil, RunningValuesPrio, RunningIntentName, ts), flagsExisting, nil)) le := lv.GetHighestPrecedence(true, false, false) @@ -1379,29 +1383,32 @@ func TestLeafVariants_GetHighesPrio(t *testing.T) { // // on onlyIfPrioChanged == true we do not expect output. t.Run("New Low Prio IsNew OnlyChanged == True, with running not existing", func(t *testing.T) { - lv := newLeafVariants(&TreeContext{}, nil) - - lv.Add(NewLeafEntry(types.NewUpdate(nil, nil, 5, owner1, ts), flagsExisting, nil)) - lv.Add(NewLeafEntry(types.NewUpdate(nil, nil, 6, owner2, ts), flagsNew, nil)) + lv := api.NewLeafVariants(&TreeContext{}, nil) + le1 := api.NewLeafEntry(types.NewUpdate(nil, nil, 5, owner1, ts), flagsExisting, nil) + lv.Add(le1) + le2 := api.NewLeafEntry(types.NewUpdate(nil, nil, 6, owner2, ts), flagsNew, nil) + lv.Add(le2) le := lv.GetHighestPrecedence(true, false, false) - if le != lv.les[0] { - t.Errorf("expected to get entry %v, got %v", lv.les[0], le) + if le != le1 { + t.Errorf("expected to get entry %v, got %v", le1, le) } }, ) t.Run("New Low Prio IsNew OnlyChanged == False", func(t *testing.T) { - lv := newLeafVariants(&TreeContext{}, nil) - lv.Add(NewLeafEntry(types.NewUpdate(nil, nil, 5, owner1, ts), flagsExisting, nil)) - lv.Add(NewLeafEntry(types.NewUpdate(nil, nil, 6, owner2, ts), flagsNew, nil)) + lv := api.NewLeafVariants(&TreeContext{}, nil) + le1 := api.NewLeafEntry(types.NewUpdate(nil, nil, 5, owner1, ts), flagsExisting, nil) + lv.Add(le1) + le2 := api.NewLeafEntry(types.NewUpdate(nil, nil, 6, owner2, ts), flagsNew, nil) + lv.Add(le2) le := lv.GetHighestPrecedence(false, false, false) - if le != lv.les[0] { - t.Errorf("expected to get entry %v, got %v", lv.les[0], le) + if le != le1 { + t.Errorf("expected to get entry %v, got %v", le1, le) } }, ) @@ -1409,7 +1416,7 @@ func TestLeafVariants_GetHighesPrio(t *testing.T) { // If no entries exist in the list nil should be returned. t.Run("No Entries", func(t *testing.T) { - lv := LeafVariants{} + lv := api.NewLeafVariants(&TreeContext{}, nil) le := lv.GetHighestPrecedence(true, false, false) @@ -1422,15 +1429,14 @@ func TestLeafVariants_GetHighesPrio(t *testing.T) { // make sure the secondhighes is also populated if the highes was the first entry t.Run("secondhighes populated if highes was first", func(t *testing.T) { - lv := newLeafVariants(&TreeContext{}, nil) - lv.Add(NewLeafEntry(types.NewUpdate(nil, nil, 1, owner1, ts), flagsExisting, nil)) - lv.les[0].MarkDelete(false) - lv.Add(NewLeafEntry(types.NewUpdate(nil, nil, 2, owner2, ts), flagsExisting, nil)) - + lv := api.NewLeafVariants(&TreeContext{}, nil) + le1 := api.NewLeafEntry(types.NewUpdate(nil, nil, 1, owner1, ts), flagsDelete, nil) + lv.Add(le1) + le2 := api.NewLeafEntry(types.NewUpdate(nil, nil, 2, owner2, ts), flagsExisting, nil) + lv.Add(le2) le := lv.GetHighestPrecedence(true, false, false) - - if le != lv.les[1] { - t.Errorf("expected to get entry %v, got %v", lv.les[1], le) + if le != le2 { + t.Errorf("expected to get entry %v, got %v", le2, le) } }, ) @@ -1487,7 +1493,7 @@ func Test_Schema_Population(t *testing.T) { t.Fatal(err) } - interf, err := newSharedEntryAttributes(ctx, root.sharedEntryAttributes, "interface", tc) + interf, err := newSharedEntryAttributes(ctx, root.Entry, "interface", tc) if err != nil { t.Error(err) } @@ -1499,7 +1505,7 @@ func Test_Schema_Population(t *testing.T) { } expectNil(t, e00.schema, "/interface/ethernet-1/1 schema") - dk, err := newSharedEntryAttributes(ctx, root.sharedEntryAttributes, "doublekey", tc) + dk, err := newSharedEntryAttributes(ctx, root.Entry, "doublekey", tc) if err != nil { t.Error(err) } @@ -1542,7 +1548,7 @@ func Test_sharedEntryAttributes_SdcpbPath(t *testing.T) { t.Fatal(err) } - interf, err := newSharedEntryAttributes(ctx, root.sharedEntryAttributes, "interface", tc) + interf, err := newSharedEntryAttributes(ctx, root.Entry, "interface", tc) if err != nil { t.Error(err) } @@ -1557,7 +1563,7 @@ func Test_sharedEntryAttributes_SdcpbPath(t *testing.T) { t.Error(err) } - dk, err := newSharedEntryAttributes(ctx, root.sharedEntryAttributes, "doublekey", tc) + dk, err := newSharedEntryAttributes(ctx, root.Entry, "doublekey", tc) if err != nil { t.Error(err) } diff --git a/pkg/tree/json.go b/pkg/tree/json.go index 151f2f24..baae11c6 100644 --- a/pkg/tree/json.go +++ b/pkg/tree/json.go @@ -4,13 +4,14 @@ import ( "fmt" "slices" + "github.com/sdcio/data-server/pkg/tree/api" "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils" sdcpb "github.com/sdcio/sdc-protos/sdcpb" ) func (s *sharedEntryAttributes) ToJson(onlyNewOrUpdated bool) (any, error) { - result, err := s.toJsonInternal(onlyNewOrUpdated, false) + result, err := s.ToJsonInternal(onlyNewOrUpdated, false) if err != nil { return nil, err } @@ -21,7 +22,7 @@ func (s *sharedEntryAttributes) ToJson(onlyNewOrUpdated bool) (any, error) { } func (s *sharedEntryAttributes) ToJsonIETF(onlyNewOrUpdated bool) (any, error) { - result, err := s.toJsonInternal(onlyNewOrUpdated, true) + result, err := s.ToJsonInternal(onlyNewOrUpdated, true) if err != nil { return nil, err } @@ -35,7 +36,7 @@ func (s *sharedEntryAttributes) ToJsonIETF(onlyNewOrUpdated bool) (any, error) { // If the ietf parameter is set to true, JSON_IETF encoding is used. // The actualPrefix is used only for the JSON_IETF encoding and can be ignored for JSON // In the initial / users call with ietf == true, actualPrefix should be set to "" -func (s *sharedEntryAttributes) toJsonInternal(onlyNewOrUpdated bool, ietf bool) (any, error) { +func (s *sharedEntryAttributes) ToJsonInternal(onlyNewOrUpdated bool, ietf bool) (any, error) { switch s.schema.GetSchema().(type) { case nil: // we're operating on a key level, no schema attached, but the @@ -46,7 +47,7 @@ func (s *sharedEntryAttributes) toJsonInternal(onlyNewOrUpdated bool, ietf bool) ancest, _ := s.GetFirstAncestorWithSchema() prefixedKey := jsonGetIetfPrefixConditional(key, c, ancest, ietf) // recurse the call - js, err := c.toJsonInternal(onlyNewOrUpdated, ietf) + js, err := c.ToJsonInternal(onlyNewOrUpdated, ietf) if err != nil { return nil, err } @@ -74,7 +75,7 @@ func (s *sharedEntryAttributes) toJsonInternal(onlyNewOrUpdated bool, ietf bool) result := make([]any, 0, len(childs)) for _, c := range childs { - j, err := c.toJsonInternal(onlyNewOrUpdated, ietf) + j, err := c.ToJsonInternal(onlyNewOrUpdated, ietf) if err != nil { return nil, err } @@ -90,7 +91,7 @@ func (s *sharedEntryAttributes) toJsonInternal(onlyNewOrUpdated bool, ietf bool) // Presence container without any childs if onlyNewOrUpdated { // presence containers have leafvariantes with typedValue_Empty, so check that - if s.leafVariants.shouldDelete() { + if s.leafVariants.ShouldDelete() { return nil, nil } le := s.leafVariants.GetHighestPrecedence(false, false, false) @@ -104,7 +105,7 @@ func (s *sharedEntryAttributes) toJsonInternal(onlyNewOrUpdated bool, ietf bool) result := map[string]any{} for key, c := range s.GetChilds(types.DescendMethodActiveChilds) { prefixedKey := jsonGetIetfPrefixConditional(key, c, s, ietf) - js, err := c.toJsonInternal(onlyNewOrUpdated, ietf) + js, err := c.ToJsonInternal(onlyNewOrUpdated, ietf) if err != nil { return nil, err } @@ -119,7 +120,7 @@ func (s *sharedEntryAttributes) toJsonInternal(onlyNewOrUpdated bool, ietf bool) } case *sdcpb.SchemaElem_Leaflist, *sdcpb.SchemaElem_Field: - if s.leafVariants.canDelete() { + if s.leafVariants.CanDelete() { return nil, nil } le := s.leafVariants.GetHighestPrecedence(onlyNewOrUpdated, false, false) @@ -132,7 +133,7 @@ func (s *sharedEntryAttributes) toJsonInternal(onlyNewOrUpdated bool, ietf bool) } // jsonAddIetfPrefixConditional adds the module name -func jsonGetIetfPrefixConditional(key string, a Entry, b Entry, ietf bool) string { +func jsonGetIetfPrefixConditional(key string, a api.Entry, b api.Entry, ietf bool) string { // if not ietf, then we do not need module prefixes if !ietf { return key @@ -146,13 +147,13 @@ func jsonGetIetfPrefixConditional(key string, a Entry, b Entry, ietf bool) strin // xmlAddKeyElements determines the keys of a certain Entry in the tree and adds those to the // element if they do not already exist. -func jsonAddKeyElements(s Entry, dict map[string]any) { +func jsonAddKeyElements(s api.Entry, dict map[string]any) { // retrieve the parent schema, we need to extract the key names // values are the tree level names parentSchema, levelsUp := s.GetFirstAncestorWithSchema() // from the parent we get the keys as slice schemaKeys := parentSchema.GetSchemaKeys() - var treeElem Entry = s + var treeElem api.Entry = s // the keys do match the levels up in the tree in reverse order // hence we init i with levelUp and count down for i := levelsUp - 1; i >= 0; i-- { diff --git a/pkg/tree/json_test.go b/pkg/tree/json_test.go index 007ccab6..289731eb 100644 --- a/pkg/tree/json_test.go +++ b/pkg/tree/json_test.go @@ -10,6 +10,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/openconfig/ygot/ygot" "github.com/sdcio/data-server/pkg/pool" + "github.com/sdcio/data-server/pkg/tree/consts" "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils" "github.com/sdcio/data-server/pkg/utils/testhelper" @@ -393,7 +394,7 @@ func TestToJsonTable(t *testing.T) { t.Error(err) } - err = addToRoot(ctx, root, updsRunning, flagsOld, RunningIntentName, RunningValuesPrio) + err = addToRoot(ctx, root, updsRunning, flagsOld, consts.RunningIntentName, consts.RunningValuesPrio) if err != nil { t.Fatal(err) } diff --git a/pkg/tree/ops/get_by_owner/get_by_owner.go b/pkg/tree/ops/get_by_owner/get_by_owner.go new file mode 100644 index 00000000..178f64f1 --- /dev/null +++ b/pkg/tree/ops/get_by_owner/get_by_owner.go @@ -0,0 +1,22 @@ +package getbyowner + +import "github.com/sdcio/data-server/pkg/tree/api" + +// GetByOwner returns all the LeafEntries that belong to a certain owner. +func GetByOwner(e Entry, owner string, result []*api.LeafEntry) api.LeafVariantSlice { + lv := e.GetLeafVariants().GetByOwner(owner) + if lv != nil { + result = append(result, lv) + } + + // continue with childs + for _, c := range e.GetChilds() { + result = GetByOwner(c, owner, result) + } + return result +} + +type Entry interface { + GetLeafVariants() *api.LeafVariants + GetChilds() []Entry +} diff --git a/pkg/tree/processor_blame_config.go b/pkg/tree/processor_blame_config.go index 603b4ca7..80a76f18 100644 --- a/pkg/tree/processor_blame_config.go +++ b/pkg/tree/processor_blame_config.go @@ -5,6 +5,8 @@ import ( "errors" "github.com/sdcio/data-server/pkg/pool" + "github.com/sdcio/data-server/pkg/tree/api" + "github.com/sdcio/data-server/pkg/tree/consts" "github.com/sdcio/data-server/pkg/tree/types" sdcpb "github.com/sdcio/sdc-protos/sdcpb" "google.golang.org/protobuf/proto" @@ -34,7 +36,7 @@ func NewBlameConfigProcessorConfig(includeDefaults bool) *BlameConfigProcessorCo // (intent) is responsible for each configuration value. The pool parameter should be // VirtualFailFast to stop on first error. // Returns the blame tree structure and any error encountered. -func (p *BlameConfigProcessor) Run(ctx context.Context, e Entry, pool pool.VirtualPoolI) (*sdcpb.BlameTreeElement, error) { +func (p *BlameConfigProcessor) Run(ctx context.Context, e api.Entry, pool pool.VirtualPoolI) (*sdcpb.BlameTreeElement, error) { blameTask := NewBlameConfigTask(e, p.config) if err := pool.Submit(blameTask); err != nil { @@ -57,10 +59,10 @@ type BlameConfigTask struct { config *BlameConfigProcessorConfig parent *sdcpb.BlameTreeElement self *sdcpb.BlameTreeElement - selfEntry Entry + selfEntry api.Entry } -func NewBlameConfigTask(e Entry, c *BlameConfigProcessorConfig) *BlameConfigTask { +func NewBlameConfigTask(e api.Entry, c *BlameConfigProcessorConfig) *BlameConfigTask { return &BlameConfigTask{ config: c, parent: nil, @@ -80,13 +82,13 @@ func (t *BlameConfigTask) Run(ctx context.Context, submit func(pool.Task) error) } // process Value - highestLe := t.selfEntry.GetLeafVariantEntries().GetHighestPrecedence(false, true, true) + highestLe := t.selfEntry.GetLeafVariants().GetHighestPrecedence(false, true, true) if highestLe != nil { - if highestLe.Update.Owner() != DefaultsIntentName || t.config.includeDefaults { + if highestLe.Update.Owner() != consts.DefaultsIntentName || t.config.includeDefaults { t.self.SetValue(highestLe.Update.Value()).SetOwner(highestLe.Update.Owner()) // check if running equals the expected - runningLe := t.selfEntry.GetLeafVariantEntries().GetRunning() + runningLe := t.selfEntry.GetLeafVariants().GetRunning() if runningLe != nil { if !proto.Equal(runningLe.Update.Value(), highestLe.Update.Value()) { t.self.SetDeviationValue(runningLe.Value()) @@ -98,9 +100,9 @@ func (t *BlameConfigTask) Run(ctx context.Context, submit func(pool.Task) error) childs := t.selfEntry.GetChilds(types.DescendMethodActiveChilds) for _, childKey := range childs.SortedKeys() { childEntry := childs[childKey] - childHighestLe := childEntry.GetLeafVariantEntries().GetHighestPrecedence(false, true, true) + childHighestLe := childEntry.GetLeafVariants().GetHighestPrecedence(false, true, true) if childHighestLe != nil { - if childHighestLe.Update.Owner() == DefaultsIntentName && !t.config.includeDefaults { + if childHighestLe.Update.Owner() == consts.DefaultsIntentName && !t.config.includeDefaults { continue } } diff --git a/pkg/tree/processor_blame_config_test.go b/pkg/tree/processor_blame_config_test.go index 75f2aee2..09c477cd 100644 --- a/pkg/tree/processor_blame_config_test.go +++ b/pkg/tree/processor_blame_config_test.go @@ -8,6 +8,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/openconfig/ygot/ygot" "github.com/sdcio/data-server/pkg/pool" + "github.com/sdcio/data-server/pkg/tree/consts" "github.com/sdcio/data-server/pkg/utils/testhelper" sdcio_schema "github.com/sdcio/data-server/tests/sdcioygot" sdcpb "github.com/sdcio/sdc-protos/sdcpb" @@ -132,7 +133,7 @@ func Test_sharedEntryAttributes_BlameConfig(t *testing.T) { running.Patterntest = ygot.String("hallo 0") - _, err = loadYgotStructIntoTreeRoot(ctx, running, root, RunningIntentName, RunningValuesPrio, false, flagsExisting) + _, err = loadYgotStructIntoTreeRoot(ctx, running, root, consts.RunningIntentName, consts.RunningValuesPrio, false, flagsExisting) if err != nil { t.Fatal(err) } @@ -156,7 +157,7 @@ func Test_sharedEntryAttributes_BlameConfig(t *testing.T) { vPool := sharedPool.NewVirtualPool(pool.VirtualFailFast) bp := NewBlameConfigProcessor(NewBlameConfigProcessorConfig(tt.includeDefaults)) - got, err := bp.Run(ctx, treeRoot.sharedEntryAttributes, vPool) + got, err := bp.Run(ctx, treeRoot.Entry, vPool) if err != nil { t.Errorf("BlameConfig() error %s", err) return diff --git a/pkg/tree/processor_explicit_delete.go b/pkg/tree/processor_explicit_delete.go index afc601a5..4a002cfe 100644 --- a/pkg/tree/processor_explicit_delete.go +++ b/pkg/tree/processor_explicit_delete.go @@ -5,6 +5,7 @@ import ( "sync" "github.com/sdcio/data-server/pkg/pool" + "github.com/sdcio/data-server/pkg/tree/api" "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils" ) @@ -25,14 +26,14 @@ func (edp *ExplicitDeleteProcessor) GetExplicitDeleteCreationCount() int { } // GetCreatedExplicitDeleteLeafEntries returns all the explicitDelete LeafVariants that where created. -func (edp *ExplicitDeleteProcessor) GetCreatedExplicitDeleteLeafEntries() LeafVariantSlice { +func (edp *ExplicitDeleteProcessor) GetCreatedExplicitDeleteLeafEntries() api.LeafVariantSlice { return edp.params.relatedLeafVariants } type ExplicitDeleteTaskParameters struct { owner string priority int32 - relatedLeafVariants []*LeafEntry + relatedLeafVariants api.LeafVariantSlice rlvMutex *sync.Mutex } @@ -40,12 +41,12 @@ func NewExplicitDeleteTaskParameters(owner string, priority int32) *ExplicitDele return &ExplicitDeleteTaskParameters{ priority: priority, owner: owner, - relatedLeafVariants: []*LeafEntry{}, + relatedLeafVariants: api.LeafVariantSlice{}, rlvMutex: &sync.Mutex{}, } } -func (p *ExplicitDeleteProcessor) Run(ctx context.Context, e Entry, poolFactory pool.VirtualPoolFactory) error { +func (p *ExplicitDeleteProcessor) Run(ctx context.Context, e api.Entry, poolFactory pool.VirtualPoolFactory) error { taskpool := poolFactory.NewVirtualPool(pool.VirtualTolerant) err := taskpool.Submit(newExplicitDeleteTask(e, p.params)) taskpool.CloseAndWait() @@ -53,11 +54,11 @@ func (p *ExplicitDeleteProcessor) Run(ctx context.Context, e Entry, poolFactory } type explicitDeleteTask struct { - entry Entry + entry api.Entry params *ExplicitDeleteTaskParameters } -func newExplicitDeleteTask(entry Entry, params *ExplicitDeleteTaskParameters) *explicitDeleteTask { +func newExplicitDeleteTask(entry api.Entry, params *ExplicitDeleteTaskParameters) *explicitDeleteTask { return &explicitDeleteTask{ entry: entry, params: params, @@ -66,11 +67,11 @@ func newExplicitDeleteTask(entry Entry, params *ExplicitDeleteTaskParameters) *e func (t *explicitDeleteTask) Run(ctx context.Context, submit func(pool.Task) error) error { if t.entry.HoldsLeafvariants() { - le := t.entry.GetLeafVariantEntries().GetByOwner(t.params.owner) + le := t.entry.GetLeafVariants().GetByOwner(t.params.owner) if le != nil { le.MarkExpliciteDelete() } else { - le = t.entry.GetLeafVariantEntries().AddExplicitDeleteEntry(t.params.owner, t.params.priority) + le = t.entry.GetLeafVariants().AddExplicitDeleteEntry(t.params.owner, t.params.priority) } t.params.rlvMutex.Lock() t.params.relatedLeafVariants = append(t.params.relatedLeafVariants, le) @@ -90,7 +91,7 @@ func (t *explicitDeleteTask) Run(ctx context.Context, submit func(pool.Task) err // Stats structs type ExplicitDeleteProcessorStat interface { - GetCreatedExplicitDeleteLeafEntries() LeafVariantSlice + GetCreatedExplicitDeleteLeafEntries() api.LeafVariantSlice GetExplicitDeleteCreationCount() int } diff --git a/pkg/tree/processor_explicit_delete_test.go b/pkg/tree/processor_explicit_delete_test.go index 2b3939a8..12e3f17d 100644 --- a/pkg/tree/processor_explicit_delete_test.go +++ b/pkg/tree/processor_explicit_delete_test.go @@ -9,6 +9,8 @@ import ( "github.com/openconfig/ygot/ygot" schemaClient "github.com/sdcio/data-server/pkg/datastore/clients/schema" "github.com/sdcio/data-server/pkg/pool" + "github.com/sdcio/data-server/pkg/tree/api" + "github.com/sdcio/data-server/pkg/tree/consts" "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils/testhelper" sdcio_schema "github.com/sdcio/data-server/tests/sdcioygot" @@ -32,7 +34,7 @@ func TestExplicitDeleteVisitor_Visit(t *testing.T) { priority int32 explicitDeletes *sdcpb.PathSet wantErr bool - expectedLeafVariants LeafVariantSlice + expectedLeafVariants api.LeafVariantSlice }{ { name: "No Deletes", @@ -88,7 +90,7 @@ func TestExplicitDeleteVisitor_Visit(t *testing.T) { t.Error(err) } - _, err = loadYgotStructIntoTreeRoot(ctx, config1(), root, RunningIntentName, RunningValuesPrio, false, flagsExisting) + _, err = loadYgotStructIntoTreeRoot(ctx, config1(), root, consts.RunningIntentName, consts.RunningValuesPrio, false, flagsExisting) if err != nil { t.Error(err) } @@ -104,8 +106,8 @@ func TestExplicitDeleteVisitor_Visit(t *testing.T) { }, }, ), - expectedLeafVariants: LeafVariantSlice{ - NewLeafEntry(types.NewUpdate(testhelper.NewUpdateParentMock(&sdcpb.Path{ + expectedLeafVariants: api.LeafVariantSlice{ + api.NewLeafEntry(types.NewUpdate(testhelper.NewUpdateParentMock(&sdcpb.Path{ IsRootBased: true, Elem: []*sdcpb.PathElem{ sdcpb.NewPathElem("interface", map[string]string{"name": "ethernet-1/1"}), @@ -140,7 +142,7 @@ func TestExplicitDeleteVisitor_Visit(t *testing.T) { t.Error(err) } - _, err = loadYgotStructIntoTreeRoot(ctx, config1(), root, RunningIntentName, RunningValuesPrio, false, flagsExisting) + _, err = loadYgotStructIntoTreeRoot(ctx, config1(), root, consts.RunningIntentName, consts.RunningValuesPrio, false, flagsExisting) if err != nil { t.Error(err) } @@ -162,8 +164,8 @@ func TestExplicitDeleteVisitor_Visit(t *testing.T) { }, }, ), - expectedLeafVariants: LeafVariantSlice{ - NewLeafEntry( + expectedLeafVariants: api.LeafVariantSlice{ + api.NewLeafEntry( types.NewUpdate( testhelper.NewUpdateParentMock(&sdcpb.Path{ IsRootBased: true, @@ -178,7 +180,7 @@ func TestExplicitDeleteVisitor_Visit(t *testing.T) { types.NewUpdateInsertFlags().SetExplicitDeleteFlag(), nil, ), - NewLeafEntry( + api.NewLeafEntry( types.NewUpdate( testhelper.NewUpdateParentMock(&sdcpb.Path{ IsRootBased: true, @@ -193,7 +195,7 @@ func TestExplicitDeleteVisitor_Visit(t *testing.T) { explicitDeleteFlag, nil, ), - NewLeafEntry( + api.NewLeafEntry( types.NewUpdate( testhelper.NewUpdateParentMock(&sdcpb.Path{ IsRootBased: true, @@ -208,7 +210,7 @@ func TestExplicitDeleteVisitor_Visit(t *testing.T) { explicitDeleteFlag, nil, ), - NewLeafEntry( + api.NewLeafEntry( types.NewUpdate( testhelper.NewUpdateParentMock(&sdcpb.Path{ IsRootBased: true, @@ -224,7 +226,7 @@ func TestExplicitDeleteVisitor_Visit(t *testing.T) { explicitDeleteFlag, nil, ), - NewLeafEntry( + api.NewLeafEntry( types.NewUpdate( testhelper.NewUpdateParentMock(&sdcpb.Path{ IsRootBased: true, @@ -240,7 +242,7 @@ func TestExplicitDeleteVisitor_Visit(t *testing.T) { explicitDeleteFlag, nil, ), - NewLeafEntry( + api.NewLeafEntry( types.NewUpdate( testhelper.NewUpdateParentMock(&sdcpb.Path{ IsRootBased: true, @@ -256,7 +258,7 @@ func TestExplicitDeleteVisitor_Visit(t *testing.T) { explicitDeleteFlag, nil, ), - NewLeafEntry( + api.NewLeafEntry( types.NewUpdate( testhelper.NewUpdateParentMock(&sdcpb.Path{ IsRootBased: true, @@ -278,7 +280,7 @@ func TestExplicitDeleteVisitor_Visit(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { root := tt.root() - root.GetTreeContext().AddExplicitDeletes(owner2, tt.priority, tt.explicitDeletes) + root.GetTreeContext().ExplicitDeletes().Add(owner2, tt.priority, tt.explicitDeletes) err := root.FinishInsertionPhase(ctx) if err != nil { @@ -287,7 +289,7 @@ func TestExplicitDeleteVisitor_Visit(t *testing.T) { t.Log(root.String()) - lvs := LeafVariantSlice{} + lvs := api.LeafVariantSlice{} lvs = root.GetByOwner(owner2, lvs) equal, err := lvs.Equal(tt.expectedLeafVariants) diff --git a/pkg/tree/processor_importer.go b/pkg/tree/processor_importer.go index e2cc8ac1..34a6c2fa 100644 --- a/pkg/tree/processor_importer.go +++ b/pkg/tree/processor_importer.go @@ -8,6 +8,7 @@ import ( "github.com/sdcio/data-server/pkg/pool" + "github.com/sdcio/data-server/pkg/tree/api" treeimporter "github.com/sdcio/data-server/pkg/tree/importer" "github.com/sdcio/data-server/pkg/tree/types" sdcpb "github.com/sdcio/sdc-protos/sdcpb" @@ -15,7 +16,7 @@ import ( ) type importConfigTask struct { - entry Entry + entry api.Entry importerElement treeimporter.ImportConfigAdapterElement params *ImportConfigProcessorParams } @@ -24,7 +25,7 @@ type ImportConfigProcessorParams struct { intentName string intentPrio int32 insertFlags *types.UpdateInsertFlags - treeContext *TreeContext + treeContext api.TreeContext leafListTracker *sync.Map stats *types.ImportStats } @@ -33,7 +34,7 @@ func NewParameters( intentName string, intentPrio int32, insertFlags *types.UpdateInsertFlags, - treeContext *TreeContext, + treeContext api.TreeContext, leafListLock *sync.Map, stats *types.ImportStats, ) *ImportConfigProcessorParams { @@ -65,12 +66,12 @@ func (p *ImportConfigProcessor) GetStats() *types.ImportStats { return p.stats } -func (p *ImportConfigProcessor) Run(ctx context.Context, e Entry, poolFactory pool.VirtualPoolFactory) error { +func (p *ImportConfigProcessor) Run(ctx context.Context, e api.Entry, poolFactory pool.VirtualPoolFactory) error { // store non revertive info - e.GetTreeContext().nonRevertiveInfo[p.importer.GetName()] = p.importer.GetNonRevertive() + e.GetTreeContext().NonRevertiveInfo().Add(p.importer.GetName(), p.importer.GetNonRevertive()) // store explicit deletes - e.GetTreeContext().explicitDeletes.Add(p.importer.GetName(), p.importer.GetPriority(), p.importer.GetDeletes()) + e.GetTreeContext().ExplicitDeletes().Add(p.importer.GetName(), p.importer.GetPriority(), p.importer.GetDeletes()) workerPool := poolFactory.NewVirtualPool(pool.VirtualFailFast) @@ -103,8 +104,8 @@ func (task importConfigTask) Run(ctx context.Context, submit func(pool.Task) err // keyed container: handle keys sequentially if len(task.entry.GetSchema().GetContainer().GetKeys()) > 0 { var exists bool - var actual Entry = task.entry - var keyChild Entry + var actual api.Entry = task.entry + var keyChild api.Entry keys := task.entry.GetSchemaKeys() slices.Sort(keys) @@ -137,7 +138,7 @@ func (task importConfigTask) Run(ctx context.Context, submit func(pool.Task) err if schem != nil && schem.IsPresence { tv := &sdcpb.TypedValue{Value: &sdcpb.TypedValue_EmptyVal{EmptyVal: &emptypb.Empty{}}} upd := types.NewUpdate(task.entry, tv, task.params.intentPrio, task.params.intentName, 0) - task.entry.GetLeafVariantEntries().Add(NewLeafEntry(upd, task.params.insertFlags, task.entry)) + task.entry.GetLeafVariants().Add(api.NewLeafEntry(upd, task.params.insertFlags, task.entry)) } return nil } @@ -173,7 +174,7 @@ func (task importConfigTask) Run(ctx context.Context, submit func(pool.Task) err return err } upd := types.NewUpdate(task.entry, tv, task.params.intentPrio, task.params.intentName, 0) - task.entry.GetLeafVariantEntries().AddWithStats(NewLeafEntry(upd, task.params.insertFlags, task.entry), task.params.stats) + task.entry.GetLeafVariants().AddWithStats(api.NewLeafEntry(upd, task.params.insertFlags, task.entry), task.params.stats) return nil case *sdcpb.SchemaElem_Leaflist: @@ -184,7 +185,7 @@ func (task importConfigTask) Run(ctx context.Context, submit func(pool.Task) err // create a unique key for the leaflist based on the parent entry and the leaflist name key := struct { - parent Entry + parent api.Entry name string }{task.entry.GetParent(), task.importerElement.GetName()} @@ -192,15 +193,15 @@ func (task importConfigTask) Run(ctx context.Context, submit func(pool.Task) err var scalarArr *sdcpb.ScalarArray mustAdd := false - var le *LeafEntry + var le *api.LeafEntry if loaded { // if loaded is true, it means that another goroutine already did the first insertion and reset, // so we just need to get the leaf list and add to it - le = task.entry.GetLeafVariantEntries().GetByOwner(task.params.intentName) + le = task.entry.GetLeafVariants().GetByOwner(task.params.intentName) scalarArr = le.Value().GetLeaflistVal() } else { // reset / create the leaf list on the first insertion - le = NewLeafEntry(nil, task.params.insertFlags, task.entry) + le = api.NewLeafEntry(nil, task.params.insertFlags, task.entry) mustAdd = true scalarArr = &sdcpb.ScalarArray{Element: []*sdcpb.TypedValue{}} } @@ -215,7 +216,7 @@ func (task importConfigTask) Run(ctx context.Context, submit func(pool.Task) err } le.Update = types.NewUpdate(task.entry, tv, task.params.intentPrio, task.params.intentName, 0) if mustAdd { - task.entry.GetLeafVariantEntries().Add(le) + task.entry.GetLeafVariants().Add(le) } return nil default: diff --git a/pkg/tree/processor_mark_owner_delete.go b/pkg/tree/processor_mark_owner_delete.go index ffe93abf..655a362d 100644 --- a/pkg/tree/processor_mark_owner_delete.go +++ b/pkg/tree/processor_mark_owner_delete.go @@ -6,25 +6,26 @@ import ( "sync" "github.com/sdcio/data-server/pkg/pool" + "github.com/sdcio/data-server/pkg/tree/api" "github.com/sdcio/data-server/pkg/tree/types" ) type MarkOwnerDeleteProcessor struct { config *OwnerDeleteMarkerTaskConfig - matches *Collector[*LeafEntry] + matches *Collector[*api.LeafEntry] } func NewOwnerDeleteMarker(c *OwnerDeleteMarkerTaskConfig) *MarkOwnerDeleteProcessor { return &MarkOwnerDeleteProcessor{ config: c, - matches: NewCollector[*LeafEntry](20), + matches: NewCollector[*api.LeafEntry](20), } } // Run processes the entry tree starting from e, marking leaf variant entries for deletion // by the specified owner. The pool parameter should be VirtualFailFast to stop on first error. // Returns the first error encountered, or nil if successful. -func (p *MarkOwnerDeleteProcessor) Run(e Entry, poolFactory pool.VirtualPoolFactory) error { +func (p *MarkOwnerDeleteProcessor) Run(e api.Entry, poolFactory pool.VirtualPoolFactory) error { pool := poolFactory.NewVirtualPool(pool.VirtualFailFast) if err := pool.Submit(newOwnerDeleteMarkerTask(p.config, e, p.matches)); err != nil { // Clean up pool even on early error @@ -56,11 +57,11 @@ func NewOwnerDeleteMarkerTaskConfig(owner string, onlyIntended bool) *OwnerDelet type ownerDeleteMarkerTask struct { config *OwnerDeleteMarkerTaskConfig - matches *Collector[*LeafEntry] - e Entry + matches *Collector[*api.LeafEntry] + e api.Entry } -func newOwnerDeleteMarkerTask(c *OwnerDeleteMarkerTaskConfig, e Entry, matches *Collector[*LeafEntry]) *ownerDeleteMarkerTask { +func newOwnerDeleteMarkerTask(c *OwnerDeleteMarkerTaskConfig, e api.Entry, matches *Collector[*api.LeafEntry]) *ownerDeleteMarkerTask { return &ownerDeleteMarkerTask{ config: c, e: e, @@ -72,7 +73,7 @@ func (x ownerDeleteMarkerTask) Run(ctx context.Context, submit func(pool.Task) e if ctx.Err() != nil { return ctx.Err() } - le := x.e.GetLeafVariantEntries().MarkOwnerForDeletion(x.config.owner, x.config.onlyIntended) + le := x.e.GetLeafVariants().MarkOwnerForDeletion(x.config.owner, x.config.onlyIntended) if le != nil { x.matches.Append(le) } diff --git a/pkg/tree/processor_remove_deleted.go b/pkg/tree/processor_remove_deleted.go index 0827b31c..09556a20 100644 --- a/pkg/tree/processor_remove_deleted.go +++ b/pkg/tree/processor_remove_deleted.go @@ -7,6 +7,7 @@ import ( "sync/atomic" "github.com/sdcio/data-server/pkg/pool" + "github.com/sdcio/data-server/pkg/tree/api" "github.com/sdcio/data-server/pkg/tree/types" ) @@ -23,7 +24,7 @@ func NewRemoveDeletedProcessor(c *RemoveDeletedProcessorParameters) *RemoveDelet type RemoveDeletedProcessorParameters struct { owner string deleteStatsCount atomic.Int64 - zeroLeafEntryElements []Entry + zeroLeafEntryElements []api.Entry zeroLeafEntryElementsLock sync.Mutex } @@ -31,7 +32,7 @@ func NewRemoveDeletedProcessorParameters(owner string) *RemoveDeletedProcessorPa return &RemoveDeletedProcessorParameters{ owner: owner, deleteStatsCount: atomic.Int64{}, - zeroLeafEntryElements: []Entry{}, + zeroLeafEntryElements: []api.Entry{}, zeroLeafEntryElementsLock: sync.Mutex{}, } } @@ -41,7 +42,7 @@ func (r *RemoveDeletedProcessorParameters) GetDeleteStatsCount() int64 { } // GetZeroLengthLeafVariantEntries returns the entries that have zero-length leaf variant entries after removal -func (r *RemoveDeletedProcessorParameters) GetZeroLengthLeafVariantEntries() []Entry { +func (r *RemoveDeletedProcessorParameters) GetZeroLengthLeafVariantEntries() []api.Entry { r.zeroLeafEntryElementsLock.Lock() defer r.zeroLeafEntryElementsLock.Unlock() return r.zeroLeafEntryElements @@ -51,7 +52,7 @@ func (r *RemoveDeletedProcessorParameters) GetZeroLengthLeafVariantEntries() []E // for deletion by the specified owner. The pool parameter should be VirtualFailFast // to stop on first error. // Returns the first error encountered, or nil if successful. -func (p *RemoveDeletedProcessor) Run(e Entry, poolFactory pool.VirtualPoolFactory) error { +func (p *RemoveDeletedProcessor) Run(e api.Entry, poolFactory pool.VirtualPoolFactory) error { // create a virtual task pool for removeDeleted operations pool := poolFactory.NewVirtualPool(pool.VirtualFailFast) @@ -71,11 +72,11 @@ func (p *RemoveDeletedProcessor) Run(e Entry, poolFactory pool.VirtualPoolFactor type removeDeletedTask struct { config *RemoveDeletedProcessorParameters - e Entry + e api.Entry keepDefaults bool } -func newRemoveDeletedTask(c *RemoveDeletedProcessorParameters, e Entry, keepDefaults bool) *removeDeletedTask { +func newRemoveDeletedTask(c *RemoveDeletedProcessorParameters, e api.Entry, keepDefaults bool) *removeDeletedTask { return &removeDeletedTask{ config: c, e: e, @@ -88,7 +89,7 @@ func (t *removeDeletedTask) Run(ctx context.Context, submit func(pool.Task) erro return ctx.Err() } - res := t.e.GetLeafVariantEntries().RemoveDeletedByOwner(t.config.owner) + res := t.e.GetLeafVariants().RemoveDeletedByOwner(t.config.owner) if res != nil { // increment the delete stats count t.config.deleteStatsCount.Add(1) diff --git a/pkg/tree/processor_reset_flags.go b/pkg/tree/processor_reset_flags.go index 623fb410..b38bd442 100644 --- a/pkg/tree/processor_reset_flags.go +++ b/pkg/tree/processor_reset_flags.go @@ -7,6 +7,7 @@ import ( "sync/atomic" "github.com/sdcio/data-server/pkg/pool" + "github.com/sdcio/data-server/pkg/tree/api" "github.com/sdcio/data-server/pkg/tree/types" ) @@ -56,7 +57,7 @@ func (r *ResetFlagsProcessorParameters) GetAdjustedFlagsCount() int64 { // according to the processor configuration. The pool parameter can be either VirtualFailFast // (stops on first error) or VirtualTolerant (collects all errors). // Returns the first error for fail-fast pools, or a combined error for tolerant pools. -func (p *ResetFlagsProcessor) Run(e Entry, poolFactory pool.VirtualPoolFactory) error { +func (p *ResetFlagsProcessor) Run(e api.Entry, poolFactory pool.VirtualPoolFactory) error { if e == nil { return fmt.Errorf("entry cannot be nil") } @@ -80,10 +81,10 @@ func (p *ResetFlagsProcessor) Run(e Entry, poolFactory pool.VirtualPoolFactory) type resetFlagsTask struct { config *ResetFlagsProcessorParameters - e Entry + e api.Entry } -func newResetFlagsTask(config *ResetFlagsProcessorParameters, e Entry) *resetFlagsTask { +func newResetFlagsTask(config *ResetFlagsProcessorParameters, e api.Entry) *resetFlagsTask { return &resetFlagsTask{ config: config, e: e, @@ -92,7 +93,7 @@ func newResetFlagsTask(config *ResetFlagsProcessorParameters, e Entry) *resetFla func (t *resetFlagsTask) Run(ctx context.Context, submit func(pool.Task) error) error { // Reset flags as per config - count := t.e.GetLeafVariantEntries().ResetFlags(t.config.deleteFlag, t.config.newFlag, t.config.updateFlag) + count := t.e.GetLeafVariants().ResetFlags(t.config.deleteFlag, t.config.newFlag, t.config.updateFlag) t.config.adjustedFlagsCount.Add(int64(count)) // Process children recursively diff --git a/pkg/tree/processor_reset_flags_test.go b/pkg/tree/processor_reset_flags_test.go index 424f4b17..0619ae83 100644 --- a/pkg/tree/processor_reset_flags_test.go +++ b/pkg/tree/processor_reset_flags_test.go @@ -8,6 +8,7 @@ import ( schemaClient "github.com/sdcio/data-server/pkg/datastore/clients/schema" "github.com/sdcio/data-server/pkg/pool" + . "github.com/sdcio/data-server/pkg/tree/consts" "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils/testhelper" sdcpb "github.com/sdcio/sdc-protos/sdcpb" @@ -42,7 +43,7 @@ func TestResetFlagsProcessorRun(t *testing.T) { t.Fatal(err) } scb := schemaClient.NewSchemaClientBound(schema, sc) - tc := NewTreeContext(scb, pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0))) + tc := NewTreeContext(scb, pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0))) root, err := NewTreeRoot(ctx, tc) if err != nil { diff --git a/pkg/tree/processor_validate.go b/pkg/tree/processor_validate.go index 314d3d91..01d49ed7 100644 --- a/pkg/tree/processor_validate.go +++ b/pkg/tree/processor_validate.go @@ -5,6 +5,7 @@ import ( "github.com/sdcio/data-server/pkg/config" "github.com/sdcio/data-server/pkg/pool" + "github.com/sdcio/data-server/pkg/tree/api" "github.com/sdcio/data-server/pkg/tree/types" ) @@ -18,7 +19,7 @@ func NewValidateProcessor(parameters *ValidateProcessorParameters) *ValidateProc } } -func (p *ValidateProcessor) Run(taskpoolFactory pool.VirtualPoolFactory, e Entry) { +func (p *ValidateProcessor) Run(taskpoolFactory pool.VirtualPoolFactory, e api.Entry) { taskpool := taskpoolFactory.NewVirtualPool(pool.VirtualTolerant) taskpool.Submit(newValidateTask(e, p.parameters)) taskpool.CloseAndWait() @@ -39,11 +40,11 @@ func NewValidateProcessorConfig(resultChan chan<- *types.ValidationResultEntry, } type validateTask struct { - e Entry + e api.Entry parameters *ValidateProcessorParameters } -func newValidateTask(e Entry, parameters *ValidateProcessorParameters) *validateTask { +func newValidateTask(e api.Entry, parameters *ValidateProcessorParameters) *validateTask { return &validateTask{ e: e, parameters: parameters, diff --git a/pkg/tree/root_entry.go b/pkg/tree/root_entry.go index 02e937c2..6833c23c 100644 --- a/pkg/tree/root_entry.go +++ b/pkg/tree/root_entry.go @@ -9,6 +9,7 @@ import ( "github.com/sdcio/data-server/pkg/config" "github.com/sdcio/data-server/pkg/pool" + "github.com/sdcio/data-server/pkg/tree/api" "github.com/sdcio/data-server/pkg/tree/importer" "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils" @@ -19,7 +20,7 @@ import ( // RootEntry the root of the cache.Update tree type RootEntry struct { - *sharedEntryAttributes + api.Entry } var ( @@ -27,14 +28,14 @@ var ( ) // NewTreeRoot Instantiate a new Tree Root element. -func NewTreeRoot(ctx context.Context, tc *TreeContext) (*RootEntry, error) { +func NewTreeRoot(ctx context.Context, tc api.TreeContext) (*RootEntry, error) { sea, err := newSharedEntryAttributes(ctx, nil, "", tc) if err != nil { return nil, err } root := &RootEntry{ - sharedEntryAttributes: sea, + Entry: sea, } return root, nil @@ -47,14 +48,14 @@ func (r *RootEntry) stringToDisk(filename string) error { } func (r *RootEntry) DeepCopy(ctx context.Context) (*RootEntry, error) { - tc := r.GetTreeContext().deepCopy() - se, err := r.sharedEntryAttributes.deepCopy(tc, nil) + tc := r.GetTreeContext().DeepCopy() + se, err := r.Entry.DeepCopy(tc, nil) if err != nil { return nil, err } result := &RootEntry{ - sharedEntryAttributes: se, + Entry: se, } return result, nil @@ -64,7 +65,7 @@ func (r *RootEntry) AddUpdatesRecursive(ctx context.Context, us []*types.PathAnd var err error for idx, u := range us { _ = idx - _, err = r.sharedEntryAttributes.AddUpdateRecursive(ctx, u.GetPath(), u.GetUpdate(), flags) + _, err = r.Entry.AddUpdateRecursive(ctx, u.GetPath(), u.GetUpdate(), flags) if err != nil { return err } @@ -73,7 +74,7 @@ func (r *RootEntry) AddUpdatesRecursive(ctx context.Context, us []*types.PathAnd } func (r *RootEntry) ImportConfig(ctx context.Context, basePath *sdcpb.Path, importer importer.ImportConfigAdapter, flags *types.UpdateInsertFlags, poolFactory pool.VirtualPoolFactory) (*types.ImportStats, error) { - e, err := r.sharedEntryAttributes.getOrCreateChilds(ctx, basePath) + e, err := r.Entry.GetOrCreateChilds(ctx, basePath) if err != nil { return nil, err } @@ -86,7 +87,7 @@ func (r *RootEntry) ImportConfig(ctx context.Context, basePath *sdcpb.Path, impo } func (r *RootEntry) SetNonRevertiveIntent(intentName string, nonRevertive bool) { - r.GetTreeContext().nonRevertiveInfo[intentName] = nonRevertive + r.GetTreeContext().NonRevertiveInfo().Add(intentName, nonRevertive) } func (r *RootEntry) Validate(ctx context.Context, vCfg *config.Validation, taskpoolFactory pool.VirtualPoolFactory) (types.ValidationResults, *types.ValidationStats) { @@ -109,7 +110,7 @@ func (r *RootEntry) Validate(ctx context.Context, vCfg *config.Validation, taskp }() validationProcessor := NewValidateProcessor(NewValidateProcessorConfig(validationResultEntryChan, validationStats, vCfg)) - validationProcessor.Run(taskpoolFactory, r.sharedEntryAttributes) + validationProcessor.Run(taskpoolFactory, r.Entry) close(validationResultEntryChan) syncWait.Wait() @@ -119,7 +120,7 @@ func (r *RootEntry) Validate(ctx context.Context, vCfg *config.Validation, taskp // String returns the string representation of the Tree. func (r *RootEntry) String() string { s := []string{} - s = r.sharedEntryAttributes.StringIndent(s) + s = r.Entry.StringIndent(s) return strings.Join(s, "\n") } @@ -128,7 +129,7 @@ func (r *RootEntry) GetUpdatesForOwner(owner string) types.UpdateSlice { // retrieve all the entries from the tree that belong to the given // Owner / Intent, skipping the once marked for deletion // this is to insert / update entries in the cache. - return LeafEntriesToUpdates(r.getByOwnerFiltered(owner, FilterNonDeletedButNewOrUpdated)) + return api.LeafEntriesToUpdates(r.getByOwnerFiltered(owner, api.FilterNonDeletedButNewOrUpdated)) } // GetDeletesForOwner returns the deletes that have been calculated for the given intent / owner @@ -137,12 +138,12 @@ func (r *RootEntry) GetDeletesForOwner(owner string) sdcpb.Paths { // and that are marked for deletion. // This is to cover all the cases where an intent was changed and certain // part of the config got deleted. - deletesOwnerUpdates := LeafEntriesToUpdates(r.getByOwnerFiltered(owner, FilterDeleted)) + deletesOwnerUpdates := api.LeafEntriesToUpdates(r.getByOwnerFiltered(owner, api.FilterDeleted)) // they are retrieved as cache.update, we just need the path for deletion from cache deletesOwner := make(sdcpb.Paths, 0, len(deletesOwnerUpdates)) // so collect the paths for _, d := range deletesOwnerUpdates { - deletesOwner = append(deletesOwner, d.Path()) + deletesOwner = append(deletesOwner, d.SdcpbPath()) } return deletesOwner } @@ -150,14 +151,14 @@ func (r *RootEntry) GetDeletesForOwner(owner string) sdcpb.Paths { // GetHighesPrecedence return the new cache.Update entried from the tree that are the highes priority. // If the onlyNewOrUpdated option is set to true, only the New or Updated entries will be returned // It will append to the given list and provide a new pointer to the slice -func (r *RootEntry) GetHighestPrecedence(onlyNewOrUpdated bool) LeafVariantSlice { - return r.sharedEntryAttributes.GetHighestPrecedence(make(LeafVariantSlice, 0), onlyNewOrUpdated, false, false) +func (r *RootEntry) GetHighestPrecedence(onlyNewOrUpdated bool) api.LeafVariantSlice { + return r.Entry.GetHighestPrecedence(make(api.LeafVariantSlice, 0), onlyNewOrUpdated, false, false) } // GetDeletes returns the paths that due to the Tree content are to be deleted from the southbound device. func (r *RootEntry) GetDeletes(aggregatePaths bool) (types.DeleteEntriesList, error) { deletes := []types.DeleteEntry{} - return r.sharedEntryAttributes.GetDeletes(deletes, aggregatePaths) + return r.Entry.GetDeletes(deletes, aggregatePaths) } func (r *RootEntry) GetAncestorSchema() (*sdcpb.SchemaElem, int) { @@ -165,16 +166,16 @@ func (r *RootEntry) GetAncestorSchema() (*sdcpb.SchemaElem, int) { } func (r *RootEntry) GetDeviations(ctx context.Context, ch chan<- *types.DeviationEntry) { - r.sharedEntryAttributes.GetDeviations(ctx, ch, true) + r.Entry.GetDeviations(ctx, ch, true) } func (r *RootEntry) TreeExport(owner string, priority int32) (*tree_persist.Intent, error) { - treeExport, err := r.sharedEntryAttributes.TreeExport(owner) + treeExport, err := r.Entry.TreeExport(owner) if err != nil { return nil, err } - explicitDeletes := r.GetTreeContext().explicitDeletes.GetByIntentName(owner).ToPathSlice() + explicitDeletes := r.GetTreeContext().ExplicitDeletes().GetByIntentName(owner).ToPathSlice() var rootExportEntry *tree_persist.TreeElement if len(treeExport) != 0 { @@ -186,7 +187,7 @@ func (r *RootEntry) TreeExport(owner string, priority int32) (*tree_persist.Inte IntentName: owner, Root: rootExportEntry, Priority: priority, - NonRevertive: r.GetTreeContext().nonRevertiveInfo[owner], + NonRevertive: r.GetTreeContext().NonRevertiveInfo().IsGenerallyNonRevertive(owner), ExplicitDeletes: explicitDeletes, }, nil } @@ -195,10 +196,10 @@ func (r *RootEntry) TreeExport(owner string, priority int32) (*tree_persist.Inte // getByOwnerFiltered returns the Tree content filtered by owner, whilst allowing to filter further // via providing additional LeafEntryFilter -func (r *RootEntry) getByOwnerFiltered(owner string, f ...LeafEntryFilter) []*LeafEntry { - result := []*LeafEntry{} +func (r *RootEntry) getByOwnerFiltered(owner string, f ...api.LeafEntryFilter) []*api.LeafEntry { + result := []*api.LeafEntry{} // retrieve all leafentries for the owner - leafEntries := r.sharedEntryAttributes.GetByOwner(owner, result) + leafEntries := r.Entry.GetByOwner(owner, result) // range through entries NEXTELEMENT: for _, e := range leafEntries { @@ -230,7 +231,7 @@ func (r *RootEntry) FinishInsertionPhase(ctx context.Context) error { edpsc := ExplicitDeleteProcessorStatCollection{} // apply the explicit deletes - for deletePathPrio := range r.GetTreeContext().explicitDeletes.Items() { + for deletePathPrio := range r.GetTreeContext().ExplicitDeletes().Items() { params := NewExplicitDeleteTaskParameters(deletePathPrio.GetOwner(), deletePathPrio.GetPrio()) @@ -243,7 +244,7 @@ func (r *RootEntry) FinishInsertionPhase(ctx context.Context) error { log.Error(nil, "Applying explicit delete - path not found, skipping", "severity", "WARN", "path", path.ToXPath(false)) } edp := NewExplicitDeleteProcessor(params) - err = edp.Run(ctx, entry, r.GetTreeContext().GetPoolFactory()) + err = edp.Run(ctx, entry, r.GetTreeContext().PoolFactory()) if err != nil { return err } @@ -257,5 +258,5 @@ func (r *RootEntry) FinishInsertionPhase(ctx context.Context) error { })) } - return r.sharedEntryAttributes.FinishInsertionPhase(ctx) + return r.Entry.FinishInsertionPhase(ctx) } diff --git a/pkg/tree/root_entry_test.go b/pkg/tree/root_entry_test.go index 661970b6..6c465c80 100644 --- a/pkg/tree/root_entry_test.go +++ b/pkg/tree/root_entry_test.go @@ -12,6 +12,7 @@ import ( "github.com/openconfig/ygot/ygot" schemaClient "github.com/sdcio/data-server/pkg/datastore/clients/schema" "github.com/sdcio/data-server/pkg/pool" + "github.com/sdcio/data-server/pkg/tree/api" jsonImporter "github.com/sdcio/data-server/pkg/tree/importer/json" "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils/testhelper" @@ -50,10 +51,10 @@ func TestRootEntry_TreeExport(t *testing.T) { cacheMutex: sync.Mutex{}, treeContext: tc, } - result.leafVariants = newLeafVariants(tc, result) + result.leafVariants = api.NewLeafVariants(tc, result) result.leafVariants.Add( - NewLeafEntry( + api.NewLeafEntry( types.NewUpdate(nil, &sdcpb.TypedValue{ Value: &sdcpb.TypedValue_StringVal{StringVal: "Value"}, @@ -98,7 +99,7 @@ func TestRootEntry_TreeExport(t *testing.T) { cacheMutex: sync.Mutex{}, treeContext: tc, } - result.leafVariants = newLeafVariants(tc, result) + result.leafVariants = api.NewLeafVariants(tc, result) // create /interface sharedEntryAttributes interf := &sharedEntryAttributes{ @@ -108,13 +109,13 @@ func TestRootEntry_TreeExport(t *testing.T) { schemaMutex: sync.RWMutex{}, cacheMutex: sync.Mutex{}, } - interf.leafVariants = newLeafVariants(tc, interf) + interf.leafVariants = api.NewLeafVariants(tc, interf) // add interf to result (root) result.childs.Add(interf) // add interface LeafVariant interf.leafVariants.Add( - NewLeafEntry( + api.NewLeafEntry( types.NewUpdate(nil, &sdcpb.TypedValue{ Value: &sdcpb.TypedValue_StringVal{StringVal: "Value"}, @@ -163,7 +164,7 @@ func TestRootEntry_TreeExport(t *testing.T) { cacheMutex: sync.Mutex{}, treeContext: tc, } - result.leafVariants = newLeafVariants(tc, result) + result.leafVariants = api.NewLeafVariants(tc, result) // create /interface sharedEntryAttributes interf := &sharedEntryAttributes{ @@ -173,13 +174,13 @@ func TestRootEntry_TreeExport(t *testing.T) { schemaMutex: sync.RWMutex{}, cacheMutex: sync.Mutex{}, } - interf.leafVariants = newLeafVariants(tc, interf) + interf.leafVariants = api.NewLeafVariants(tc, interf) // add interf to result (root) result.childs.Add(interf) // add interface LeafVariant interf.leafVariants.Add( - NewLeafEntry( + api.NewLeafEntry( types.NewUpdate(nil, &sdcpb.TypedValue{ Value: &sdcpb.TypedValue_StringVal{StringVal: "Value"}, @@ -189,7 +190,7 @@ func TestRootEntry_TreeExport(t *testing.T) { ) // add interface LeafVariant interf.leafVariants.Add( - NewLeafEntry( + api.NewLeafEntry( types.NewUpdate(nil, &sdcpb.TypedValue{ Value: &sdcpb.TypedValue_StringVal{StringVal: "OtherValue"}, @@ -206,13 +207,13 @@ func TestRootEntry_TreeExport(t *testing.T) { schemaMutex: sync.RWMutex{}, cacheMutex: sync.Mutex{}, } - system.leafVariants = newLeafVariants(tc, system) + system.leafVariants = api.NewLeafVariants(tc, system) // add interf to result (root) result.childs.Add(system) // add interface LeafVariant interf.leafVariants.Add( - NewLeafEntry( + api.NewLeafEntry( types.NewUpdate(nil, &sdcpb.TypedValue{ Value: &sdcpb.TypedValue_StringVal{StringVal: "Value"}, @@ -261,7 +262,7 @@ func TestRootEntry_TreeExport(t *testing.T) { cacheMutex: sync.Mutex{}, treeContext: tc, } - result.leafVariants = newLeafVariants(tc, result) + result.leafVariants = api.NewLeafVariants(tc, result) // create /interface sharedEntryAttributes interf := &sharedEntryAttributes{ @@ -271,13 +272,13 @@ func TestRootEntry_TreeExport(t *testing.T) { schemaMutex: sync.RWMutex{}, cacheMutex: sync.Mutex{}, } - interf.leafVariants = newLeafVariants(tc, interf) + interf.leafVariants = api.NewLeafVariants(tc, interf) // add interf to result (root) result.childs.Add(interf) // add interface LeafVariant interf.leafVariants.Add( - NewLeafEntry( + api.NewLeafEntry( types.NewUpdate(nil, &sdcpb.TypedValue{ Value: &sdcpb.TypedValue_StringVal{StringVal: "Value"}, @@ -298,7 +299,7 @@ func TestRootEntry_TreeExport(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := &RootEntry{ - sharedEntryAttributes: tt.sharedEntryAttributes(), + Entry: tt.sharedEntryAttributes(), } got, err := r.TreeExport(tt.args.owner, tt.args.priority) if (err != nil) != tt.wantErr { @@ -455,7 +456,7 @@ func TestRootEntry_AddUpdatesRecursive(t *testing.T) { t.Fatal(err) } s.schema = schema.GetSchema() - s.leafVariants = newLeafVariants(tc, s) + s.leafVariants = api.NewLeafVariants(tc, s) return s }, }, @@ -520,14 +521,14 @@ func TestRootEntry_AddUpdatesRecursive(t *testing.T) { t.Fatal(err) } - return &RootEntry{sharedEntryAttributes: s} + return &RootEntry{Entry: s} }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := &RootEntry{ - sharedEntryAttributes: tt.fields.sharedEntryAttributes(t), + Entry: tt.fields.sharedEntryAttributes(t), } if err := r.AddUpdatesRecursive(ctx, tt.args.pau, tt.args.flags); (err != nil) != tt.wantErr { t.Errorf("RootEntry.AddUpdatesRecursive() error = %v, wantErr %v", err, tt.wantErr) diff --git a/pkg/tree/sharedEntryAttributes.go b/pkg/tree/sharedEntryAttributes.go index fcbacfaa..798915cd 100644 --- a/pkg/tree/sharedEntryAttributes.go +++ b/pkg/tree/sharedEntryAttributes.go @@ -14,6 +14,8 @@ import ( "unicode/utf8" "github.com/sdcio/data-server/pkg/config" + "github.com/sdcio/data-server/pkg/tree/api" + "github.com/sdcio/data-server/pkg/tree/consts" "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils" logf "github.com/sdcio/logger" @@ -29,21 +31,21 @@ var ( // sharedEntryAttributes contains the attributes shared by Entry and RootEntry type sharedEntryAttributes struct { // parent entry, nil for the root Entry - parent Entry + parent api.Entry // pathElemName the path elements name the entry represents pathElemName string // childs mutual exclusive with LeafVariants childs *childMap // leafVariants mutual exclusive with Childs // If Entry is a leaf it can hold multiple leafVariants - leafVariants *LeafVariants + leafVariants *api.LeafVariants // schema the schema element for this entry schema *sdcpb.SchemaElem schemaMutex sync.RWMutex choicesResolvers choiceResolvers - treeContext *TreeContext + treeContext api.TreeContext // state cache cacheMutex sync.Mutex @@ -54,7 +56,20 @@ type sharedEntryAttributes struct { level *int } -func (s *sharedEntryAttributes) deepCopy(tc *TreeContext, parent Entry) (*sharedEntryAttributes, error) { +// NewEntry constructor for Entries +func NewEntry(ctx context.Context, parent api.Entry, pathElemName string, tc api.TreeContext) (*sharedEntryAttributes, error) { + // create a new sharedEntryAttributes instance + sea, err := newSharedEntryAttributes(ctx, parent, pathElemName, tc) + if err != nil { + return nil, err + } + + // add the Entry as a child to the parent Entry + err = parent.AddChild(ctx, sea) + return sea, err +} + +func (s *sharedEntryAttributes) DeepCopy(tc api.TreeContext, parent api.Entry) (api.Entry, error) { result := &sharedEntryAttributes{ parent: parent, pathElemName: s.pathElemName, @@ -82,18 +97,14 @@ func (s *sharedEntryAttributes) deepCopy(tc *TreeContext, parent Entry) (*shared return result, nil } -func (s *sharedEntryAttributes) DeepCopy(tc *TreeContext, parent Entry) (Entry, error) { - return s.deepCopy(tc, parent) -} - -func newSharedEntryAttributes(ctx context.Context, parent Entry, pathElemName string, tc *TreeContext) (*sharedEntryAttributes, error) { +func newSharedEntryAttributes(ctx context.Context, parent api.Entry, pathElemName string, tc api.TreeContext) (*sharedEntryAttributes, error) { s := &sharedEntryAttributes{ parent: parent, pathElemName: pathElemName, childs: newChildMap(), treeContext: tc, } - s.leafVariants = newLeafVariants(tc, s) + s.leafVariants = api.NewLeafVariants(tc, s) // populate the schema err := s.populateSchema(ctx) @@ -113,14 +124,14 @@ func newSharedEntryAttributes(ctx context.Context, parent Entry, pathElemName st return s, nil } -func (s *sharedEntryAttributes) GetRoot() Entry { +func (s *sharedEntryAttributes) GetRoot() api.Entry { if s.IsRoot() { return s } return s.parent.GetRoot() } -func (s *sharedEntryAttributes) GetTreeContext() *TreeContext { +func (s *sharedEntryAttributes) GetTreeContext() api.TreeContext { return s.treeContext } @@ -228,7 +239,7 @@ func (s *sharedEntryAttributes) checkAndCreateKeysAsLeafs(ctx context.Context, i }) // iterate through the keys - var item Entry = s + var item api.Entry = s // construct the key path // doing so outside the loop to reuse @@ -241,10 +252,10 @@ func (s *sharedEntryAttributes) checkAndCreateKeysAsLeafs(ctx context.Context, i // if the key Leaf exists continue with next key if entryExists { // if it exists, we need to check that the entry for the owner exists. - var result []*LeafEntry + var result []*api.LeafEntry lvs := child.GetByOwner(intentName, result) if len(lvs) > 0 { - lvs[0].RemoveDeleteFlag() + lvs[0].DropDeleteFlag() // continue with parent Entry BEFORE continuing the loop item = item.GetParent() continue @@ -312,7 +323,7 @@ func (s *sharedEntryAttributes) populateSchema(ctx context.Context) error { if getSchema { // trieve if the getSchema var is still true - schemaResp, err := s.treeContext.schemaClient.GetSchemaSdcpbPath(ctx, path) + schemaResp, err := s.treeContext.SchemaClient().GetSchemaSdcpbPath(ctx, path) if err != nil { return err } @@ -332,13 +343,13 @@ func (s *sharedEntryAttributes) GetSchema() *sdcpb.SchemaElem { } // GetChildren returns the children Map of the Entry -func (s *sharedEntryAttributes) getChildren() map[string]Entry { +func (s *sharedEntryAttributes) getChildren() map[string]api.Entry { return s.childs.GetAll() } // getListChilds collects all the childs of the list. In the tree we store them seperated into their key branches. // this is collecting all the last level key entries. -func (s *sharedEntryAttributes) GetListChilds() ([]Entry, error) { +func (s *sharedEntryAttributes) GetListChilds() ([]api.Entry, error) { if s.schema == nil { return nil, fmt.Errorf("error GetListChilds() non schema level %s", s.SdcpbPath().ToXPath(false)) } @@ -349,8 +360,8 @@ func (s *sharedEntryAttributes) GetListChilds() ([]Entry, error) { if len(keys) == 0 { return nil, fmt.Errorf("error GetListChilds() not a List Container %s", s.SdcpbPath().ToXPath(false)) } - actualEntries := []Entry{s} - var newEntries []Entry + actualEntries := []api.Entry{s} + var newEntries []api.Entry for level := 0; level < len(keys); level++ { for _, e := range actualEntries { @@ -360,7 +371,7 @@ func (s *sharedEntryAttributes) GetListChilds() ([]Entry, error) { } } actualEntries = newEntries - newEntries = []Entry{} + newEntries = []api.Entry{} } return actualEntries, nil @@ -369,14 +380,14 @@ func (s *sharedEntryAttributes) GetListChilds() ([]Entry, error) { // FilterChilds returns the child entries (skipping the key entries in the tree) that // match the given keys. The keys do not need to match all levels of keys, in which case the // key level is considered a wildcard match (*) -func (s *sharedEntryAttributes) FilterChilds(keys map[string]string) ([]Entry, error) { +func (s *sharedEntryAttributes) FilterChilds(keys map[string]string) ([]api.Entry, error) { if s.schema == nil { return nil, fmt.Errorf("error non schema level %s", s.SdcpbPath().ToXPath(false)) } - result := []Entry{} + result := []api.Entry{} // init the processEntries with s - processEntries := []Entry{s} + processEntries := []api.Entry{s} // retrieve the schema keys schemaKeys := s.GetSchemaKeys() @@ -401,7 +412,7 @@ func (s *sharedEntryAttributes) FilterChilds(keys map[string]string) ([]Entry, e } } else { // this is basically the wildcard case, so go through all childs and add them - result = []Entry{} + result = []api.Entry{} for _, entry := range processEntries { childs := entry.GetChilds(types.DescendMethodAll) for _, v := range childs { @@ -417,7 +428,7 @@ func (s *sharedEntryAttributes) FilterChilds(keys map[string]string) ([]Entry, e } // GetParent returns the parent entry -func (s *sharedEntryAttributes) GetParent() Entry { +func (s *sharedEntryAttributes) GetParent() api.Entry { return s.parent } @@ -475,7 +486,7 @@ func (s *sharedEntryAttributes) GetSchemaKeys() []string { // getAggregatedDeletes is called on levels that have no schema attached, meaning key schemas. // here we might delete the whole branch of the tree, if all key elements are being deleted // if not, we continue with regular deltes -func (s *sharedEntryAttributes) getAggregatedDeletes(deletes []types.DeleteEntry, aggregatePaths bool) ([]types.DeleteEntry, error) { +func (s *sharedEntryAttributes) GetAggregatedDeletes(deletes []types.DeleteEntry, aggregatePaths bool) ([]types.DeleteEntry, error) { var err error // we take a look into the level(s) up // trying to get the schema @@ -493,7 +504,7 @@ func (s *sharedEntryAttributes) getAggregatedDeletes(deletes []types.DeleteEntry for _, n := range keys { c, exists := s.childs.GetEntry(n) // these keys should aways exist, so for now we do not catch the non existing key case - if exists && !c.shouldDelete() { + if exists && !c.ShouldDelete() { // if not all the keys are marked for deletion, we need to revert to regular deletion doAggregateDelete = false break @@ -522,14 +533,14 @@ func (s *sharedEntryAttributes) getAggregatedDeletes(deletes []types.DeleteEntry // In caomparison to // - remainsToExists() returns true, because they remain to exist even though implicitly. // - shouldDelete() returns false, because no explicit delete should be issued for them. -func (s *sharedEntryAttributes) canDelete() bool { +func (s *sharedEntryAttributes) CanDelete() bool { s.cacheMutex.Lock() defer s.cacheMutex.Unlock() if s.cacheCanDelete != nil { return *s.cacheCanDelete } - leafVariantCanDelete := s.leafVariants.canDelete() + leafVariantCanDelete := s.leafVariants.CanDelete() if !leafVariantCanDelete { s.cacheCanDelete = utils.BoolPtr(false) return *s.cacheCanDelete @@ -537,7 +548,7 @@ func (s *sharedEntryAttributes) canDelete() bool { // handle containers for _, c := range s.GetChilds(types.DescendMethodActiveChilds) { - canDelete := c.canDelete() + canDelete := c.CanDelete() if !canDelete { s.cacheCanDelete = utils.BoolPtr(false) return *s.cacheCanDelete @@ -551,7 +562,7 @@ func (s *sharedEntryAttributes) CanDeleteBranch(keepDefault bool) bool { s.cacheMutex.Lock() defer s.cacheMutex.Unlock() - leafVariantCanDelete := s.leafVariants.canDeleteBranch(keepDefault) + leafVariantCanDelete := s.leafVariants.CanDeleteBranch(keepDefault) if !leafVariantCanDelete { return false } @@ -568,7 +579,7 @@ func (s *sharedEntryAttributes) CanDeleteBranch(keepDefault bool) bool { } // shouldDelete checks if a container or Leaf(List) is to be explicitly deleted. -func (s *sharedEntryAttributes) shouldDelete() bool { +func (s *sharedEntryAttributes) ShouldDelete() bool { // see if we have the value cached s.cacheMutex.Lock() defer s.cacheMutex.Unlock() @@ -576,7 +587,7 @@ func (s *sharedEntryAttributes) shouldDelete() bool { return *s.cacheShouldDelete } // check if the leafVariants result in a should delete - leafVariantshouldDelete := s.leafVariants.shouldDelete() + leafVariantshouldDelete := s.leafVariants.ShouldDelete() // for containers, it is basically a canDelete() check. canDelete := false @@ -593,7 +604,7 @@ func (s *sharedEntryAttributes) shouldDelete() bool { // iterate through the active childs for _, c := range activeChilds { // check if the child can be deleted - canDelete = c.canDelete() + canDelete = c.CanDelete() // if it can explicitly not be deleted, then the result is clear, we should not delete if !canDelete { break @@ -601,7 +612,7 @@ func (s *sharedEntryAttributes) shouldDelete() bool { // if it can be deleted we need to check if there is a contibuting entry that // requires deletion only if there is a contributing shouldDelete() == true then we must issue // a real delete - shouldDelete = shouldDelete || c.shouldDelete() + shouldDelete = shouldDelete || c.ShouldDelete() } // the overall result is @@ -613,7 +624,7 @@ func (s *sharedEntryAttributes) shouldDelete() bool { // shouldDelete() [only if an entry is explicitly to be deleted, issue a delete] // and // s.leafVariants.canDelete() - result := leafVariantshouldDelete || (canDelete && shouldDelete && s.leafVariants.canDelete()) + result := leafVariantshouldDelete || (canDelete && shouldDelete && s.leafVariants.CanDelete()) s.cacheShouldDelete = &result return result @@ -626,7 +637,7 @@ func (s *sharedEntryAttributes) RemainsToExist() bool { if s.cacheRemains != nil { return *s.cacheRemains } - leafVariantResult := s.leafVariants.remainsToExist() + leafVariantResult := s.leafVariants.RemainsToExist() // handle containers childsRemain := false @@ -648,7 +659,7 @@ func (s *sharedEntryAttributes) RemainsToExist() bool { func (s *sharedEntryAttributes) getRegularDeletes(deletes []types.DeleteEntry, aggregate bool) ([]types.DeleteEntry, error) { var err error - if s.shouldDelete() && !s.IsRoot() && len(s.GetSchemaKeys()) == 0 { + if s.ShouldDelete() && !s.IsRoot() && len(s.GetSchemaKeys()) == 0 { return append(deletes, s), nil } @@ -671,7 +682,7 @@ func (s *sharedEntryAttributes) GetDeletes(deletes []types.DeleteEntry, aggregat // if the actual level has no schema assigned we're on a key level // element. Hence we try deletion via aggregation if s.schema == nil && aggregatePaths { - return s.getAggregatedDeletes(deletes, aggregatePaths) + return s.GetAggregatedDeletes(deletes, aggregatePaths) } // else perform regular deletion @@ -681,7 +692,7 @@ func (s *sharedEntryAttributes) GetDeletes(deletes []types.DeleteEntry, aggregat // GetAncestorSchema returns the schema of the parent node if the schema is set. // if the parent has no schema (is a key element in the tree) it will recurs the call to the parents parent. // the level of recursion is indicated via the levelUp attribute -func (s *sharedEntryAttributes) GetFirstAncestorWithSchema() (Entry, int) { +func (s *sharedEntryAttributes) GetFirstAncestorWithSchema() (api.Entry, int) { // if root node is reached if s.IsRoot() { return nil, 0 @@ -699,7 +710,7 @@ func (s *sharedEntryAttributes) GetFirstAncestorWithSchema() (Entry, int) { } // GetByOwner returns all the LeafEntries that belong to a certain owner. -func (s *sharedEntryAttributes) GetByOwner(owner string, result []*LeafEntry) LeafVariantSlice { +func (s *sharedEntryAttributes) GetByOwner(owner string, result []*api.LeafEntry) api.LeafVariantSlice { lv := s.leafVariants.GetByOwner(owner) if lv != nil { result = append(result, lv) @@ -723,7 +734,7 @@ func (s *sharedEntryAttributes) String() string { } // AddChild add an entry to the list of child entries for the entry. -func (s *sharedEntryAttributes) AddChild(ctx context.Context, e Entry) error { +func (s *sharedEntryAttributes) AddChild(ctx context.Context, e api.Entry) error { // make sure Entry should not only hold LeafEntries if s.leafVariants.Length() > 0 { // An exception are presence containers @@ -736,7 +747,7 @@ func (s *sharedEntryAttributes) AddChild(ctx context.Context, e Entry) error { return nil } -func (s *sharedEntryAttributes) NavigateSdcpbPath(ctx context.Context, path *sdcpb.Path) (Entry, error) { +func (s *sharedEntryAttributes) NavigateSdcpbPath(ctx context.Context, path *sdcpb.Path) (api.Entry, error) { pathElems := path.GetElem() var err error if len(pathElems) == 0 { @@ -751,7 +762,7 @@ func (s *sharedEntryAttributes) NavigateSdcpbPath(ctx context.Context, path *sdc case ".": s.NavigateSdcpbPath(ctx, path.CopyAndRemoveFirstPathElem()) case "..": - var entry Entry + var entry api.Entry entry = s.parent // we need to skip key levels in the tree // if the next path element is again .. we need to skip key values that are present in the tree @@ -788,8 +799,8 @@ func (s *sharedEntryAttributes) NavigateSdcpbPath(ctx context.Context, path *sdc return nil, fmt.Errorf("navigating tree, reached %v but child %v does not exist", s.SdcpbPath().ToXPath(false), pathElems) } -func (s *sharedEntryAttributes) tryLoadingDefault(ctx context.Context, path *sdcpb.Path) (Entry, error) { - schema, err := s.treeContext.schemaClient.GetSchemaSdcpbPath(ctx, path) +func (s *sharedEntryAttributes) tryLoadingDefault(ctx context.Context, path *sdcpb.Path) (api.Entry, error) { + schema, err := s.treeContext.SchemaClient().GetSchemaSdcpbPath(ctx, path) if err != nil { return nil, fmt.Errorf("error trying to load defaults for %s: %v", path.ToXPath(false), err) } @@ -810,7 +821,7 @@ func (s *sharedEntryAttributes) tryLoadingDefault(ctx context.Context, path *sdc } func (s *sharedEntryAttributes) DeleteBranch(ctx context.Context, path *sdcpb.Path, owner string) error { - var entry Entry + var entry api.Entry var err error if path == nil { @@ -881,7 +892,7 @@ func (s *sharedEntryAttributes) deleteBranchInternal(ctx context.Context, owner // GetHighestPrecedence goes through the whole branch and returns the new and updated cache.Updates. // These are the updated that will be send to the device. -func (s *sharedEntryAttributes) GetHighestPrecedence(result LeafVariantSlice, onlyNewOrUpdated bool, includeDefaults bool, includeExplicitDelete bool) LeafVariantSlice { +func (s *sharedEntryAttributes) GetHighestPrecedence(result api.LeafVariantSlice, onlyNewOrUpdated bool, includeDefaults bool, includeExplicitDelete bool) api.LeafVariantSlice { // get the highes precedence LeafeVariant and add it to the list lv := s.leafVariants.GetHighestPrecedence(onlyNewOrUpdated, includeDefaults, includeExplicitDelete) if lv != nil { @@ -895,7 +906,7 @@ func (s *sharedEntryAttributes) GetHighestPrecedence(result LeafVariantSlice, on return result } -func (s *sharedEntryAttributes) getHighestPrecedenceLeafValue(ctx context.Context) (*LeafEntry, error) { +func (s *sharedEntryAttributes) GetHighestPrecedenceLeafValue(ctx context.Context) (*api.LeafEntry, error) { for _, x := range []string{"existing", "default"} { lv := s.leafVariants.GetHighestPrecedence(false, true, false) if lv != nil { @@ -911,10 +922,10 @@ func (s *sharedEntryAttributes) getHighestPrecedenceLeafValue(ctx context.Contex return nil, fmt.Errorf("error no value present for %s", s.SdcpbPath().ToXPath(false)) } -func (s *sharedEntryAttributes) GetRootBasedEntryChain() []Entry { +func (s *sharedEntryAttributes) GetRootBasedEntryChain() []api.Entry { // Build the chain from root to this entry without recursion - var chain []Entry - var current Entry = s + var chain []api.Entry + var current api.Entry = s for current != nil && !current.IsRoot() { chain = append(chain, current) current = current.GetParent() @@ -928,24 +939,12 @@ func (s *sharedEntryAttributes) GetRootBasedEntryChain() []Entry { return chain } -type HighestPrecedenceFilter func(le *LeafEntry) bool - -func HighestPrecedenceFilterAll(le *LeafEntry) bool { - return true -} -func HighestPrecedenceFilterWithoutNew(le *LeafEntry) bool { - return !le.IsNew -} -func HighestPrecedenceFilterWithoutDeleted(le *LeafEntry) bool { - return !le.Delete -} - // getHighestPrecedenceValueOfBranch goes through all the child branches to find the highest // precedence value (lowest priority value) for the entire branch and returns it. -func (s *sharedEntryAttributes) getHighestPrecedenceValueOfBranch(filter HighestPrecedenceFilter) int32 { +func (s *sharedEntryAttributes) GetHighestPrecedenceValueOfBranch(filter api.HighestPrecedenceFilter) int32 { result := int32(math.MaxInt32) for _, e := range s.childs.GetAll() { - if val := e.getHighestPrecedenceValueOfBranch(filter); val < result { + if val := e.GetHighestPrecedenceValueOfBranch(filter); val < result { result = val } } @@ -963,7 +962,7 @@ func (s *sharedEntryAttributes) ValidateLevel(ctx context.Context, resultChan ch if s.RemainsToExist() { // TODO: Validate Enums if !vCfg.DisabledValidators.Mandatory { - s.validateMandatory(ctx, resultChan, stats) + s.ValidateMandatory(ctx, resultChan, stats) } if !vCfg.DisabledValidators.Leafref { s.validateLeafRefs(ctx, resultChan, stats) @@ -1210,7 +1209,7 @@ func (s *sharedEntryAttributes) validatePattern(resultChan chan<- *types.Validat // validateMandatory validates that all the mandatory attributes, // defined by the schema are present either in the tree or in the index. -func (s *sharedEntryAttributes) validateMandatory(ctx context.Context, resultChan chan<- *types.ValidationResultEntry, stats *types.ValidationStats) { +func (s *sharedEntryAttributes) ValidateMandatory(ctx context.Context, resultChan chan<- *types.ValidationResultEntry, stats *types.ValidationStats) { log := logf.FromContext(ctx) if !s.RemainsToExist() { return @@ -1254,7 +1253,7 @@ func (s *sharedEntryAttributes) validateMandatory(ctx context.Context, resultCha log.Error(ValidationError, "mandatory attribute could not be found as child, field or choice", "path", s.SdcpbPath().ToXPath(false), "attribute", c.Name) } - s.validateMandatoryWithKeys(ctx, len(containerSchema.GetKeys()), attributes, choiceName, resultChan) + s.ValidateMandatoryWithKeys(ctx, len(containerSchema.GetKeys()), attributes, choiceName, resultChan) } stats.Add(types.StatTypeMandatory, uint32(len(containerSchema.GetMandatoryChildrenConfig()))) } @@ -1264,14 +1263,14 @@ func (s *sharedEntryAttributes) validateMandatory(ctx context.Context, resultCha // validateMandatoryWithKeys steps down the tree, passing the key levels and checking the existence of the mandatory. // attributes is a string slice, it will be checked that at least of the the given attributes is defined // !Not checking all of these are defined (call multiple times with single entry in attributes for that matter)! -func (s *sharedEntryAttributes) validateMandatoryWithKeys(ctx context.Context, level int, attributes []string, choiceName string, resultChan chan<- *types.ValidationResultEntry) { - if s.shouldDelete() { +func (s *sharedEntryAttributes) ValidateMandatoryWithKeys(ctx context.Context, level int, attributes []string, choiceName string, resultChan chan<- *types.ValidationResultEntry) { + if s.ShouldDelete() { return } if level == 0 { success := false existsInTree := false - var v Entry + var v api.Entry // iterate over the attributes make sure any of these exists for _, attr := range attributes { // first check if the mandatory value is set via the intent, e.g. part of the tree already @@ -1299,7 +1298,7 @@ func (s *sharedEntryAttributes) validateMandatoryWithKeys(ctx context.Context, l } for _, c := range s.GetChilds(types.DescendMethodActiveChilds) { - c.validateMandatoryWithKeys(ctx, level-1, attributes, choiceName, resultChan) + c.ValidateMandatoryWithKeys(ctx, level-1, attributes, choiceName, resultChan) } } @@ -1389,19 +1388,19 @@ func (s *sharedEntryAttributes) populateChoiceCaseResolvers(_ context.Context) e child, childExists := s.childs.GetEntry(elem) // set the value from the tree as well if childExists { - valWDeleted := child.getHighestPrecedenceValueOfBranch(HighestPrecedenceFilterAll) + valWDeleted := child.GetHighestPrecedenceValueOfBranch(api.HighestPrecedenceFilterAll) if valWDeleted <= highestWDeleted { highestWDeleted = valWDeleted - if child.canDelete() { + if child.CanDelete() { isDeleted = true } } - valWODeleted := child.getHighestPrecedenceValueOfBranch(HighestPrecedenceFilterWithoutDeleted) + valWODeleted := child.GetHighestPrecedenceValueOfBranch(api.HighestPrecedenceFilterWithoutDeleted) if valWODeleted <= highestWODeleted { highestWODeleted = valWODeleted } - valWONew := child.getHighestPrecedenceValueOfBranch(HighestPrecedenceFilterWithoutNew) + valWONew := child.GetHighestPrecedenceValueOfBranch(api.HighestPrecedenceFilterWithoutNew) if valWONew <= highestWONew { highestWONew = valWONew } @@ -1413,11 +1412,11 @@ func (s *sharedEntryAttributes) populateChoiceCaseResolvers(_ context.Context) e return nil } -func (s *sharedEntryAttributes) GetChild(name string) (Entry, bool) { +func (s *sharedEntryAttributes) GetChild(name string) (api.Entry, bool) { return s.childs.GetEntry(name) } -func (s *sharedEntryAttributes) GetChilds(d types.DescendMethod) EntryMap { +func (s *sharedEntryAttributes) GetChilds(d types.DescendMethod) api.EntryMap { if s.schema == nil { return s.childs.GetAll() } @@ -1432,7 +1431,7 @@ func (s *sharedEntryAttributes) GetChilds(d types.DescendMethod) EntryMap { if len(skipAttributesList) == 0 { return s.childs.GetAll() } - result := map[string]Entry{} + result := map[string]api.Entry{} // optimization option: sort the slices and forward in parallel, lifts extra burden that the contains call holds. for childName, child := range s.childs.GetAll() { if slices.Contains(skipAttributesList, childName) { @@ -1559,12 +1558,12 @@ func (s *sharedEntryAttributes) TreeExport(owner string) ([]*tree_persist.TreeEl return nil, nil } -func (s *sharedEntryAttributes) getOrCreateChilds(ctx context.Context, path *sdcpb.Path) (Entry, error) { +func (s *sharedEntryAttributes) GetOrCreateChilds(ctx context.Context, path *sdcpb.Path) (api.Entry, error) { if path == nil || len(path.Elem) == 0 { return s, nil } - var current Entry = s + var current api.Entry = s for i, pe := range path.Elem { // Step 1: Find or create the child for the path element name newCurrent, exists := current.GetChilds(types.DescendMethodAll)[pe.Name] @@ -1616,7 +1615,7 @@ func (s *sharedEntryAttributes) getOrCreateChilds(ctx context.Context, path *sdc // AddUpdateRecursive recursively adds the given cache.Update to the tree. Thereby creating all the entries along the path. // if the entries along th path already exist, the existing entries are called to add the Update. -func (s *sharedEntryAttributes) AddUpdateRecursive(ctx context.Context, path *sdcpb.Path, u *types.Update, flags *types.UpdateInsertFlags) (Entry, error) { +func (s *sharedEntryAttributes) AddUpdateRecursive(ctx context.Context, path *sdcpb.Path, u *types.Update, flags *types.UpdateInsertFlags) (api.Entry, error) { var err error relPath := path @@ -1627,9 +1626,9 @@ func (s *sharedEntryAttributes) AddUpdateRecursive(ctx context.Context, path *sd return nil, err } } - return s.addUpdateRecursiveInternal(ctx, relPath, 0, u, flags) + return s.AddUpdateRecursiveInternal(ctx, relPath, 0, u, flags) } -func (s *sharedEntryAttributes) addUpdateRecursiveInternal(ctx context.Context, path *sdcpb.Path, idx int, u *types.Update, flags *types.UpdateInsertFlags) (Entry, error) { +func (s *sharedEntryAttributes) AddUpdateRecursiveInternal(ctx context.Context, path *sdcpb.Path, idx int, u *types.Update, flags *types.UpdateInsertFlags) (api.Entry, error) { // make sure all the keys are also present as leafs err := s.checkAndCreateKeysAsLeafs(ctx, u.Owner(), u.Priority(), flags) @@ -1640,12 +1639,12 @@ func (s *sharedEntryAttributes) addUpdateRecursiveInternal(ctx context.Context, // continue with recursive add otherwise if path == nil || len(path.GetElem()) == 0 || idx >= len(path.GetElem()) { // delegate update handling to leafVariants - s.leafVariants.Add(NewLeafEntry(u, flags, s)) + s.leafVariants.Add(api.NewLeafEntry(u, flags, s)) return s, nil } - var e Entry - var x Entry = s + var e api.Entry + var x api.Entry = s var exists bool for name := range path.GetElem()[idx].PathElemNames() { if e, exists = x.GetChilds(types.DescendMethodAll)[name]; !exists { @@ -1662,7 +1661,7 @@ func (s *sharedEntryAttributes) addUpdateRecursiveInternal(ctx context.Context, x = e } - return x.addUpdateRecursiveInternal(ctx, path, idx+1, u, flags) + return x.AddUpdateRecursiveInternal(ctx, path, idx+1, u, flags) } // containsOnlyDefaults checks for presence containers, if only default values are present, @@ -1688,12 +1687,12 @@ func (s *sharedEntryAttributes) containsOnlyDefaults() bool { return false } // check if the value is the default value - le, err := v.getHighestPrecedenceLeafValue(context.TODO()) + le, err := v.GetHighestPrecedenceLeafValue(context.TODO()) if err != nil { return false } // if the owner is not Default return false - if le.Owner() != DefaultsIntentName { + if le.Owner() != consts.DefaultsIntentName { return false } } @@ -1701,6 +1700,6 @@ func (s *sharedEntryAttributes) containsOnlyDefaults() bool { return true } -func (s *sharedEntryAttributes) GetLeafVariantEntries() LeafVariantEntries { +func (s *sharedEntryAttributes) GetLeafVariants() *api.LeafVariants { return s.leafVariants } diff --git a/pkg/tree/sharedEntryAttributes_test.go b/pkg/tree/sharedEntryAttributes_test.go index 30428ed6..829b25a1 100644 --- a/pkg/tree/sharedEntryAttributes_test.go +++ b/pkg/tree/sharedEntryAttributes_test.go @@ -14,6 +14,8 @@ import ( "github.com/sdcio/data-server/pkg/config" schemaClient "github.com/sdcio/data-server/pkg/datastore/clients/schema" "github.com/sdcio/data-server/pkg/pool" + "github.com/sdcio/data-server/pkg/tree/api" + "github.com/sdcio/data-server/pkg/tree/consts" jsonImporter "github.com/sdcio/data-server/pkg/tree/importer/json" "github.com/sdcio/data-server/pkg/tree/importer/proto" "github.com/sdcio/data-server/pkg/tree/types" @@ -94,16 +96,19 @@ func Test_sharedEntryAttributes_DeepCopy(t *testing.T) { root: func() *RootEntry { tc := NewTreeContext(nil, pool.NewSharedTaskPool(context.Background(), runtime.GOMAXPROCS(0))) + var e api.Entry + e = &sharedEntryAttributes{ + pathElemName: "__root__", + childs: newChildMap(), + choicesResolvers: choiceResolvers{}, + parent: nil, + treeContext: tc, + leafVariants: api.NewLeafVariants(tc, e), + } r := &RootEntry{ - sharedEntryAttributes: &sharedEntryAttributes{ - pathElemName: "__root__", - childs: newChildMap(), - choicesResolvers: choiceResolvers{}, - parent: nil, - treeContext: tc, - }, + Entry: e, } - r.leafVariants = newLeafVariants(tc, r.sharedEntryAttributes) + return r }, }, @@ -187,13 +192,13 @@ func Test_sharedEntryAttributes_DeleteSubtree(t *testing.T) { } tests := []struct { name string - sharedEntryAttributes func(t *testing.T) *sharedEntryAttributes + sharedEntryAttributes func(t *testing.T) api.Entry args args wantErr bool }{ { name: "one", - sharedEntryAttributes: func(t *testing.T) *sharedEntryAttributes { + sharedEntryAttributes: func(t *testing.T) api.Entry { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() @@ -221,7 +226,7 @@ func Test_sharedEntryAttributes_DeleteSubtree(t *testing.T) { t.Fatal(err) } - return root.sharedEntryAttributes + return root.Entry }, args: args{ relativePath: &sdcpb.Path{Elem: []*sdcpb.PathElem{sdcpb.NewPathElem("interface", nil)}}, @@ -231,7 +236,7 @@ func Test_sharedEntryAttributes_DeleteSubtree(t *testing.T) { }, { name: "wrong path", - sharedEntryAttributes: func(t *testing.T) *sharedEntryAttributes { + sharedEntryAttributes: func(t *testing.T) api.Entry { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() @@ -259,7 +264,7 @@ func Test_sharedEntryAttributes_DeleteSubtree(t *testing.T) { t.Fatal(err) } - return root.sharedEntryAttributes + return root.Entry }, args: args{ relativePath: &sdcpb.Path{ @@ -286,7 +291,7 @@ func Test_sharedEntryAttributes_DeleteSubtree(t *testing.T) { t.Error(err) return } - les := []*LeafEntry{} + les := []*api.LeafEntry{} result := e.GetByOwner(tt.args.owner, les) if len(result) > 0 { t.Errorf("expected all elements under %s to be deleted for owner %s but got %d elements", tt.args.relativePath.ToXPath(false), tt.args.owner, len(result)) @@ -473,7 +478,7 @@ func Test_sharedEntryAttributes_GetDeviations(t *testing.T) { running.Patterntest = ygot.String("hallo 0") - _, err = loadYgotStructIntoTreeRoot(ctx, running, root, RunningIntentName, RunningValuesPrio, false, flagsExisting) + _, err = loadYgotStructIntoTreeRoot(ctx, running, root, consts.RunningIntentName, consts.RunningValuesPrio, false, flagsExisting) if err != nil { t.Fatal(err) } @@ -510,7 +515,7 @@ func Test_sharedEntryAttributes_GetDeviations(t *testing.T) { ).SetCurrentValue(testhelper.GetStringTvProto("hallo 0")).SetExpectedValue(testhelper.GetStringTvProto("hallo 00")), // three types.NewDeviationEntry( - RunningIntentName, + consts.RunningIntentName, types.DeviationReasonUnhandled, &sdcpb.Path{ Elem: []*sdcpb.PathElem{ @@ -522,7 +527,7 @@ func Test_sharedEntryAttributes_GetDeviations(t *testing.T) { ).SetCurrentValue(testhelper.GetStringTvProto("ethernet-1/3 description")).SetExpectedValue(nil), // four types.NewDeviationEntry( - RunningIntentName, + consts.RunningIntentName, types.DeviationReasonUnhandled, &sdcpb.Path{ Elem: []*sdcpb.PathElem{ @@ -870,7 +875,7 @@ func Test_sharedEntryAttributes_getOrCreateChilds(t *testing.T) { t.Fatal(err) } - x, err := root.getOrCreateChilds(ctx, tt.path) + x, err := root.GetOrCreateChilds(ctx, tt.path) if (err != nil) != tt.wantErr { t.Errorf("sharedEntryAttributes.getOrCreateChilds() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/pkg/tree/sorter.go b/pkg/tree/sorter.go index 54a6433d..06fc9fde 100644 --- a/pkg/tree/sorter.go +++ b/pkg/tree/sorter.go @@ -1,10 +1,13 @@ package tree -import "github.com/sdcio/data-server/pkg/tree/types" +import ( + "github.com/sdcio/data-server/pkg/tree/api" + "github.com/sdcio/data-server/pkg/tree/types" +) -func getListEntrySortFunc(parent Entry) func(a, b Entry) int { +func getListEntrySortFunc(parent api.Entry) func(a, b api.Entry) int { // return the comparison function - return func(a, b Entry) int { + return func(a, b api.Entry) int { keys := parent.GetSchemaKeys() var cmpResult int for _, v := range keys { @@ -16,8 +19,8 @@ func getListEntrySortFunc(parent Entry) func(a, b Entry) int { if !exists { return 0 } - aLvSlice := achild.GetHighestPrecedence(LeafVariantSlice{}, false, true, true) - bLvSlice := bchild.GetHighestPrecedence(LeafVariantSlice{}, false, true, true) + aLvSlice := achild.GetHighestPrecedence(api.LeafVariantSlice{}, false, true, true) + bLvSlice := bchild.GetHighestPrecedence(api.LeafVariantSlice{}, false, true, true) aEntry := aLvSlice[0] bEntry := bLvSlice[0] diff --git a/pkg/tree/tree_context.go b/pkg/tree/tree_context.go index e0297de5..d9c821b3 100644 --- a/pkg/tree/tree_context.go +++ b/pkg/tree/tree_context.go @@ -3,59 +3,49 @@ package tree import ( schemaClient "github.com/sdcio/data-server/pkg/datastore/clients/schema" "github.com/sdcio/data-server/pkg/pool" - sdcpb "github.com/sdcio/sdc-protos/sdcpb" + "github.com/sdcio/data-server/pkg/tree/api" ) type TreeContext struct { schemaClient schemaClient.SchemaClientBound - nonRevertiveInfo map[string]bool - explicitDeletes *DeletePathSet + nonRevertiveInfo api.NonRevertiveInfos + explicitDeletes *api.DeletePathSet poolFactory pool.VirtualPoolFactory } func NewTreeContext(sc schemaClient.SchemaClientBound, poolFactory pool.VirtualPoolFactory) *TreeContext { return &TreeContext{ schemaClient: sc, - nonRevertiveInfo: map[string]bool{}, - explicitDeletes: NewDeletePaths(), + nonRevertiveInfo: api.NewNonRevertiveInfos(), + explicitDeletes: api.NewDeletePaths(), poolFactory: poolFactory, } } // deepCopy root is required to be set manually -func (t *TreeContext) deepCopy() *TreeContext { +func (t *TreeContext) DeepCopy() api.TreeContext { tc := &TreeContext{ schemaClient: t.schemaClient, poolFactory: t.poolFactory, } - // deepcopy nonRevertiveInfo - m := make(map[string]bool, len(t.nonRevertiveInfo)) - for k, v := range t.nonRevertiveInfo { - m[k] = v - } - tc.nonRevertiveInfo = m + tc.nonRevertiveInfo = t.nonRevertiveInfo.DeepCopy() tc.explicitDeletes = t.explicitDeletes.DeepCopy() return tc } -func (t *TreeContext) GetPoolFactory() pool.VirtualPoolFactory { +func (t *TreeContext) PoolFactory() pool.VirtualPoolFactory { return t.poolFactory } -func (t *TreeContext) AddExplicitDeletes(intentName string, priority int32, pathset *sdcpb.PathSet) { - t.explicitDeletes.Add(intentName, priority, pathset) -} - -func (t *TreeContext) RemoveExplicitDeletes(intentName string) *sdcpb.PathSet { - return t.explicitDeletes.RemoveIntentDeletes(intentName) +func (t *TreeContext) SchemaClient() schemaClient.SchemaClientBound { + return t.schemaClient } -func (t *TreeContext) AddNonRevertiveInfo(intent string, nonRevertive bool) { - t.nonRevertiveInfo[intent] = nonRevertive +func (t *TreeContext) ExplicitDeletes() *api.DeletePathSet { + return t.explicitDeletes } -// IsNonRevertiveIntent returns the non-revertive flag per intent. False is also returned the intent does not exist. -func (t *TreeContext) IsNonRevertiveIntent(intent string) bool { - return t.nonRevertiveInfo[intent] +func (t *TreeContext) NonRevertiveInfo() api.NonRevertiveInfos { + return t.nonRevertiveInfo } diff --git a/pkg/tree/types/update.go b/pkg/tree/types/update.go index 6e27c786..a03245ae 100644 --- a/pkg/tree/types/update.go +++ b/pkg/tree/types/update.go @@ -89,7 +89,7 @@ func (u *Update) ValueAsBytes() ([]byte, error) { return proto.Marshal(u.value) } -func (u *Update) Path() *sdcpb.Path { +func (u *Update) SdcpbPath() *sdcpb.Path { if u.parent == nil { return nil } diff --git a/pkg/tree/types/update_slice.go b/pkg/tree/types/update_slice.go index 0d108a82..72a67423 100644 --- a/pkg/tree/types/update_slice.go +++ b/pkg/tree/types/update_slice.go @@ -14,7 +14,7 @@ type UpdateSlice []*Update func (u UpdateSlice) CopyWithNewOwnerAndPrio(owner string, prio int32) []*PathAndUpdate { result := make([]*PathAndUpdate, 0, len(u)) for _, x := range u { - result = append(result, NewPathAndUpdate(x.Path(), NewUpdate(nil, x.Value(), prio, owner, x.Timestamp()))) + result = append(result, NewPathAndUpdate(x.SdcpbPath(), NewUpdate(nil, x.Value(), prio, owner, x.Timestamp()))) } return result } @@ -22,7 +22,7 @@ func (u UpdateSlice) CopyWithNewOwnerAndPrio(owner string, prio int32) []*PathAn func (u UpdateSlice) String() string { sb := &strings.Builder{} for i, j := range u { - sb.WriteString(fmt.Sprintf("%d - %s -> %s\n", i, j.Path().ToXPath(false), j.value.ToString())) + sb.WriteString(fmt.Sprintf("%d - %s -> %s\n", i, j.SdcpbPath().ToXPath(false), j.value.ToString())) } return sb.String() } @@ -57,7 +57,7 @@ func (u UpdateSlice) GetLowestPriorityValue(filters []CacheUpdateFilter) int32 { func (u UpdateSlice) ToSdcpbPathSet() *sdcpb.PathSet { result := &sdcpb.PathSet{} for _, upd := range u { - result.AddPath(upd.Path()) + result.AddPath(upd.SdcpbPath()) } return result } @@ -65,7 +65,7 @@ func (u UpdateSlice) ToSdcpbPathSet() *sdcpb.PathSet { func (u UpdateSlice) ToPathAndUpdateSlice() []*PathAndUpdate { result := make([]*PathAndUpdate, 0, len(u)) for _, x := range u { - result = append(result, NewPathAndUpdate(x.Path(), x)) + result = append(result, NewPathAndUpdate(x.SdcpbPath(), x)) } return result } diff --git a/pkg/tree/utils.go b/pkg/tree/utils.go index 06edda8c..8f7ff05a 100644 --- a/pkg/tree/utils.go +++ b/pkg/tree/utils.go @@ -36,7 +36,7 @@ func loadYgotStructIntoTreeRoot(ctx context.Context, gs ygot.GoStruct, root *Roo stp := pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0)) importProcessor := NewImportConfigProcessor(jsonImporter.NewJsonTreeImporter(jsonConfAny, owner, prio, nonRevertive), flags) - err = importProcessor.Run(ctx, root.sharedEntryAttributes, stp) + err = importProcessor.Run(ctx, root.Entry, stp) if err != nil { return nil, err diff --git a/pkg/tree/validation_entry_leafref.go b/pkg/tree/validation_entry_leafref.go index 25a63c91..1bc4b541 100644 --- a/pkg/tree/validation_entry_leafref.go +++ b/pkg/tree/validation_entry_leafref.go @@ -5,23 +5,24 @@ import ( "fmt" "strings" + "github.com/sdcio/data-server/pkg/tree/api" "github.com/sdcio/data-server/pkg/tree/types" sdcpb "github.com/sdcio/sdc-protos/sdcpb" ) -func (s *sharedEntryAttributes) BreadthSearch(ctx context.Context, sdcpbPath *sdcpb.Path) ([]Entry, error) { +func (s *sharedEntryAttributes) BreadthSearch(ctx context.Context, sdcpbPath *sdcpb.Path) ([]api.Entry, error) { var err error - var resultEntries []Entry - var processEntries []Entry + var resultEntries []api.Entry + var processEntries []api.Entry sdcpbPath.StripPathElemPrefixPath() lrefPath := types.NewLrefPath(sdcpbPath) if sdcpbPath.GetIsRootBased() { - processEntries = []Entry{s.GetRoot()} + processEntries = []api.Entry{s.GetRoot()} } else { - var entry Entry = s + var entry api.Entry = s dotdotcount := 0 sdcpbUp := []*sdcpb.PathElem{} // process the .. instructions @@ -39,7 +40,7 @@ func (s *sharedEntryAttributes) BreadthSearch(ctx context.Context, sdcpbPath *sd if err != nil { return nil, err } - processEntries = []Entry{entry} + processEntries = []api.Entry{entry} lrefPath = lrefPath[dotdotcount:] } @@ -47,7 +48,7 @@ func (s *sharedEntryAttributes) BreadthSearch(ctx context.Context, sdcpbPath *sd // forward the path by a single element for _, elem := range lrefPath { - resultEntries = []Entry{} + resultEntries = []api.Entry{} err := s.resolve_leafref_key_path(ctx, elem.Keys) if err != nil { return nil, err @@ -70,7 +71,7 @@ func (s *sharedEntryAttributes) BreadthSearch(ctx context.Context, sdcpbPath *sd resultEntries = append(resultEntries, entry) continue } - var childs []Entry + var childs []api.Entry // if the entry is a list with keys, try filtering the entries based on the keys if len(entry.GetSchemaKeys()) > 0 { // filter the keys @@ -92,7 +93,7 @@ func (s *sharedEntryAttributes) BreadthSearch(ctx context.Context, sdcpbPath *sd } // NavigateLeafRef -func (s *sharedEntryAttributes) NavigateLeafRef(ctx context.Context) ([]Entry, error) { +func (s *sharedEntryAttributes) NavigateLeafRef(ctx context.Context) ([]api.Entry, error) { // leafref path takes as an argument a string that MUST refer to a leaf or leaf-list node. // e.g. @@ -133,10 +134,10 @@ func (s *sharedEntryAttributes) NavigateLeafRef(ctx context.Context) ([]Entry, e return nil, err } - var resultEntries []Entry + var resultEntries []api.Entry for _, e := range foundEntries { - r, err := e.getHighestPrecedenceLeafValue(ctx) + r, err := e.GetHighestPrecedenceLeafValue(ctx) if err != nil { return nil, err } @@ -188,7 +189,7 @@ func (s *sharedEntryAttributes) resolve_leafref_key_path(ctx context.Context, ke return err } - lvs := keyValue.GetHighestPrecedence(LeafVariantSlice{}, false, false, false) + lvs := keyValue.GetHighestPrecedence(api.LeafVariantSlice{}, false, false, false) if lvs == nil { return fmt.Errorf("no leafentry found") } @@ -200,7 +201,7 @@ func (s *sharedEntryAttributes) resolve_leafref_key_path(ctx context.Context, ke } func (s *sharedEntryAttributes) validateLeafRefs(ctx context.Context, resultChan chan<- *types.ValidationResultEntry, stats *types.ValidationStats) { - if s.shouldDelete() { + if s.ShouldDelete() { return } @@ -230,7 +231,7 @@ func (s *sharedEntryAttributes) validateLeafRefs(ctx context.Context, resultChan } // Only if the value remains, even after the SetIntent made it through, the LeafRef can be considered resolved. - if entry[0].shouldDelete() { + if entry[0].ShouldDelete() { lv := s.leafVariants.GetHighestPrecedence(false, true, false) if lv == nil { return @@ -248,8 +249,8 @@ func (s *sharedEntryAttributes) validateLeafRefs(ctx context.Context, resultChan stats.Add(types.StatTypeLeafRef, 1) } -func generateOptionalWarning(ctx context.Context, s Entry, lref string, resultChan chan<- *types.ValidationResultEntry) { - lrefval, err := s.getHighestPrecedenceLeafValue(ctx) +func generateOptionalWarning(ctx context.Context, s api.Entry, lref string, resultChan chan<- *types.ValidationResultEntry) { + lrefval, err := s.GetHighestPrecedenceLeafValue(ctx) if err != nil { resultChan <- types.NewValidationResultEntry(lrefval.Owner(), err, types.ValidationResultEntryTypeError) return diff --git a/pkg/tree/xml.go b/pkg/tree/xml.go index e568a8b8..feddf708 100644 --- a/pkg/tree/xml.go +++ b/pkg/tree/xml.go @@ -7,6 +7,7 @@ import ( "sort" "github.com/beevik/etree" + "github.com/sdcio/data-server/pkg/tree/api" "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils" sdcpb "github.com/sdcio/sdc-protos/sdcpb" @@ -18,19 +19,19 @@ import ( // If useOperationRemove is set, the remove operation will be used for deletes, instead of the delete operation. func (s *sharedEntryAttributes) ToXML(onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove bool) (*etree.Document, error) { doc := etree.NewDocument() - _, err := s.toXmlInternal(&doc.Element, onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove) + _, err := s.ToXmlInternal(&doc.Element, onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove) if err != nil { return nil, err } return doc, nil } -func (s *sharedEntryAttributes) toXmlInternal(parent *etree.Element, onlyNewOrUpdated bool, honorNamespace bool, operationWithNamespace bool, useOperationRemove bool) (doAdd bool, err error) { +func (s *sharedEntryAttributes) ToXmlInternal(parent *etree.Element, onlyNewOrUpdated bool, honorNamespace bool, operationWithNamespace bool, useOperationRemove bool) (doAdd bool, err error) { switch s.schema.GetSchema().(type) { case nil: // This case represents a key level element. So no schema present. all child attributes need to be adedd directly to the parent element, since the key levels are not visible in the resulting xml. - if s.shouldDelete() { + if s.ShouldDelete() { // If the element is to be deleted // add the delete operation to the parent element utils.AddXMLOperation(parent, utils.XMLOperationDelete, operationWithNamespace, useOperationRemove) @@ -76,7 +77,7 @@ func (s *sharedEntryAttributes) toXmlInternal(parent *etree.Element, onlyNewOrUp for _, k := range keys { // recurse the call // no additional element is created, since we're on a key level, so add to parent element - doAdd, err := childs[k].toXmlInternal(parent, onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove) + doAdd, err := childs[k].ToXmlInternal(parent, onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove) if err != nil { return false, err } @@ -107,7 +108,7 @@ func (s *sharedEntryAttributes) toXmlInternal(parent *etree.Element, onlyNewOrUp // process the honorNamespace instruction xmlAddNamespaceConditional(s, s.parent, newElem, honorNamespace) // recurse the call - doAdd, err := child.toXmlInternal(newElem, onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove) + doAdd, err := child.ToXmlInternal(newElem, onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove) if err != nil { return false, err } @@ -130,7 +131,7 @@ func (s *sharedEntryAttributes) toXmlInternal(parent *etree.Element, onlyNewOrUp } } return overallDoAdd, nil - case s.shouldDelete(): + case s.ShouldDelete(): // s is meant to be removed // if delete, create the element as child of parent newElem := parent.CreateElement(s.pathElemName) @@ -143,7 +144,7 @@ func (s *sharedEntryAttributes) toXmlInternal(parent *etree.Element, onlyNewOrUp // process presence containers with no childs if onlyNewOrUpdated { // presence containers have leafvariantes with typedValue_Empty, so check that - if s.leafVariants.shouldDelete() { + if s.leafVariants.ShouldDelete() { return false, nil } le := s.leafVariants.GetHighestPrecedence(false, false, false) @@ -191,7 +192,7 @@ func (s *sharedEntryAttributes) toXmlInternal(parent *etree.Element, onlyNewOrUp return false, fmt.Errorf("child %s does not exist for %s", k, s.SdcpbPath().ToXPath(false)) } // TODO: Do we also need to xmlAddAllChildValues here too? - doAdd, err := child.toXmlInternal(newElem, onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove) + doAdd, err := child.ToXmlInternal(newElem, onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove) if err != nil { return false, err } @@ -210,7 +211,7 @@ func (s *sharedEntryAttributes) toXmlInternal(parent *etree.Element, onlyNewOrUp case *sdcpb.SchemaElem_Leaflist, *sdcpb.SchemaElem_Field: // check if the element remains to exist - if s.shouldDelete() { + if s.ShouldDelete() { // if not, add the remove / delete op utils.AddXMLOperation(parent.CreateElement(s.pathElemName), utils.XMLOperationDelete, operationWithNamespace, useOperationRemove) // see case nil for an explanation of this, it is basically the same @@ -239,10 +240,10 @@ func (s *sharedEntryAttributes) toXmlInternal(parent *etree.Element, onlyNewOrUp // namespaceIsEqual takes the two given Entries, gets the namespace // and reports if both belong to the same namespace -func namespaceIsEqual(a Entry, b Entry) bool { +func namespaceIsEqual(a api.Entry, b api.Entry) bool { // store for the calculated namespaces namespaces := make([]string, 0, 2) - for _, e := range []Entry{a, b} { + for _, e := range []api.Entry{a, b} { // get schemas for a and b schema := e.GetSchema() @@ -261,7 +262,7 @@ func namespaceIsEqual(a Entry, b Entry) bool { } // xmlAddNamespaceConditional adds the namespace of a to elem if namespaces of a and b are different -func xmlAddNamespaceConditional(a Entry, b Entry, elem *etree.Element, honorNamespace bool) { +func xmlAddNamespaceConditional(a api.Entry, b api.Entry, elem *etree.Element, honorNamespace bool) { if honorNamespace && (b == nil || !namespaceIsEqual(a, b)) { elem.CreateAttr("xmlns", utils.GetNamespaceFromGetSchema(a.GetSchema())) } @@ -269,7 +270,7 @@ func xmlAddNamespaceConditional(a Entry, b Entry, elem *etree.Element, honorName // xmlAddKeyElements determines the keys of a certain Entry in the tree and adds those to the // element if they do not already exist. -func xmlAddKeyElements(s Entry, parent *etree.Element) { +func xmlAddKeyElements(s api.Entry, parent *etree.Element) { // retrieve the parent schema, we need to extract the key names // values are the tree level names parentSchema, levelsUp := s.GetFirstAncestorWithSchema() @@ -279,7 +280,7 @@ func xmlAddKeyElements(s Entry, parent *etree.Element) { //issue #364: sort the slice sort.Strings(schemaKeys) - var treeElem Entry = s + var treeElem api.Entry = s // the keys do match the levels up in the tree in reverse order // hence we init i with levelUp and count down for i := levelsUp - 1; i >= 0; i-- { @@ -295,9 +296,9 @@ func xmlAddKeyElements(s Entry, parent *etree.Element) { } } -func xmlAddAllChildValues(s Entry, parent *etree.Element, honorNamespace bool, operationWithNamespace bool, useOperationRemove bool) error { +func xmlAddAllChildValues(s api.Entry, parent *etree.Element, honorNamespace bool, operationWithNamespace bool, useOperationRemove bool) error { parent.Child = make([]etree.Token, 0) - _, err := s.toXmlInternal(parent, false, honorNamespace, operationWithNamespace, useOperationRemove) + _, err := s.ToXmlInternal(parent, false, honorNamespace, operationWithNamespace, useOperationRemove) if err != nil { return err } diff --git a/pkg/tree/xml_test.go b/pkg/tree/xml_test.go index 8084a4ff..b627f5f9 100644 --- a/pkg/tree/xml_test.go +++ b/pkg/tree/xml_test.go @@ -10,6 +10,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/openconfig/ygot/ygot" "github.com/sdcio/data-server/pkg/pool" + "github.com/sdcio/data-server/pkg/tree/consts" "github.com/sdcio/data-server/pkg/utils" "github.com/sdcio/data-server/pkg/utils/testhelper" sdcio_schema "github.com/sdcio/data-server/tests/sdcioygot" @@ -594,7 +595,7 @@ func TestToXMLTable(t *testing.T) { if err != nil { t.Error(err) } - err = addToRoot(ctx, root, runningUpds, flagsExisting, RunningIntentName, RunningValuesPrio) + err = addToRoot(ctx, root, runningUpds, flagsExisting, consts.RunningIntentName, consts.RunningValuesPrio) if err != nil { t.Fatal(err) } diff --git a/pkg/tree/yang-parser-adapter.go b/pkg/tree/yang-parser-adapter.go index 3268d37c..aa4d78f3 100644 --- a/pkg/tree/yang-parser-adapter.go +++ b/pkg/tree/yang-parser-adapter.go @@ -4,17 +4,18 @@ import ( "context" "fmt" + "github.com/sdcio/data-server/pkg/tree/api" sdcpb "github.com/sdcio/sdc-protos/sdcpb" "github.com/sdcio/yang-parser/xpath" "github.com/sdcio/yang-parser/xpath/xutils" ) type yangParserEntryAdapter struct { - e Entry + e api.Entry ctx context.Context } -func newYangParserEntryAdapter(ctx context.Context, e Entry) *yangParserEntryAdapter { +func newYangParserEntryAdapter(ctx context.Context, e api.Entry) *yangParserEntryAdapter { return &yangParserEntryAdapter{ e: e, ctx: ctx, @@ -73,7 +74,7 @@ func (y *yangParserEntryAdapter) GetValue() (xpath.Datum, error) { } // if y.e is anything else then a container - lv, _ := y.e.getHighestPrecedenceLeafValue(y.ctx) + lv, _ := y.e.GetHighestPrecedenceLeafValue(y.ctx) if lv == nil { return xpath.NewNodesetDatum([]xutils.XpathNode{}), nil } From ed87e065f38cb3bf837ed8a52ae511f469501b03 Mon Sep 17 00:00:00 2001 From: steiler Date: Wed, 18 Feb 2026 16:16:52 +0100 Subject: [PATCH 2/8] adjust makefile referencing path --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index dae01961..6cc7408a 100644 --- a/Makefile +++ b/Makefile @@ -63,7 +63,7 @@ mocks-gen: mocks-rm ## Generate mocks for all the defined interfaces. mockgen -package=mockcacheclient -source=pkg/cache/cache.go -destination=$(MOCKDIR)/mockcacheclient/client.go mockgen -package=mockcacheclient -source=pkg/cache/cacheClientBound.go -destination=$(MOCKDIR)/mockcacheclient/clientbound.go mockgen -package=mocktarget -source=pkg/datastore/target/target.go -destination=$(MOCKDIR)/mocktarget/target.go - mockgen -package=mockTreeEntry -source=pkg/tree/entry.go -destination=$(MOCKDIR)/mocktreeentry/entry.go + mockgen -package=mockTreeEntry -source=pkg/tree/api/entry.go -destination=$(MOCKDIR)/mocktreeentry/entry.go .PHONY: mocks-rm mocks-rm: ## remove generated mocks From efa72f3ab83ee688dff1344d861221051dc0e491 Mon Sep 17 00:00:00 2001 From: steiler Date: Thu, 19 Feb 2026 11:17:46 +0100 Subject: [PATCH 3/8] GetByOwner to ops and nonRevertiveInfo fix --- mocks/mocksdcpbpath/sdcpb_path.go | 55 +++++ pkg/datastore/transaction_rpc.go | 7 +- pkg/datastore/tree_operation_test.go | 2 - pkg/tree/api/entry.go | 2 +- pkg/tree/api/leaf_variant_slice.go | 11 +- pkg/tree/api/leaf_variants.go | 2 +- pkg/tree/api/non_revertive_infos.go | 11 +- pkg/tree/api/non_revertive_infos_test.go | 262 +++++++++++++++++++++ pkg/tree/entry_test.go | 262 ++++++++++++++++++++- pkg/tree/ops/get_by_owner.go | 31 +++ pkg/tree/ops/get_by_owner/get_by_owner.go | 22 -- pkg/tree/ops/interface/opsentry.go | 11 + pkg/tree/ops/validate.go | 7 + pkg/tree/processor_explicit_delete_test.go | 5 +- pkg/tree/root_entry.go | 4 +- pkg/tree/sharedEntryAttributes.go | 18 +- pkg/tree/sharedEntryAttributes_test.go | 4 +- 17 files changed, 645 insertions(+), 71 deletions(-) create mode 100644 mocks/mocksdcpbpath/sdcpb_path.go create mode 100644 pkg/tree/api/non_revertive_infos_test.go create mode 100644 pkg/tree/ops/get_by_owner.go delete mode 100644 pkg/tree/ops/get_by_owner/get_by_owner.go create mode 100644 pkg/tree/ops/interface/opsentry.go create mode 100644 pkg/tree/ops/validate.go diff --git a/mocks/mocksdcpbpath/sdcpb_path.go b/mocks/mocksdcpbpath/sdcpb_path.go new file mode 100644 index 00000000..7b694c9a --- /dev/null +++ b/mocks/mocksdcpbpath/sdcpb_path.go @@ -0,0 +1,55 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: pkg/tree/api/sdcpb_path.go +// +// Generated by this command: +// +// mockgen -package=mocksdcpbpath -source=pkg/tree/api/sdcpb_path.go -destination=./mocks/mocksdcpbpath/sdcpb_path.go +// + +// Package mocksdcpbpath is a generated GoMock package. +package mocksdcpbpath + +import ( + reflect "reflect" + + sdcpb "github.com/sdcio/sdc-protos/sdcpb" + gomock "go.uber.org/mock/gomock" +) + +// MockSdcpbPath is a mock of SdcpbPath interface. +type MockSdcpbPath struct { + ctrl *gomock.Controller + recorder *MockSdcpbPathMockRecorder + isgomock struct{} +} + +// MockSdcpbPathMockRecorder is the mock recorder for MockSdcpbPath. +type MockSdcpbPathMockRecorder struct { + mock *MockSdcpbPath +} + +// NewMockSdcpbPath creates a new mock instance. +func NewMockSdcpbPath(ctrl *gomock.Controller) *MockSdcpbPath { + mock := &MockSdcpbPath{ctrl: ctrl} + mock.recorder = &MockSdcpbPathMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSdcpbPath) EXPECT() *MockSdcpbPathMockRecorder { + return m.recorder +} + +// SdcpbPath mocks base method. +func (m *MockSdcpbPath) SdcpbPath() *sdcpb.Path { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SdcpbPath") + ret0, _ := ret[0].(*sdcpb.Path) + return ret0 +} + +// SdcpbPath indicates an expected call of SdcpbPath. +func (mr *MockSdcpbPathMockRecorder) SdcpbPath() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SdcpbPath", reflect.TypeOf((*MockSdcpbPath)(nil).SdcpbPath)) +} diff --git a/pkg/datastore/transaction_rpc.go b/pkg/datastore/transaction_rpc.go index 7792db43..e75d75a2 100644 --- a/pkg/datastore/transaction_rpc.go +++ b/pkg/datastore/transaction_rpc.go @@ -13,6 +13,7 @@ import ( "github.com/sdcio/data-server/pkg/tree/api" "github.com/sdcio/data-server/pkg/tree/consts" treeproto "github.com/sdcio/data-server/pkg/tree/importer/proto" + "github.com/sdcio/data-server/pkg/tree/ops" treetypes "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils" "github.com/sdcio/logger" @@ -216,8 +217,7 @@ func (d *Datastore) lowlevelTransactionSet(ctx context.Context, transaction *typ // iterate through all the intents for _, intent := range transaction.GetNewIntents() { // update the TreeContext to reflect the actual owner (intent name) - lvs := api.LeafVariantSlice{} - lvs = root.GetByOwner(intent.GetName(), lvs) + lvs := ops.GetByOwner(root, intent.GetName()) oldIntentContent := lvs.ToPathAndUpdateSlice() @@ -262,8 +262,7 @@ func (d *Datastore) lowlevelTransactionSet(ctx context.Context, transaction *typ root.SetNonRevertiveIntent(intent.GetName(), intent.NonRevertive()) } - les := api.LeafVariantSlice{} - les = root.GetByOwner(consts.RunningIntentName, les) + les := ops.GetByOwner(root, consts.RunningIntentName) transaction.GetOldRunning().AddUpdates(les.ToPathAndUpdateSlice()) diff --git a/pkg/datastore/tree_operation_test.go b/pkg/datastore/tree_operation_test.go index a3cb10b1..ea5a1009 100644 --- a/pkg/datastore/tree_operation_test.go +++ b/pkg/datastore/tree_operation_test.go @@ -1515,9 +1515,7 @@ func TestDatastore_populateTree(t *testing.T) { newFlag := types.NewUpdateInsertFlags().SetNewFlag() sharedPool := pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0)) - _, err = root.ImportConfig(ctx, tt.intentReqPath, jsonImporter.NewJsonTreeImporter(jsonConfAny, tt.intentName, tt.intentPrio, tt.nonRevertive), newFlag, sharedPool) - if err != nil { t.Error(err) } diff --git a/pkg/tree/api/entry.go b/pkg/tree/api/entry.go index 84785343..699eaa4d 100644 --- a/pkg/tree/api/entry.go +++ b/pkg/tree/api/entry.go @@ -35,7 +35,7 @@ type Entry interface { // will return an error if the Entry is not a Leaf GetHighestPrecedenceLeafValue(context.Context) (*LeafEntry, error) // GetByOwner returns the branches Updates by owner - GetByOwner(owner string, result []*LeafEntry) LeafVariantSlice + // GetByOwner(owner string, result []*LeafEntry) LeafVariantSlice // // markOwnerDelete Sets the delete flag on all the LeafEntries belonging to the given owner. // MarkOwnerDelete(o string, onlyIntended bool) // GetDeletes returns the cache-updates that are not updated, have no lower priority value left and hence should be deleted completely diff --git a/pkg/tree/api/leaf_variant_slice.go b/pkg/tree/api/leaf_variant_slice.go index b3ee1aea..a0a51712 100644 --- a/pkg/tree/api/leaf_variant_slice.go +++ b/pkg/tree/api/leaf_variant_slice.go @@ -19,15 +19,6 @@ func (lvs LeafVariantSlice) ToUpdateSlice() types.UpdateSlice { return result } -func (lvs LeafVariantSlice) GetByOwner(owner string) *LeafEntry { - for _, le := range lvs { - if le.Owner() == owner { - return le - } - } - return nil -} - func (lvs LeafVariantSlice) ToPathAndUpdateSlice() []*types.PathAndUpdate { result := make([]*types.PathAndUpdate, 0, len(lvs)) for _, x := range lvs { @@ -57,7 +48,7 @@ func (lvs LeafVariantSlice) Equal(otherLvs LeafVariantSlice) (bool, error) { return le1.Compare(le2) }) // sort otherLvs - slices.SortFunc(lvs, func(le1, le2 *LeafEntry) int { + slices.SortFunc(otherLvs, func(le1, le2 *LeafEntry) int { return le1.Compare(le2) }) diff --git a/pkg/tree/api/leaf_variants.go b/pkg/tree/api/leaf_variants.go index 8bce7bb0..0a3d84a5 100644 --- a/pkg/tree/api/leaf_variants.go +++ b/pkg/tree/api/leaf_variants.go @@ -382,7 +382,7 @@ func (lv *LeafVariants) highestIsUnequalRunning(highest *LeafEntry) bool { } // if highest is not new or updated and highest is non-revertive - if !highest.IsNew && !highest.IsUpdated && lv.tc.NonRevertiveInfo().IsGenerallyNonRevertive(highest.Update.Owner()) { + if !highest.IsNew && !highest.IsUpdated && lv.tc.NonRevertiveInfo().IsNonRevertive(highest.Update.Owner(), lv.parentEntry) { return false } diff --git a/pkg/tree/api/non_revertive_infos.go b/pkg/tree/api/non_revertive_infos.go index 60ce9cb1..b48fe758 100644 --- a/pkg/tree/api/non_revertive_infos.go +++ b/pkg/tree/api/non_revertive_infos.go @@ -1,5 +1,7 @@ package api +import "github.com/sdcio/sdc-protos/sdcpb" + type NonRevertiveInfos map[string]*NonRevertiveInfo func NewNonRevertiveInfos() NonRevertiveInfos { @@ -7,20 +9,19 @@ func NewNonRevertiveInfos() NonRevertiveInfos { } func (n NonRevertiveInfos) Add(owner string, nonRevertive bool) { - info, ok := n[owner] + _, ok := n[owner] if !ok { - info = NewNonRevertiveInfo(owner, nonRevertive) - n[owner] = info + n[owner] = NewNonRevertiveInfo(owner, nonRevertive) } } -func (n NonRevertiveInfos) AddNonRevertivePath(owner string, path SdcpbPath) { +func (n NonRevertiveInfos) AddNonRevertivePath(owner string, path *sdcpb.Path) { info, ok := n[owner] if !ok { info = NewNonRevertiveInfo(owner, false) n[owner] = info } - info.AddPath(path.SdcpbPath()) + info.AddPath(path) } func (n NonRevertiveInfos) IsNonRevertive(owner string, path SdcpbPath) bool { diff --git a/pkg/tree/api/non_revertive_infos_test.go b/pkg/tree/api/non_revertive_infos_test.go new file mode 100644 index 00000000..2bb9ac11 --- /dev/null +++ b/pkg/tree/api/non_revertive_infos_test.go @@ -0,0 +1,262 @@ +package api + +import ( + "testing" + + "github.com/sdcio/data-server/mocks/mocksdcpbpath" + sdcpb "github.com/sdcio/sdc-protos/sdcpb" + "go.uber.org/mock/gomock" +) + +func TestNewNonRevertiveInfos(t *testing.T) { + n := NewNonRevertiveInfos() + if n == nil { + t.Fatal("expected non-nil NonRevertiveInfos") + } + if len(n) != 0 { + t.Fatalf("expected empty map, got len=%d", len(n)) + } +} + +func TestNonRevertiveInfos_Add(t *testing.T) { + tests := []struct { + name string + adds []struct { + owner string + nonRevertive bool + } + checkOwner string + wantNonRevertive bool + }{ + { + name: "adds new entry as non-revertive", + adds: []struct { + owner string + nonRevertive bool + }{{"owner1", true}}, + checkOwner: "owner1", + wantNonRevertive: true, + }, + { + name: "does not overwrite existing entry", + adds: []struct { + owner string + nonRevertive bool + }{ + {"owner1", true}, + {"owner1", false}, // should be ignored + }, + checkOwner: "owner1", + wantNonRevertive: true, + }, + { + name: "adds new entry as revertive", + adds: []struct { + owner string + nonRevertive bool + }{{"owner1", false}}, + checkOwner: "owner1", + wantNonRevertive: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := NewNonRevertiveInfos() + for _, a := range tt.adds { + n.Add(a.owner, a.nonRevertive) + } + info, ok := n[tt.checkOwner] + if !ok { + t.Fatalf("expected %q to be present", tt.checkOwner) + } + if info.IsGenerallyNonRevertive() != tt.wantNonRevertive { + t.Errorf("IsGenerallyNonRevertive() = %v, want %v", info.IsGenerallyNonRevertive(), tt.wantNonRevertive) + } + }) + } +} + +func TestNonRevertiveInfos_AddNonRevertivePath(t *testing.T) { + tests := []struct { + name string + setup func(n NonRevertiveInfos) + owner string + path *sdcpb.Path + wantPresent bool + }{ + { + name: "creates entry if not exists", + setup: func(n NonRevertiveInfos) {}, + owner: "owner1", + path: &sdcpb.Path{Elem: []*sdcpb.PathElem{{Name: "interface"}}}, + wantPresent: true, + }, + { + name: "appends path to existing entry", + setup: func(n NonRevertiveInfos) { + n.Add("owner1", false) + }, + owner: "owner1", + path: &sdcpb.Path{Elem: []*sdcpb.PathElem{{Name: "interface"}}}, + wantPresent: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := NewNonRevertiveInfos() + tt.setup(n) + n.AddNonRevertivePath(tt.owner, tt.path) + _, ok := n[tt.owner] + if ok != tt.wantPresent { + t.Errorf("entry present = %v, want %v", ok, tt.wantPresent) + } + }) + } +} + +func TestNonRevertiveInfos_IsNonRevertive(t *testing.T) { + tests := []struct { + name string + setup func(n NonRevertiveInfos) + owner string + pathElems []*sdcpb.PathElem + wantResult bool + }{ + { + name: "unknown owner returns false", + setup: func(n NonRevertiveInfos) {}, + owner: "unknown", + pathElems: []*sdcpb.PathElem{{Name: "a"}}, + wantResult: false, + }, + { + name: "generally non-revertive owner with no paths", + setup: func(n NonRevertiveInfos) { n.Add("owner1", true) }, + owner: "owner1", + pathElems: []*sdcpb.PathElem{{Name: "a"}}, + wantResult: true, + }, + { + name: "generally revertive owner with no paths", + setup: func(n NonRevertiveInfos) { n.Add("owner1", false) }, + owner: "owner1", + pathElems: []*sdcpb.PathElem{{Name: "a"}}, + wantResult: false, + }, + { + name: "path outside revert paths is non-revertive", + setup: func(n NonRevertiveInfos) { + n.AddNonRevertivePath("owner1", &sdcpb.Path{Elem: []*sdcpb.PathElem{{Name: "interface"}}}) + }, + owner: "owner1", + pathElems: []*sdcpb.PathElem{{Name: "bgp"}}, + wantResult: true, + }, + { + name: "path under revert path is revertive", + setup: func(n NonRevertiveInfos) { + n.AddNonRevertivePath("owner1", &sdcpb.Path{Elem: []*sdcpb.PathElem{{Name: "interface"}}}) + }, + owner: "owner1", + pathElems: []*sdcpb.PathElem{{Name: "interface"}, {Name: "eth0"}}, + wantResult: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := NewNonRevertiveInfos() + tt.setup(n) + ctrl := gomock.NewController(t) + mockPath := mocksdcpbpath.NewMockSdcpbPath(ctrl) + mockPath.EXPECT().SdcpbPath().Return(&sdcpb.Path{Elem: tt.pathElems}).AnyTimes() + got := n.IsNonRevertive(tt.owner, mockPath) + if got != tt.wantResult { + t.Errorf("IsNonRevertive() = %v, want %v", got, tt.wantResult) + } + }) + } +} + +func TestNonRevertiveInfos_IsGenerallyNonRevertive(t *testing.T) { + tests := []struct { + name string + setup func(n NonRevertiveInfos) + owner string + wantResult bool + }{ + { + name: "unknown owner returns false", + setup: func(n NonRevertiveInfos) {}, + owner: "unknown", + wantResult: false, + }, + { + name: "true for generally non-revertive owner", + setup: func(n NonRevertiveInfos) { n.Add("owner1", true) }, + owner: "owner1", + wantResult: true, + }, + { + name: "false for generally revertive owner", + setup: func(n NonRevertiveInfos) { n.Add("owner1", false) }, + owner: "owner1", + wantResult: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := NewNonRevertiveInfos() + tt.setup(n) + got := n.IsGenerallyNonRevertive(tt.owner) + if got != tt.wantResult { + t.Errorf("IsGenerallyNonRevertive() = %v, want %v", got, tt.wantResult) + } + }) + } +} + +func TestNonRevertiveInfos_DeepCopy(t *testing.T) { + tests := []struct { + name string + setup func(n NonRevertiveInfos) + wantLen int + mutateKey string + wantInCopy bool + }{ + { + name: "copy is independent from original", + setup: func(n NonRevertiveInfos) { + n.Add("owner1", true) + n.Add("owner2", false) + }, + wantLen: 2, + mutateKey: "owner3", + wantInCopy: false, + }, + { + name: "empty map deep copy", + setup: func(n NonRevertiveInfos) {}, + wantLen: 0, + mutateKey: "owner1", + wantInCopy: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := NewNonRevertiveInfos() + tt.setup(n) + cp := n.DeepCopy() + if cp == nil { + t.Fatal("expected non-nil copy") + } + if len(cp) != tt.wantLen { + t.Fatalf("copy length = %d, want %d", len(cp), tt.wantLen) + } + n.Add(tt.mutateKey, true) + _, ok := cp[tt.mutateKey] + if ok != tt.wantInCopy { + t.Errorf("key %q in copy = %v, want %v", tt.mutateKey, ok, tt.wantInCopy) + } + }) + } +} diff --git a/pkg/tree/entry_test.go b/pkg/tree/entry_test.go index 858a0e44..cb08781f 100644 --- a/pkg/tree/entry_test.go +++ b/pkg/tree/entry_test.go @@ -2,6 +2,7 @@ package tree import ( "context" + "encoding/json" "reflect" "runtime" "slices" @@ -9,10 +10,14 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/openconfig/ygot/ygot" "github.com/sdcio/data-server/pkg/config" "github.com/sdcio/data-server/pkg/pool" "github.com/sdcio/data-server/pkg/tree/api" . "github.com/sdcio/data-server/pkg/tree/consts" + "github.com/sdcio/data-server/pkg/tree/importer" + jsonImporter "github.com/sdcio/data-server/pkg/tree/importer/json" + "github.com/sdcio/data-server/pkg/tree/ops" "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils/testhelper" sdcpb "github.com/sdcio/sdc-protos/sdcpb" @@ -209,8 +214,7 @@ func Test_Entry_One(t *testing.T) { t.Log(root.String()) t.Run("Test 1 - expected entries for owner1", func(t *testing.T) { - o1Le := []*api.LeafEntry{} - o1Le = root.GetByOwner(owner1, o1Le) + o1Le := ops.GetByOwner(root, owner1) o1 := api.LeafEntriesToUpdates(o1Le) // diff the result with the expected // if diff := testhelper.DiffUpdates([]*types.Update{u0o1, u2, u2_1, u1, u1_1}, o1); diff != "" { @@ -226,8 +230,7 @@ func Test_Entry_One(t *testing.T) { }) t.Run("Test 2 - expected entries for owner2", func(t *testing.T) { - o2Le := []*api.LeafEntry{} - o2Le = root.GetByOwner(owner2, o2Le) + o2Le := ops.GetByOwner(root, owner2) o2 := api.LeafEntriesToUpdates(o2Le) // diff the result with the expected if diff := testhelper.DiffUpdates([]*types.PathAndUpdate{ @@ -2028,3 +2031,254 @@ func Test_Validation_MultiKey_Pattern(t *testing.T) { }, ) } + +func Test_RevertNonRevertive(t *testing.T) { + + owner1 := "OwnerOne" + owner1Prio := int32(50) + owner2 := "OwnerTwo" + owner2Prio := int32(55) + + // create table test data + tests := []struct { + name string + running func() (importer.ImportConfigAdapter, error) + existing func() ([]importer.ImportConfigAdapter, error) + existingIsRevertive bool + revertPaths []*sdcpb.Path + expectedResult string + }{ + { + name: "revertive path with ignored key", + running: func() (importer.ImportConfigAdapter, error) { + c := config1() + + c.Interface["ethernet-1/1"].Description = ygot.String("test") + c.NetworkInstance["default"].Description = ygot.String("test") + + sConf, err := ygot.EmitJSON(c, &ygot.EmitJSONConfig{ + Format: ygot.RFC7951, + SkipValidation: false}, + ) + if err != nil { + return nil, err + } + + var jConf any + err = json.Unmarshal([]byte(sConf), &jConf) + return jsonImporter.NewJsonTreeImporter(jConf, RunningIntentName, RunningValuesPrio, false), err + }, + existing: func() ([]importer.ImportConfigAdapter, error) { + c := config1() + sConf, err := ygot.EmitJSON(c, &ygot.EmitJSONConfig{ + Format: ygot.RFC7951, + SkipValidation: false}, + ) + if err != nil { + return nil, err + } + + var jConf any + err = json.Unmarshal([]byte(sConf), &jConf) + return []importer.ImportConfigAdapter{jsonImporter.NewJsonTreeImporter(jConf, owner1, owner1Prio, true)}, err // this bool defines the revertive or non revertiveness + }, + existingIsRevertive: true, + revertPaths: []*sdcpb.Path{ + { + Elem: []*sdcpb.PathElem{sdcpb.NewPathElem("interface", nil)}, + IsRootBased: true, + }, + }, + expectedResult: `{"interface":[{"description":"Foo","name":"ethernet-1/1"}]}`, + }, + { + name: "revertive path /", + running: func() (importer.ImportConfigAdapter, error) { + c := config1() + + c.Interface["ethernet-1/1"].Description = ygot.String("test") + c.NetworkInstance["default"].Description = ygot.String("test") + + sConf, err := ygot.EmitJSON(c, &ygot.EmitJSONConfig{ + Format: ygot.RFC7951, + SkipValidation: false}, + ) + if err != nil { + return nil, err + } + + var jConf any + err = json.Unmarshal([]byte(sConf), &jConf) + return jsonImporter.NewJsonTreeImporter(jConf, RunningIntentName, RunningValuesPrio, false), err + }, + existing: func() ([]importer.ImportConfigAdapter, error) { + c := config1() + sConf, err := ygot.EmitJSON(c, &ygot.EmitJSONConfig{ + Format: ygot.RFC7951, + SkipValidation: false}, + ) + if err != nil { + return nil, err + } + + var jConf any + err = json.Unmarshal([]byte(sConf), &jConf) + return []importer.ImportConfigAdapter{jsonImporter.NewJsonTreeImporter(jConf, owner1, owner1Prio, true)}, err // this bool defines the revertive or non revertiveness + }, + existingIsRevertive: true, + revertPaths: []*sdcpb.Path{ + { + Elem: []*sdcpb.PathElem{}, + IsRootBased: true, + }, + }, + expectedResult: `{"interface":[{"description":"Foo","name":"ethernet-1/1"}],"network-instance":[{"description":"Default NI","name":"default"}]}`, + }, + { + name: "revertive path multiple intents, with ignored key", + running: func() (importer.ImportConfigAdapter, error) { + c := config1() + c.Patterntest = nil + err := ygot.MergeStructInto(c, config2()) + if err != nil { + return nil, err + } + c.Interface["ethernet-1/1"].Description = ygot.String("test") + c.NetworkInstance["default"].Description = ygot.String("test") + + sConf, err := ygot.EmitJSON(c, &ygot.EmitJSONConfig{ + Format: ygot.RFC7951, + SkipValidation: false}, + ) + if err != nil { + return nil, err + } + + var jConf any + err = json.Unmarshal([]byte(sConf), &jConf) + return jsonImporter.NewJsonTreeImporter(jConf, RunningIntentName, RunningValuesPrio, false), err + }, + existing: func() ([]importer.ImportConfigAdapter, error) { + c := config1() + + sConf, err := ygot.EmitJSON(c, &ygot.EmitJSONConfig{ + Format: ygot.RFC7951, + SkipValidation: false}, + ) + if err != nil { + return nil, err + } + + var jConf any + err = json.Unmarshal([]byte(sConf), &jConf) + + c1Importer := jsonImporter.NewJsonTreeImporter(jConf, owner1, owner1Prio, true) + + c2 := config2() + c2.Interface["ethernet-1/2"].Description = ygot.String("test") + sConf, err = ygot.EmitJSON(c2, &ygot.EmitJSONConfig{ + Format: ygot.RFC7951, + SkipValidation: false}, + ) + if err != nil { + return nil, err + } + + err = json.Unmarshal([]byte(sConf), &jConf) + c2Importer := jsonImporter.NewJsonTreeImporter(jConf, owner2, owner2Prio, true) + + return []importer.ImportConfigAdapter{c1Importer, c2Importer}, err // this bool defines the revertive or non revertiveness + }, + existingIsRevertive: true, + revertPaths: []*sdcpb.Path{ + { + Elem: []*sdcpb.PathElem{sdcpb.NewPathElem("interface", nil)}, + IsRootBased: true, + }, + }, + expectedResult: `{"interface":[{"description":"Foo","name":"ethernet-1/1"}]}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.TODO() + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + scb, err := testhelper.GetSchemaClientBound(t, mockCtrl) + if err != nil { + t.Fatal(err) + } + + tc := NewTreeContext(scb, pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0))) + + root, err := NewTreeRoot(ctx, tc) + if err != nil { + t.Fatal(err) + } + + runningConfig, err := tt.running() + if err != nil { + t.Fatalf("failed to get running config: %v", err) + } + + existingConfig, err := tt.existing() + if err != nil { + t.Fatalf("failed to get existing config: %v", err) + } + + _, err = root.ImportConfig(ctx, nil, runningConfig, types.NewUpdateInsertFlags(), tc.poolFactory) + if err != nil { + t.Fatalf("failed to import running config: %v", err) + } + + for _, existing := range existingConfig { + _, err = root.ImportConfig(ctx, nil, existing, types.NewUpdateInsertFlags(), tc.poolFactory) + if err != nil { + t.Fatalf("failed to import existing config: %v", err) + } + } + + // adding paths to the non revertive info, this should mark the paths as non revertive, and thus not be deleted in the end. + for _, path := range tt.revertPaths { + tc.NonRevertiveInfo().AddNonRevertivePath(owner1, path) + } + + err = root.FinishInsertionPhase(ctx) + if err != nil { + t.Fatalf("failed to finish insertion phase: %v", err) + } + + t.Logf("Tree:\n%s", root.String()) + + deletes, err := root.GetDeletes(true) + if err != nil { + t.Fatalf("failed to get deletes: %v", err) + } + t.Logf("Deletes: %v", deletes) + + j, err := root.ToJson(true) + if err != nil { + t.Fatalf("failed to convert to JSON: %v", err) + } + + jPlaneResult, err := json.Marshal(j) + if err != nil { + t.Fatalf("failed to marshal JSON: %v", err) + } + + if string(jPlaneResult) != tt.expectedResult { + t.Errorf("expected JSON result to be %s, but got %s", tt.expectedResult, string(jPlaneResult)) + } + + // logging + jResult, err := json.MarshalIndent(j, "", " ") + if err != nil { + t.Fatalf("failed to marshal JSON: %v", err) + } + t.Logf("Resulting JSON:\n%s", string(jResult)) + }) + } +} diff --git a/pkg/tree/ops/get_by_owner.go b/pkg/tree/ops/get_by_owner.go new file mode 100644 index 00000000..59d188c0 --- /dev/null +++ b/pkg/tree/ops/get_by_owner.go @@ -0,0 +1,31 @@ +package ops + +import ( + "github.com/sdcio/data-server/pkg/tree/api" + opsinterface "github.com/sdcio/data-server/pkg/tree/ops/interface" + + "github.com/sdcio/data-server/pkg/tree/types" +) + +// GetByOwner returns all the LeafEntries that belong to a certain owner. +func GetByOwner(e opsinterface.Entry, owner string) api.LeafVariantSlice { + result := api.LeafVariantSlice{} + result = getByOwnerInternal(e, owner, result) + return result +} + +// getByOwnerInternal is the internal function that performs the actual retrieval of the LeafEntries by owner. It is called recursively to traverse the tree. +// It takes an additional result parameter that is used to accumulate the results during the recursive traversal. Since that LeafVariantSlice might grow during the traversal, +// it is returned as a new slice to ensure that the changes are reflected in the caller. +func getByOwnerInternal(e opsinterface.Entry, owner string, result api.LeafVariantSlice) api.LeafVariantSlice { + lv := e.GetLeafVariants().GetByOwner(owner) + if lv != nil { + result = append(result, lv) + } + + // continue with childs + for _, c := range e.GetChilds(types.DescendMethodAll) { + result = getByOwnerInternal(c, owner, result) + } + return result +} diff --git a/pkg/tree/ops/get_by_owner/get_by_owner.go b/pkg/tree/ops/get_by_owner/get_by_owner.go deleted file mode 100644 index 178f64f1..00000000 --- a/pkg/tree/ops/get_by_owner/get_by_owner.go +++ /dev/null @@ -1,22 +0,0 @@ -package getbyowner - -import "github.com/sdcio/data-server/pkg/tree/api" - -// GetByOwner returns all the LeafEntries that belong to a certain owner. -func GetByOwner(e Entry, owner string, result []*api.LeafEntry) api.LeafVariantSlice { - lv := e.GetLeafVariants().GetByOwner(owner) - if lv != nil { - result = append(result, lv) - } - - // continue with childs - for _, c := range e.GetChilds() { - result = GetByOwner(c, owner, result) - } - return result -} - -type Entry interface { - GetLeafVariants() *api.LeafVariants - GetChilds() []Entry -} diff --git a/pkg/tree/ops/interface/opsentry.go b/pkg/tree/ops/interface/opsentry.go new file mode 100644 index 00000000..faa607bb --- /dev/null +++ b/pkg/tree/ops/interface/opsentry.go @@ -0,0 +1,11 @@ +package opsinterface + +import ( + "github.com/sdcio/data-server/pkg/tree/api" + "github.com/sdcio/data-server/pkg/tree/types" +) + +type Entry interface { + GetChilds(types.DescendMethod) api.EntryMap + GetLeafVariants() *api.LeafVariants +} diff --git a/pkg/tree/ops/validate.go b/pkg/tree/ops/validate.go new file mode 100644 index 00000000..3704f478 --- /dev/null +++ b/pkg/tree/ops/validate.go @@ -0,0 +1,7 @@ +package ops + +import opsinterface "github.com/sdcio/data-server/pkg/tree/ops/interface" + +func Validate(e opsinterface.Entry) error { + return nil +} diff --git a/pkg/tree/processor_explicit_delete_test.go b/pkg/tree/processor_explicit_delete_test.go index 12e3f17d..3c6aac6a 100644 --- a/pkg/tree/processor_explicit_delete_test.go +++ b/pkg/tree/processor_explicit_delete_test.go @@ -11,6 +11,7 @@ import ( "github.com/sdcio/data-server/pkg/pool" "github.com/sdcio/data-server/pkg/tree/api" "github.com/sdcio/data-server/pkg/tree/consts" + "github.com/sdcio/data-server/pkg/tree/ops" "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils/testhelper" sdcio_schema "github.com/sdcio/data-server/tests/sdcioygot" @@ -289,9 +290,7 @@ func TestExplicitDeleteVisitor_Visit(t *testing.T) { t.Log(root.String()) - lvs := api.LeafVariantSlice{} - lvs = root.GetByOwner(owner2, lvs) - + lvs := ops.GetByOwner(root, owner2) equal, err := lvs.Equal(tt.expectedLeafVariants) if err != nil { t.Error(err) diff --git a/pkg/tree/root_entry.go b/pkg/tree/root_entry.go index 6833c23c..1e4b9316 100644 --- a/pkg/tree/root_entry.go +++ b/pkg/tree/root_entry.go @@ -11,6 +11,7 @@ import ( "github.com/sdcio/data-server/pkg/pool" "github.com/sdcio/data-server/pkg/tree/api" "github.com/sdcio/data-server/pkg/tree/importer" + "github.com/sdcio/data-server/pkg/tree/ops" "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils" logf "github.com/sdcio/logger" @@ -199,7 +200,8 @@ func (r *RootEntry) TreeExport(owner string, priority int32) (*tree_persist.Inte func (r *RootEntry) getByOwnerFiltered(owner string, f ...api.LeafEntryFilter) []*api.LeafEntry { result := []*api.LeafEntry{} // retrieve all leafentries for the owner - leafEntries := r.Entry.GetByOwner(owner, result) + leafEntries := ops.GetByOwner(r.Entry, owner) + // range through entries NEXTELEMENT: for _, e := range leafEntries { diff --git a/pkg/tree/sharedEntryAttributes.go b/pkg/tree/sharedEntryAttributes.go index 798915cd..94497aa4 100644 --- a/pkg/tree/sharedEntryAttributes.go +++ b/pkg/tree/sharedEntryAttributes.go @@ -16,6 +16,7 @@ import ( "github.com/sdcio/data-server/pkg/config" "github.com/sdcio/data-server/pkg/tree/api" "github.com/sdcio/data-server/pkg/tree/consts" + "github.com/sdcio/data-server/pkg/tree/ops" "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils" logf "github.com/sdcio/logger" @@ -252,8 +253,7 @@ func (s *sharedEntryAttributes) checkAndCreateKeysAsLeafs(ctx context.Context, i // if the key Leaf exists continue with next key if entryExists { // if it exists, we need to check that the entry for the owner exists. - var result []*api.LeafEntry - lvs := child.GetByOwner(intentName, result) + lvs := ops.GetByOwner(child, intentName) if len(lvs) > 0 { lvs[0].DropDeleteFlag() // continue with parent Entry BEFORE continuing the loop @@ -709,20 +709,6 @@ func (s *sharedEntryAttributes) GetFirstAncestorWithSchema() (api.Entry, int) { return schema, level + 1 } -// GetByOwner returns all the LeafEntries that belong to a certain owner. -func (s *sharedEntryAttributes) GetByOwner(owner string, result []*api.LeafEntry) api.LeafVariantSlice { - lv := s.leafVariants.GetByOwner(owner) - if lv != nil { - result = append(result, lv) - } - - // continue with childs - for _, c := range s.childs.GetAll() { - result = c.GetByOwner(owner, result) - } - return result -} - // PathName returns the name of the Entry func (s *sharedEntryAttributes) PathName() string { return s.pathElemName diff --git a/pkg/tree/sharedEntryAttributes_test.go b/pkg/tree/sharedEntryAttributes_test.go index 829b25a1..4d7ecdc3 100644 --- a/pkg/tree/sharedEntryAttributes_test.go +++ b/pkg/tree/sharedEntryAttributes_test.go @@ -18,6 +18,7 @@ import ( "github.com/sdcio/data-server/pkg/tree/consts" jsonImporter "github.com/sdcio/data-server/pkg/tree/importer/json" "github.com/sdcio/data-server/pkg/tree/importer/proto" + "github.com/sdcio/data-server/pkg/tree/ops" "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils/testhelper" sdcio_schema "github.com/sdcio/data-server/tests/sdcioygot" @@ -291,8 +292,7 @@ func Test_sharedEntryAttributes_DeleteSubtree(t *testing.T) { t.Error(err) return } - les := []*api.LeafEntry{} - result := e.GetByOwner(tt.args.owner, les) + result := ops.GetByOwner(e, tt.args.owner) if len(result) > 0 { t.Errorf("expected all elements under %s to be deleted for owner %s but got %d elements", tt.args.relativePath.ToXPath(false), tt.args.owner, len(result)) return From 9bd0cfe675e81ccd6f663c1cf1742ac0f0d7ac9a Mon Sep 17 00:00:00 2001 From: steiler Date: Thu, 19 Feb 2026 15:22:06 +0100 Subject: [PATCH 4/8] add mock forsdcpbpath inteface --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 6cc7408a..bbe483ff 100644 --- a/Makefile +++ b/Makefile @@ -64,6 +64,7 @@ mocks-gen: mocks-rm ## Generate mocks for all the defined interfaces. mockgen -package=mockcacheclient -source=pkg/cache/cacheClientBound.go -destination=$(MOCKDIR)/mockcacheclient/clientbound.go mockgen -package=mocktarget -source=pkg/datastore/target/target.go -destination=$(MOCKDIR)/mocktarget/target.go mockgen -package=mockTreeEntry -source=pkg/tree/api/entry.go -destination=$(MOCKDIR)/mocktreeentry/entry.go + mockgen -package=mocksdcpbpath -source=pkg/tree/api/sdcpb_path.go -destination=$(MOCKDIR)/mocksdcpbpath/sdcpb_path.go .PHONY: mocks-rm mocks-rm: ## remove generated mocks From 3492c4b62546c81238c15b199b3907e046c479fc Mon Sep 17 00:00:00 2001 From: steiler Date: Tue, 24 Feb 2026 12:19:00 +0100 Subject: [PATCH 5/8] wire nonrevertive paths --- pkg/datastore/transaction_rpc.go | 62 +++++++++++++---------- pkg/datastore/types/transaction_intent.go | 10 ++++ pkg/tree/api/non_revertive_info.go | 5 +- pkg/tree/api/non_revertive_infos.go | 8 +-- pkg/tree/entry_test.go | 2 +- 5 files changed, 54 insertions(+), 33 deletions(-) diff --git a/pkg/datastore/transaction_rpc.go b/pkg/datastore/transaction_rpc.go index e75d75a2..dd6eac57 100644 --- a/pkg/datastore/transaction_rpc.go +++ b/pkg/datastore/transaction_rpc.go @@ -17,7 +17,6 @@ import ( treetypes "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils" "github.com/sdcio/logger" - logf "github.com/sdcio/logger" sdcpb "github.com/sdcio/sdc-protos/sdcpb" "github.com/sdcio/sdc-protos/tree_persist" "google.golang.org/protobuf/encoding/protojson" @@ -32,7 +31,7 @@ var ( // SdcpbTransactionIntentToInternalTI converts sdcpb.TransactionIntent to types.TransactionIntent func (d *Datastore) SdcpbTransactionIntentToInternalTI(ctx context.Context, req *sdcpb.TransactionIntent) (*types.TransactionIntent, error) { - + log := logger.FromContext(ctx) // create a new TransactionIntent with the given name and priority ti := types.NewTransactionIntent(req.GetIntent(), req.GetPriority()) @@ -43,9 +42,7 @@ func (d *Datastore) SdcpbTransactionIntentToInternalTI(ctx context.Context, req if req.GetOrphan() { ti.SetDeleteOnlyIntendedFlag() } - if req.GetNonRevertive() { - ti.SetNonRevertive() - } + if req.GetDeleteIgnoreNoExist() { ti.SetDeleteIgnoreNonExisting() } @@ -53,6 +50,16 @@ func (d *Datastore) SdcpbTransactionIntentToInternalTI(ctx context.Context, req ti.SetPreviouslyApplied() } + if req.GetNonRevertive() { + ti.SetNonRevertive() + } + if len(req.GetRevertPaths()) > 0 { + if !req.GetNonRevertive() { + log.Error(fmt.Errorf("revert paths provided without non-revertive flag set for intent %s", req.GetIntent()), "severity", "WARN") + } else { + ti.AddRevertPaths(req.GetRevertPaths()...) + } + } // convert the sdcpb.updates to tree.UpdateSlice Updates, err := treetypes.ExpandAndConvertIntent(ctx, d.schemaClient, req.GetIntent(), req.GetPriority(), req.GetUpdate(), time.Now().Unix()) if err != nil { @@ -71,8 +78,8 @@ func (d *Datastore) SdcpbTransactionIntentToInternalTI(ctx context.Context, req // replaceIntent takes a Transaction and treats it as a replaceIntent, replacing the whole device configuration with the content of the given intent. // returns the warnings as a []string and potential errors that happend during validation / from SBI Set() func (d *Datastore) replaceIntent(ctx context.Context, transaction *types.Transaction) ([]string, error) { - log := logf.FromContext(ctx).WithValues("transaction-type", "replace") - ctx = logf.IntoContext(ctx, log) + log := logger.FromContext(ctx).WithValues("transaction-type", "replace") + ctx = logger.IntoContext(ctx, log) // create a new TreeContext tc := tree.NewTreeContext(d.schemaClient, d.taskPool) @@ -103,14 +110,14 @@ func (d *Datastore) replaceIntent(ctx context.Context, transaction *types.Transa return nil, err } - log.V(logf.VDebug).Info("transaction finish tree insertion phase") + log.V(logger.VDebug).Info("transaction finish tree insertion phase") err = root.FinishInsertionPhase(ctx) if err != nil { return nil, err } // log the tree in trace level, making it a func call to spare overhead in lower log levels. - log.V(logf.VTrace).Info("populated tree", "tree", root.String()) + log.V(logger.VTrace).Info("populated tree", "tree", root.String()) // perform validation validationResult, validationStats := root.Validate(ctx, d.config.Validation, d.taskPool) @@ -150,7 +157,7 @@ func (d *Datastore) replaceIntent(ctx context.Context, transaction *types.Transa } func (d *Datastore) LoadAllButRunningIntents(ctx context.Context, root *tree.RootEntry) ([]string, error) { - log := logf.FromContext(ctx) + log := logger.FromContext(ctx) intentNames := []string{} IntentChan := make(chan *tree_persist.Intent) @@ -176,8 +183,8 @@ func (d *Datastore) LoadAllButRunningIntents(ctx context.Context, root *tree.Roo IntentChan = nil break selectLoop } - log.V(logf.VDebug).Info("adding intent to tree", "intent", intent.GetIntentName()) - log.V(logf.VTrace).Info("adding intent to tree", "intent", intent.GetIntentName(), "content", utils.FormatProtoJSON(intent)) + log.V(logger.VDebug).Info("adding intent to tree", "intent", intent.GetIntentName()) + log.V(logger.VTrace).Info("adding intent to tree", "intent", intent.GetIntentName(), "content", utils.FormatProtoJSON(intent)) intentNames = append(intentNames, intent.GetIntentName()) protoLoader := treeproto.NewProtoTreeImporter(intent) @@ -194,7 +201,7 @@ func (d *Datastore) LoadAllButRunningIntents(ctx context.Context, root *tree.Roo // lowlevelTransactionSet func (d *Datastore) lowlevelTransactionSet(ctx context.Context, transaction *types.Transaction, dryRun bool) (*sdcpb.TransactionSetResponse, error) { - log := logf.FromContext(ctx) + log := logger.FromContext(ctx) // create a new TreeRoot d.syncTreeMutex.RLock() root, err := d.syncTree.DeepCopy(ctx) @@ -214,6 +221,8 @@ func (d *Datastore) lowlevelTransactionSet(ctx context.Context, transaction *typ flagNew.SetNewFlag() flagExisting := treetypes.NewUpdateInsertFlags() + treeContext := root.GetTreeContext() + // iterate through all the intents for _, intent := range transaction.GetNewIntents() { // update the TreeContext to reflect the actual owner (intent name) @@ -256,29 +265,30 @@ func (d *Datastore) lowlevelTransactionSet(ctx context.Context, transaction *typ } // add the explicit delete entries - root.GetTreeContext().ExplicitDeletes().Add(intent.GetName(), intent.GetPriority(), intent.GetDeletes()) + treeContext.ExplicitDeletes().Add(intent.GetName(), intent.GetPriority(), intent.GetDeletes()) } - root.SetNonRevertiveIntent(intent.GetName(), intent.NonRevertive()) + // add non-revertive info to tree context + treeContext.NonRevertiveInfo().Add(intent.GetName(), intent.NonRevertive(), intent.GetRevertPaths()...) } les := ops.GetByOwner(root, consts.RunningIntentName) transaction.GetOldRunning().AddUpdates(les.ToPathAndUpdateSlice()) - log.V(logf.VDebug).Info("transaction finish tree insertion phase") + log.V(logger.VDebug).Info("transaction finish tree insertion phase") // FinishInsertion Phase err = root.FinishInsertionPhase(ctx) if err != nil { return nil, err } - log.V(logf.VTrace).Info("populated tree", "tree", root.String()) + log.V(logger.VTrace).Info("populated tree", "tree", root.String()) // perform validation validationResult, validationStats := root.Validate(ctx, d.config.Validation, d.taskPool) - log.V(logf.VDebug).Info("transaction validation stats", "transaction-id", transaction.GetTransactionId(), "stats", validationStats.String()) + log.V(logger.VDebug).Info("transaction validation stats", "transaction-id", transaction.GetTransactionId(), "stats", validationStats.String()) // prepare the response struct result := &sdcpb.TransactionSetResponse{ @@ -343,8 +353,8 @@ func (d *Datastore) lowlevelTransactionSet(ctx context.Context, transaction *typ // logging updStrSl := treetypes.Map(updates.ToUpdateSlice(), func(u *treetypes.Update) string { return u.String() }) - log.V(logf.VTrace).Info("generated updates", "updates", strings.Join(updStrSl, "\n")) - log.V(logf.VTrace).Info("generated deletes", "deletes", strings.Join(deletes.SdcpbPaths().ToXPathSlice(), "\n")) + log.V(logger.VTrace).Info("generated updates", "updates", strings.Join(updStrSl, "\n")) + log.V(logger.VTrace).Info("generated deletes", "deletes", strings.Join(deletes.SdcpbPaths().ToXPathSlice(), "\n")) for _, intent := range transaction.GetNewIntents() { log := log.WithValues("intent", intent.GetName()) @@ -354,10 +364,10 @@ func (d *Datastore) lowlevelTransactionSet(ctx context.Context, transaction *typ // logging strSl := treetypes.Map(updatesOwner, func(u *treetypes.Update) string { return u.String() }) - log.V(logf.VTrace).Info("updates owner", "updates-owner", strSl) + log.V(logger.VTrace).Info("updates owner", "updates-owner", strSl) delSl := deletesOwner.ToXPathSlice() - log.V(logf.VTrace).Info("deletes owner", "deletes-owner", delSl) + log.V(logger.VTrace).Info("deletes owner", "deletes-owner", delSl) protoIntent, err := root.TreeExport(intent.GetName(), intent.GetPriority()) switch { @@ -366,7 +376,7 @@ func (d *Datastore) lowlevelTransactionSet(ctx context.Context, transaction *typ if err != nil { log.Error(err, "failed deleting intent from store") } - log.V(logf.VDebug).Info("delete intent from cache") + log.V(logger.VDebug).Info("delete intent from cache") continue case err != nil: return nil, err @@ -449,7 +459,7 @@ func (d *Datastore) writeBackSyncTree(ctx context.Context, updates api.LeafVaria } func (d *Datastore) TransactionSet(ctx context.Context, transactionId string, transactionIntents []*types.TransactionIntent, replaceIntent *types.TransactionIntent, transactionTimeout time.Duration, dryRun bool) (*sdcpb.TransactionSetResponse, error) { - log := logf.FromContext(ctx) + log := logger.FromContext(ctx) var err error var transaction *types.Transaction var transactionGuard *types.TransactionGuard @@ -559,7 +569,7 @@ func updateToSdcpbUpdate(lvs api.LeafVariantSlice) ([]*sdcpb.Update, error) { } func (d *Datastore) TransactionConfirm(ctx context.Context, transactionId string) error { - log := logf.FromContext(ctx) + log := logger.FromContext(ctx) log.Info("transaction confirm") if !d.dmutex.TryLock() { @@ -571,7 +581,7 @@ func (d *Datastore) TransactionConfirm(ctx context.Context, transactionId string } func (d *Datastore) TransactionCancel(ctx context.Context, transactionId string) error { - log := logf.FromContext(ctx) + log := logger.FromContext(ctx) log.Info("transaction cancel") if !d.dmutex.TryLock() { diff --git a/pkg/datastore/types/transaction_intent.go b/pkg/datastore/types/transaction_intent.go index 674a7ec1..78137dec 100644 --- a/pkg/datastore/types/transaction_intent.go +++ b/pkg/datastore/types/transaction_intent.go @@ -17,6 +17,8 @@ type TransactionIntent struct { deleteIgnoreNonExisting bool explicitDeletes *sdcpb.PathSet previouslyApplied bool + // revertPaths paths that are meant to be reverted. This is used for non-revertive intents. + revertPaths []*sdcpb.Path } func NewTransactionIntent(name string, priority int32) *TransactionIntent { @@ -106,3 +108,11 @@ func (ti *TransactionIntent) AddExplicitDeletes(p []*sdcpb.Path) { ti.explicitDeletes.AddPath(x) } } + +func (ti *TransactionIntent) GetRevertPaths() []*sdcpb.Path { + return ti.revertPaths +} + +func (ti *TransactionIntent) AddRevertPaths(p ...*sdcpb.Path) { + ti.revertPaths = append(ti.revertPaths, p...) +} diff --git a/pkg/tree/api/non_revertive_info.go b/pkg/tree/api/non_revertive_info.go index f6e4ad10..147915b2 100644 --- a/pkg/tree/api/non_revertive_info.go +++ b/pkg/tree/api/non_revertive_info.go @@ -18,8 +18,9 @@ func NewNonRevertiveInfo(intentName string, nonRevertive bool) *NonRevertiveInfo } } -func (n *NonRevertiveInfo) AddPath(path *sdcpb.Path) { - n.revertPaths = append(n.revertPaths, path) +func (n *NonRevertiveInfo) AddPaths(path ...*sdcpb.Path) *NonRevertiveInfo { + n.revertPaths = append(n.revertPaths, path...) + return n } // IsGenerallyNonRevertive returns the general non-revertive state of the intent, which is true if the intent is non-revertive for all paths, false otherwise. diff --git a/pkg/tree/api/non_revertive_infos.go b/pkg/tree/api/non_revertive_infos.go index b48fe758..3707078c 100644 --- a/pkg/tree/api/non_revertive_infos.go +++ b/pkg/tree/api/non_revertive_infos.go @@ -8,20 +8,20 @@ func NewNonRevertiveInfos() NonRevertiveInfos { return make(map[string]*NonRevertiveInfo) } -func (n NonRevertiveInfos) Add(owner string, nonRevertive bool) { +func (n NonRevertiveInfos) Add(owner string, nonRevertive bool, paths ...*sdcpb.Path) { _, ok := n[owner] if !ok { - n[owner] = NewNonRevertiveInfo(owner, nonRevertive) + n[owner] = NewNonRevertiveInfo(owner, nonRevertive).AddPaths(paths...) } } -func (n NonRevertiveInfos) AddNonRevertivePath(owner string, path *sdcpb.Path) { +func (n NonRevertiveInfos) AddNonRevertivePaths(owner string, path ...*sdcpb.Path) { info, ok := n[owner] if !ok { info = NewNonRevertiveInfo(owner, false) n[owner] = info } - info.AddPath(path) + info.AddPaths(path...) } func (n NonRevertiveInfos) IsNonRevertive(owner string, path SdcpbPath) bool { diff --git a/pkg/tree/entry_test.go b/pkg/tree/entry_test.go index cb08781f..ca9fef69 100644 --- a/pkg/tree/entry_test.go +++ b/pkg/tree/entry_test.go @@ -2243,7 +2243,7 @@ func Test_RevertNonRevertive(t *testing.T) { // adding paths to the non revertive info, this should mark the paths as non revertive, and thus not be deleted in the end. for _, path := range tt.revertPaths { - tc.NonRevertiveInfo().AddNonRevertivePath(owner1, path) + tc.NonRevertiveInfo().AddNonRevertivePaths(owner1, path) } err = root.FinishInsertionPhase(ctx) From 651c4140dace368ea786338ae71ef78b393a2300 Mon Sep 17 00:00:00 2001 From: steiler Date: Tue, 24 Feb 2026 13:39:31 +0100 Subject: [PATCH 6/8] adjust go mod & fix test --- go.mod | 2 +- go.sum | 6 ++++-- pkg/tree/api/non_revertive_infos_test.go | 6 +++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 23656402..7778406e 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/sdcio/cache v0.0.38 github.com/sdcio/logger v0.0.3 github.com/sdcio/schema-server v0.0.34 - github.com/sdcio/sdc-protos v0.0.50 + github.com/sdcio/sdc-protos v0.0.51-0.20260224122709-ca10362f11ba github.com/sdcio/yang-parser v0.0.12 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 diff --git a/go.sum b/go.sum index cc73088d..8249811b 100644 --- a/go.sum +++ b/go.sum @@ -193,8 +193,10 @@ github.com/sdcio/logger v0.0.3 h1:IFUbObObGry+S8lHGwOQKKRxJSuOphgRU/hxVhOdMOM= github.com/sdcio/logger v0.0.3/go.mod h1:yWaOxK/G6vszjg8tKZiMqiEjlZouHsjFME4zSk+SAEA= github.com/sdcio/schema-server v0.0.34 h1:NNDOkvtUMONtBA7cVvN96F+FWGD/Do6HNqfchy9B8eI= github.com/sdcio/schema-server v0.0.34/go.mod h1:6t8HLXpqUqEJmE5yNZh29u/KZw0jlOICdNWns7zE4GE= -github.com/sdcio/sdc-protos v0.0.50 h1:aR6Av1QMTFNXKKxnrz8vlIXQ0y21lxQJEMut1oLd2Bg= -github.com/sdcio/sdc-protos v0.0.50/go.mod h1:huh1QVE023w+reU2Gt6h1f83R3lJidcFLbQje7cMY1M= +github.com/sdcio/sdc-protos v0.0.49 h1:GpLTDEyNnQxO5fjY7b8Y4xMP8f9kr4bbCHPJYVZkK00= +github.com/sdcio/sdc-protos v0.0.49/go.mod h1:huh1QVE023w+reU2Gt6h1f83R3lJidcFLbQje7cMY1M= +github.com/sdcio/sdc-protos v0.0.51-0.20260224122709-ca10362f11ba h1:WcWzmAzwXWYBvmbJRTbgFR55W30kx7TKBdLPpAndB7c= +github.com/sdcio/sdc-protos v0.0.51-0.20260224122709-ca10362f11ba/go.mod h1:huh1QVE023w+reU2Gt6h1f83R3lJidcFLbQje7cMY1M= github.com/sdcio/yang-parser v0.0.12 h1:RSSeqfAOIsJx5Lno5u4/ezyOmQYUduQ22rBfU/mtpJ4= github.com/sdcio/yang-parser v0.0.12/go.mod h1:CBqn3Miq85qmFVGHxHXHLluXkaIOsTzV06IM4DW6+D4= github.com/sirikothe/gotextfsm v1.0.1-0.20200816110946-6aa2cfd355e4 h1:FHUL2HofYJuslFOQdy/JjjP36zxqIpd/dcoiwLMIs7k= diff --git a/pkg/tree/api/non_revertive_infos_test.go b/pkg/tree/api/non_revertive_infos_test.go index 2bb9ac11..03f5bcef 100644 --- a/pkg/tree/api/non_revertive_infos_test.go +++ b/pkg/tree/api/non_revertive_infos_test.go @@ -105,7 +105,7 @@ func TestNonRevertiveInfos_AddNonRevertivePath(t *testing.T) { t.Run(tt.name, func(t *testing.T) { n := NewNonRevertiveInfos() tt.setup(n) - n.AddNonRevertivePath(tt.owner, tt.path) + n.AddNonRevertivePaths(tt.owner, tt.path) _, ok := n[tt.owner] if ok != tt.wantPresent { t.Errorf("entry present = %v, want %v", ok, tt.wantPresent) @@ -146,7 +146,7 @@ func TestNonRevertiveInfos_IsNonRevertive(t *testing.T) { { name: "path outside revert paths is non-revertive", setup: func(n NonRevertiveInfos) { - n.AddNonRevertivePath("owner1", &sdcpb.Path{Elem: []*sdcpb.PathElem{{Name: "interface"}}}) + n.AddNonRevertivePaths("owner1", &sdcpb.Path{Elem: []*sdcpb.PathElem{{Name: "interface"}}}) }, owner: "owner1", pathElems: []*sdcpb.PathElem{{Name: "bgp"}}, @@ -155,7 +155,7 @@ func TestNonRevertiveInfos_IsNonRevertive(t *testing.T) { { name: "path under revert path is revertive", setup: func(n NonRevertiveInfos) { - n.AddNonRevertivePath("owner1", &sdcpb.Path{Elem: []*sdcpb.PathElem{{Name: "interface"}}}) + n.AddNonRevertivePaths("owner1", &sdcpb.Path{Elem: []*sdcpb.PathElem{{Name: "interface"}}}) }, owner: "owner1", pathElems: []*sdcpb.PathElem{{Name: "interface"}, {Name: "eth0"}}, From a5f59f778b7b088a59bd76fab45f605641a349b1 Mon Sep 17 00:00:00 2001 From: steiler Date: Fri, 27 Feb 2026 10:46:48 +0100 Subject: [PATCH 7/8] reorg --- mocks/mocktreeentry/entry.go | 232 +---- pkg/datastore/datastore_rpc.go | 5 +- pkg/datastore/deviations.go | 3 +- pkg/datastore/intent_rpc.go | 36 +- pkg/datastore/sync.go | 21 +- pkg/datastore/sync_test.go | 5 +- pkg/datastore/target/gnmi/get.go | 5 +- pkg/datastore/target/gnmi/gnmi.go | 4 +- pkg/datastore/target/gnmi/stream.go | 5 +- pkg/datastore/target/netconf/nc.go | 2 +- pkg/datastore/target/types/targetsource.go | 6 +- pkg/datastore/transaction_rpc.go | 33 +- pkg/datastore/transaction_rpc_test.go | 5 +- pkg/datastore/tree_operation_test.go | 8 +- .../tree_operation_validation_test.go | 3 +- pkg/datastore/types/target_source_replace.go | 4 +- pkg/server/intent.go | 6 +- pkg/tree/api/adapter/entryoutputadapter.go | 40 + pkg/tree/api/adapter/intentresponse.go | 40 + pkg/tree/api/childMap.go | 115 +++ pkg/tree/{ => api}/childMap_test.go | 82 +- pkg/tree/api/entry.go | 88 +- pkg/tree/childMap.go | 104 -- pkg/tree/entry_list.go | 5 - pkg/tree/entry_test.go | 161 ++- .../proto/proto_tree_importer_test.go | 3 +- .../importer/xml/xml_tree_importer_test.go | 3 +- pkg/tree/init.go | 15 + pkg/tree/ops/checkandcreatekeysasleafs.go | 94 ++ pkg/tree/ops/containsonlydefaults.go | 46 + pkg/tree/{ => ops}/default_value.go | 2 +- pkg/tree/{ => ops}/default_value_test.go | 7 +- pkg/tree/ops/deletebranch.go | 69 ++ .../ops/{get_by_owner.go => getbyowner.go} | 5 +- pkg/tree/ops/getdeviations.go | 34 + pkg/tree/ops/getfirstancesterwithschema.go | 23 + pkg/tree/ops/getorcreatechilds.go | 116 +++ pkg/tree/ops/getroot.go | 11 + pkg/tree/ops/getrootbasedentrychain.go | 29 + pkg/tree/ops/getschemakeys.go | 20 + pkg/tree/ops/interface/opsentry.go | 11 - pkg/tree/{ => ops}/json.go | 53 +- pkg/tree/{ => ops}/json_test.go | 222 ++--- pkg/tree/ops/proto.go | 25 + pkg/tree/ops/treeexport.go | 102 ++ pkg/tree/{sorter.go => ops/utils.go} | 4 +- pkg/tree/ops/validate.go | 7 - .../validation}/processor_validate.go | 5 +- pkg/tree/ops/validation/validate.go | 401 ++++++++ .../validation}/validation_entry_leafref.go | 61 +- .../validation_entry_leafref_test.go | 31 +- .../validation}/validation_entry_must.go | 23 +- .../validation}/validation_range_test.go | 34 +- .../validation}/yang-parser-adapter.go | 6 +- pkg/tree/{ => ops}/xml.go | 107 +- pkg/tree/{ => ops}/xml_test.go | 150 +-- .../processor_blame_config.go | 2 +- .../processor_blame_config_test.go | 46 +- .../processor_error_collection_test.go | 2 +- .../processor_explicit_delete.go | 2 +- .../processor_explicit_delete_test.go | 43 +- .../{ => processors}/processor_importer.go | 10 +- .../processor_mark_owner_delete.go | 2 +- .../processor_remove_deleted.go | 2 +- .../{ => processors}/processor_reset_flags.go | 2 +- .../processor_reset_flags_test.go | 34 +- pkg/tree/proto.go | 21 - pkg/tree/root_entry.go | 80 +- pkg/tree/root_entry_test.go | 36 +- pkg/tree/sharedEntryAttributes.go | 916 ++---------------- pkg/tree/sharedEntryAttributes_test.go | 202 ++-- pkg/tree/utils.go | 45 - pkg/utils/testhelper/testhelpers.go | 169 ++++ 73 files changed, 2197 insertions(+), 2154 deletions(-) create mode 100644 pkg/tree/api/adapter/entryoutputadapter.go create mode 100644 pkg/tree/api/adapter/intentresponse.go create mode 100644 pkg/tree/api/childMap.go rename pkg/tree/{ => api}/childMap_test.go (53%) delete mode 100644 pkg/tree/childMap.go delete mode 100644 pkg/tree/entry_list.go create mode 100644 pkg/tree/init.go create mode 100644 pkg/tree/ops/checkandcreatekeysasleafs.go create mode 100644 pkg/tree/ops/containsonlydefaults.go rename pkg/tree/{ => ops}/default_value.go (99%) rename pkg/tree/{ => ops}/default_value_test.go (95%) create mode 100644 pkg/tree/ops/deletebranch.go rename pkg/tree/ops/{get_by_owner.go => getbyowner.go} (78%) create mode 100644 pkg/tree/ops/getdeviations.go create mode 100644 pkg/tree/ops/getfirstancesterwithschema.go create mode 100644 pkg/tree/ops/getorcreatechilds.go create mode 100644 pkg/tree/ops/getroot.go create mode 100644 pkg/tree/ops/getrootbasedentrychain.go create mode 100644 pkg/tree/ops/getschemakeys.go delete mode 100644 pkg/tree/ops/interface/opsentry.go rename pkg/tree/{ => ops}/json.go (70%) rename pkg/tree/{ => ops}/json_test.go (65%) create mode 100644 pkg/tree/ops/proto.go create mode 100644 pkg/tree/ops/treeexport.go rename pkg/tree/{sorter.go => ops/utils.go} (94%) delete mode 100644 pkg/tree/ops/validate.go rename pkg/tree/{ => ops/validation}/processor_validate.go (93%) create mode 100644 pkg/tree/ops/validation/validate.go rename pkg/tree/{ => ops/validation}/validation_entry_leafref.go (76%) rename pkg/tree/{ => ops/validation}/validation_entry_leafref_test.go (91%) rename pkg/tree/{ => ops/validation}/validation_entry_must.go (76%) rename pkg/tree/{ => ops/validation}/validation_range_test.go (86%) rename pkg/tree/{ => ops/validation}/yang-parser-adapter.go (97%) rename pkg/tree/{ => ops}/xml.go (71%) rename pkg/tree/{ => ops}/xml_test.go (81%) rename pkg/tree/{ => processors}/processor_blame_config.go (99%) rename pkg/tree/{ => processors}/processor_blame_config_test.go (86%) rename pkg/tree/{ => processors}/processor_error_collection_test.go (99%) rename pkg/tree/{ => processors}/processor_explicit_delete.go (99%) rename pkg/tree/{ => processors}/processor_explicit_delete_test.go (82%) rename pkg/tree/{ => processors}/processor_importer.go (96%) rename pkg/tree/{ => processors}/processor_mark_owner_delete.go (99%) rename pkg/tree/{ => processors}/processor_remove_deleted.go (99%) rename pkg/tree/{ => processors}/processor_reset_flags.go (99%) rename pkg/tree/{ => processors}/processor_reset_flags_test.go (79%) delete mode 100644 pkg/tree/proto.go delete mode 100644 pkg/tree/utils.go create mode 100644 pkg/utils/testhelper/testhelpers.go diff --git a/mocks/mocktreeentry/entry.go b/mocks/mocktreeentry/entry.go index 11f96f53..c3637e2b 100644 --- a/mocks/mocktreeentry/entry.go +++ b/mocks/mocktreeentry/entry.go @@ -13,12 +13,9 @@ import ( context "context" reflect "reflect" - etree "github.com/beevik/etree" - config "github.com/sdcio/data-server/pkg/config" api "github.com/sdcio/data-server/pkg/tree/api" types "github.com/sdcio/data-server/pkg/tree/types" sdcpb "github.com/sdcio/sdc-protos/sdcpb" - tree_persist "github.com/sdcio/sdc-protos/tree_persist" gomock "go.uber.org/mock/gomock" ) @@ -90,21 +87,6 @@ func (mr *MockEntryMockRecorder) AddUpdateRecursiveInternal(ctx, path, idx, u, f return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddUpdateRecursiveInternal", reflect.TypeOf((*MockEntry)(nil).AddUpdateRecursiveInternal), ctx, path, idx, u, flags) } -// BreadthSearch mocks base method. -func (m *MockEntry) BreadthSearch(ctx context.Context, path *sdcpb.Path) ([]api.Entry, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "BreadthSearch", ctx, path) - ret0, _ := ret[0].([]api.Entry) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// BreadthSearch indicates an expected call of BreadthSearch. -func (mr *MockEntryMockRecorder) BreadthSearch(ctx, path any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BreadthSearch", reflect.TypeOf((*MockEntry)(nil).BreadthSearch), ctx, path) -} - // CanDelete mocks base method. func (m *MockEntry) CanDelete() bool { m.ctrl.T.Helper() @@ -203,20 +185,6 @@ func (mr *MockEntryMockRecorder) FinishInsertionPhase(ctx any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FinishInsertionPhase", reflect.TypeOf((*MockEntry)(nil).FinishInsertionPhase), ctx) } -// GetByOwner mocks base method. -func (m *MockEntry) GetByOwner(owner string, result []*api.LeafEntry) api.LeafVariantSlice { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetByOwner", owner, result) - ret0, _ := ret[0].(api.LeafVariantSlice) - return ret0 -} - -// GetByOwner indicates an expected call of GetByOwner. -func (mr *MockEntryMockRecorder) GetByOwner(owner, result any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetByOwner", reflect.TypeOf((*MockEntry)(nil).GetByOwner), owner, result) -} - // GetChild mocks base method. func (m *MockEntry) GetChild(name string) (api.Entry, bool) { m.ctrl.T.Helper() @@ -232,6 +200,20 @@ func (mr *MockEntryMockRecorder) GetChild(name any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChild", reflect.TypeOf((*MockEntry)(nil).GetChild), name) } +// GetChildMap mocks base method. +func (m *MockEntry) GetChildMap() *api.ChildMap { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetChildMap") + ret0, _ := ret[0].(*api.ChildMap) + return ret0 +} + +// GetChildMap indicates an expected call of GetChildMap. +func (mr *MockEntryMockRecorder) GetChildMap() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChildMap", reflect.TypeOf((*MockEntry)(nil).GetChildMap)) +} + // GetChilds mocks base method. func (m *MockEntry) GetChilds(arg0 types.DescendMethod) api.EntryMap { m.ctrl.T.Helper() @@ -247,10 +229,10 @@ func (mr *MockEntryMockRecorder) GetChilds(arg0 any) *gomock.Call { } // GetDeletes mocks base method. -func (m *MockEntry) GetDeletes(entries []types.DeleteEntry, aggregatePaths bool) ([]types.DeleteEntry, error) { +func (m *MockEntry) GetDeletes(entries types.DeleteEntriesList, aggregatePaths bool) (types.DeleteEntriesList, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetDeletes", entries, aggregatePaths) - ret0, _ := ret[0].([]types.DeleteEntry) + ret0, _ := ret[0].(types.DeleteEntriesList) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -261,33 +243,6 @@ func (mr *MockEntryMockRecorder) GetDeletes(entries, aggregatePaths any) *gomock return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDeletes", reflect.TypeOf((*MockEntry)(nil).GetDeletes), entries, aggregatePaths) } -// GetDeviations mocks base method. -func (m *MockEntry) GetDeviations(ctx context.Context, ch chan<- *types.DeviationEntry, activeCase bool) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "GetDeviations", ctx, ch, activeCase) -} - -// GetDeviations indicates an expected call of GetDeviations. -func (mr *MockEntryMockRecorder) GetDeviations(ctx, ch, activeCase any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDeviations", reflect.TypeOf((*MockEntry)(nil).GetDeviations), ctx, ch, activeCase) -} - -// GetFirstAncestorWithSchema mocks base method. -func (m *MockEntry) GetFirstAncestorWithSchema() (api.Entry, int) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetFirstAncestorWithSchema") - ret0, _ := ret[0].(api.Entry) - ret1, _ := ret[1].(int) - return ret0, ret1 -} - -// GetFirstAncestorWithSchema indicates an expected call of GetFirstAncestorWithSchema. -func (mr *MockEntryMockRecorder) GetFirstAncestorWithSchema() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFirstAncestorWithSchema", reflect.TypeOf((*MockEntry)(nil).GetFirstAncestorWithSchema)) -} - // GetHighestPrecedence mocks base method. func (m *MockEntry) GetHighestPrecedence(result api.LeafVariantSlice, onlyNewOrUpdated, includeDefaults, includeExplicitDelete bool) api.LeafVariantSlice { m.ctrl.T.Helper() @@ -403,20 +358,6 @@ func (mr *MockEntryMockRecorder) GetParent() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetParent", reflect.TypeOf((*MockEntry)(nil).GetParent)) } -// GetRoot mocks base method. -func (m *MockEntry) GetRoot() api.Entry { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetRoot") - ret0, _ := ret[0].(api.Entry) - return ret0 -} - -// GetRoot indicates an expected call of GetRoot. -func (mr *MockEntryMockRecorder) GetRoot() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRoot", reflect.TypeOf((*MockEntry)(nil).GetRoot)) -} - // GetRootBasedEntryChain mocks base method. func (m *MockEntry) GetRootBasedEntryChain() []api.Entry { m.ctrl.T.Helper() @@ -501,21 +442,6 @@ func (mr *MockEntryMockRecorder) IsRoot() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsRoot", reflect.TypeOf((*MockEntry)(nil).IsRoot)) } -// NavigateLeafRef mocks base method. -func (m *MockEntry) NavigateLeafRef(ctx context.Context) ([]api.Entry, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NavigateLeafRef", ctx) - ret0, _ := ret[0].([]api.Entry) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// NavigateLeafRef indicates an expected call of NavigateLeafRef. -func (mr *MockEntryMockRecorder) NavigateLeafRef(ctx any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NavigateLeafRef", reflect.TypeOf((*MockEntry)(nil).NavigateLeafRef), ctx) -} - // NavigateSdcpbPath mocks base method. func (m *MockEntry) NavigateSdcpbPath(ctx context.Context, path *sdcpb.Path) (api.Entry, error) { m.ctrl.T.Helper() @@ -601,132 +527,6 @@ func (mr *MockEntryMockRecorder) StringIndent(result any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StringIndent", reflect.TypeOf((*MockEntry)(nil).StringIndent), result) } -// ToJson mocks base method. -func (m *MockEntry) ToJson(onlyNewOrUpdated bool) (any, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ToJson", onlyNewOrUpdated) - ret0, _ := ret[0].(any) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ToJson indicates an expected call of ToJson. -func (mr *MockEntryMockRecorder) ToJson(onlyNewOrUpdated any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToJson", reflect.TypeOf((*MockEntry)(nil).ToJson), onlyNewOrUpdated) -} - -// ToJsonIETF mocks base method. -func (m *MockEntry) ToJsonIETF(onlyNewOrUpdated bool) (any, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ToJsonIETF", onlyNewOrUpdated) - ret0, _ := ret[0].(any) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ToJsonIETF indicates an expected call of ToJsonIETF. -func (mr *MockEntryMockRecorder) ToJsonIETF(onlyNewOrUpdated any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToJsonIETF", reflect.TypeOf((*MockEntry)(nil).ToJsonIETF), onlyNewOrUpdated) -} - -// ToJsonInternal mocks base method. -func (m *MockEntry) ToJsonInternal(onlyNewOrUpdated, ietf bool) (any, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ToJsonInternal", onlyNewOrUpdated, ietf) - ret0, _ := ret[0].(any) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ToJsonInternal indicates an expected call of ToJsonInternal. -func (mr *MockEntryMockRecorder) ToJsonInternal(onlyNewOrUpdated, ietf any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToJsonInternal", reflect.TypeOf((*MockEntry)(nil).ToJsonInternal), onlyNewOrUpdated, ietf) -} - -// ToXML mocks base method. -func (m *MockEntry) ToXML(onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove bool) (*etree.Document, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ToXML", onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove) - ret0, _ := ret[0].(*etree.Document) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ToXML indicates an expected call of ToXML. -func (mr *MockEntryMockRecorder) ToXML(onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToXML", reflect.TypeOf((*MockEntry)(nil).ToXML), onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove) -} - -// ToXmlInternal mocks base method. -func (m *MockEntry) ToXmlInternal(parent *etree.Element, onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove bool) (bool, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ToXmlInternal", parent, onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove) - ret0, _ := ret[0].(bool) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ToXmlInternal indicates an expected call of ToXmlInternal. -func (mr *MockEntryMockRecorder) ToXmlInternal(parent, onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToXmlInternal", reflect.TypeOf((*MockEntry)(nil).ToXmlInternal), parent, onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove) -} - -// TreeExport mocks base method. -func (m *MockEntry) TreeExport(owner string) ([]*tree_persist.TreeElement, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "TreeExport", owner) - ret0, _ := ret[0].([]*tree_persist.TreeElement) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// TreeExport indicates an expected call of TreeExport. -func (mr *MockEntryMockRecorder) TreeExport(owner any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TreeExport", reflect.TypeOf((*MockEntry)(nil).TreeExport), owner) -} - -// ValidateLevel mocks base method. -func (m *MockEntry) ValidateLevel(ctx context.Context, resultChan chan<- *types.ValidationResultEntry, stats *types.ValidationStats, vCfg *config.Validation) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "ValidateLevel", ctx, resultChan, stats, vCfg) -} - -// ValidateLevel indicates an expected call of ValidateLevel. -func (mr *MockEntryMockRecorder) ValidateLevel(ctx, resultChan, stats, vCfg any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateLevel", reflect.TypeOf((*MockEntry)(nil).ValidateLevel), ctx, resultChan, stats, vCfg) -} - -// ValidateMandatory mocks base method. -func (m *MockEntry) ValidateMandatory(ctx context.Context, resultChan chan<- *types.ValidationResultEntry, stats *types.ValidationStats) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "ValidateMandatory", ctx, resultChan, stats) -} - -// ValidateMandatory indicates an expected call of ValidateMandatory. -func (mr *MockEntryMockRecorder) ValidateMandatory(ctx, resultChan, stats any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateMandatory", reflect.TypeOf((*MockEntry)(nil).ValidateMandatory), ctx, resultChan, stats) -} - -// ValidateMandatoryWithKeys mocks base method. -func (m *MockEntry) ValidateMandatoryWithKeys(ctx context.Context, level int, attributes []string, choiceName string, resultChan chan<- *types.ValidationResultEntry) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "ValidateMandatoryWithKeys", ctx, level, attributes, choiceName, resultChan) -} - -// ValidateMandatoryWithKeys indicates an expected call of ValidateMandatoryWithKeys. -func (mr *MockEntryMockRecorder) ValidateMandatoryWithKeys(ctx, level, attributes, choiceName, resultChan any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateMandatoryWithKeys", reflect.TypeOf((*MockEntry)(nil).ValidateMandatoryWithKeys), ctx, level, attributes, choiceName, resultChan) -} - // MockLeafVariantEntry is a mock of LeafVariantEntry interface. type MockLeafVariantEntry struct { ctrl *gomock.Controller diff --git a/pkg/datastore/datastore_rpc.go b/pkg/datastore/datastore_rpc.go index f1efe758..18ebafe9 100644 --- a/pkg/datastore/datastore_rpc.go +++ b/pkg/datastore/datastore_rpc.go @@ -35,6 +35,7 @@ import ( "github.com/sdcio/data-server/pkg/pool" "github.com/sdcio/data-server/pkg/schema" "github.com/sdcio/data-server/pkg/tree" + "github.com/sdcio/data-server/pkg/tree/processors" ) type Datastore struct { @@ -245,9 +246,9 @@ func (d *Datastore) BlameConfig(ctx context.Context, includeDefaults bool) (*sdc } blamePool := d.taskPool.NewVirtualPool(pool.VirtualFailFast) - bcp := tree.NewBlameConfigProcessor(tree.NewBlameConfigProcessorConfig(includeDefaults)) + bcp := processors.NewBlameConfigProcessor(processors.NewBlameConfigProcessorConfig(includeDefaults)) - bte, err := bcp.Run(ctx, root.GetRoot(), blamePool) + bte, err := bcp.Run(ctx, root.Entry, blamePool) // set the root level elements name to the target name bte.Name = d.config.Name diff --git a/pkg/datastore/deviations.go b/pkg/datastore/deviations.go index 54f56867..678d449e 100644 --- a/pkg/datastore/deviations.go +++ b/pkg/datastore/deviations.go @@ -5,6 +5,7 @@ import ( "time" "github.com/sdcio/data-server/pkg/config" + "github.com/sdcio/data-server/pkg/tree/ops" treetypes "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/logger" logf "github.com/sdcio/logger" @@ -185,7 +186,7 @@ func (d *Datastore) calculateDeviations(ctx context.Context) (<-chan *treetypes. deviationChan <- treetypes.NewDeviationEntry(n, treetypes.DeviationReasonIntentExists, nil) } - deviationTree.GetDeviations(ctx, deviationChan) + ops.GetDeviations(ctx, deviationTree.Entry, deviationChan, true) }() return deviationChan, nil diff --git a/pkg/datastore/intent_rpc.go b/pkg/datastore/intent_rpc.go index 60bfc800..ad345a02 100644 --- a/pkg/datastore/intent_rpc.go +++ b/pkg/datastore/intent_rpc.go @@ -23,6 +23,7 @@ import ( targettypes "github.com/sdcio/data-server/pkg/datastore/target/types" "github.com/sdcio/data-server/pkg/tree" + "github.com/sdcio/data-server/pkg/tree/api/adapter" "github.com/sdcio/data-server/pkg/tree/consts" "github.com/sdcio/data-server/pkg/tree/importer/proto" "github.com/sdcio/data-server/pkg/tree/types" @@ -63,7 +64,7 @@ func (d *Datastore) GetIntent(ctx context.Context, intentName string) (GetIntent return nil, err } - return newTreeRootToGetIntentResponse(d.syncTree), nil + return adapter.NewIntentResponseAdapter(d.syncTree.Entry), nil } // otherwise consult cache @@ -87,41 +88,16 @@ func (d *Datastore) GetIntent(ctx context.Context, intentName string) (GetIntent if err != nil { return nil, err } - return newTreeRootToGetIntentResponse(root), nil + return adapter.NewIntentResponseAdapter(root.Entry), nil } type GetIntentResponse interface { // ToJson returns the Tree contained structure as JSON // use e.g. json.MarshalIndent() on the returned struct - ToJson() (any, error) + ToJson(ctx context.Context) (any, error) // ToJsonIETF returns the Tree contained structure as JSON_IETF // use e.g. json.MarshalIndent() on the returned struct - ToJsonIETF() (any, error) - ToXML() (*etree.Document, error) + ToJsonIETF(ctx context.Context) (any, error) + ToXML(ctx context.Context) (*etree.Document, error) ToProtoUpdates(ctx context.Context) ([]*sdcpb.Update, error) } - -type treeRootToGetIntentResponse struct { - root *tree.RootEntry -} - -func newTreeRootToGetIntentResponse(root *tree.RootEntry) *treeRootToGetIntentResponse { - return &treeRootToGetIntentResponse{ - root: root, - } -} - -func (t *treeRootToGetIntentResponse) ToJson() (any, error) { - return t.root.ToJson(false) -} - -func (t *treeRootToGetIntentResponse) ToJsonIETF() (any, error) { - return t.root.ToJsonIETF(false) -} - -func (t *treeRootToGetIntentResponse) ToXML() (*etree.Document, error) { - return t.root.ToXML(false, true, false, false) -} -func (t *treeRootToGetIntentResponse) ToProtoUpdates(ctx context.Context) ([]*sdcpb.Update, error) { - return t.root.ToProtoUpdates(ctx, false) -} diff --git a/pkg/datastore/sync.go b/pkg/datastore/sync.go index c7ba8ecd..147583d0 100644 --- a/pkg/datastore/sync.go +++ b/pkg/datastore/sync.go @@ -5,8 +5,11 @@ import ( "sync" "github.com/sdcio/data-server/pkg/tree" + "github.com/sdcio/data-server/pkg/tree/api/adapter" "github.com/sdcio/data-server/pkg/tree/consts" "github.com/sdcio/data-server/pkg/tree/importer" + "github.com/sdcio/data-server/pkg/tree/ops" + "github.com/sdcio/data-server/pkg/tree/processors" treetypes "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/logger" sdcpb "github.com/sdcio/sdc-protos/sdcpb" @@ -31,7 +34,7 @@ func (d *Datastore) ApplyToRunning(ctx context.Context, deletes []*sdcpb.Path, i continue } // apply delete marker, setting owner delete flag on running intent - err = tree.NewOwnerDeleteMarker(tree.NewOwnerDeleteMarkerTaskConfig(consts.RunningIntentName, false)).Run(deleteRoot, d.taskPool) + err = processors.NewOwnerDeleteMarker(processors.NewOwnerDeleteMarkerTaskConfig(consts.RunningIntentName, false)).Run(deleteRoot, d.taskPool) if err != nil { log.Error(err, "failed applying delete to path", "path", delete.ToXPath(false)) continue @@ -47,15 +50,15 @@ func (d *Datastore) ApplyToRunning(ctx context.Context, deletes []*sdcpb.Path, i } // run remove deleted processor to clean up entries marked as deleted by owner - delProcessorParams := tree.NewRemoveDeletedProcessorParameters(consts.RunningIntentName) - err := tree.NewRemoveDeletedProcessor(delProcessorParams).Run(d.syncTree.GetRoot(), d.taskPool) + delProcessorParams := processors.NewRemoveDeletedProcessorParameters(consts.RunningIntentName) + err := processors.NewRemoveDeletedProcessor(delProcessorParams).Run(d.syncTree.Entry, d.taskPool) if err != nil { return err } // delete entries that have zero-length leaf variant entries after remove deleted processing for _, e := range delProcessorParams.GetZeroLengthLeafVariantEntries() { - err := e.GetParent().DeleteBranch(ctx, &sdcpb.Path{Elem: []*sdcpb.PathElem{sdcpb.NewPathElem(e.PathName(), nil)}}, consts.RunningIntentName) + err := ops.DeleteBranch(ctx, e.GetParent(), &sdcpb.Path{Elem: []*sdcpb.PathElem{sdcpb.NewPathElem(e.PathName(), nil)}}, consts.RunningIntentName) if err != nil { return err } @@ -63,7 +66,7 @@ func (d *Datastore) ApplyToRunning(ctx context.Context, deletes []*sdcpb.Path, i // conditional trace logging if log := log.V(logger.VTrace); log.Enabled() { - treeExport, err := d.syncTree.TreeExport(consts.RunningIntentName, consts.RunningValuesPrio) + treeExport, err := ops.TreeExport(d.syncTree.Entry, consts.RunningIntentName, consts.RunningValuesPrio) if err == nil { json, err := protojson.MarshalOptions{Multiline: false}.Marshal(treeExport) if err == nil { @@ -73,8 +76,8 @@ func (d *Datastore) ApplyToRunning(ctx context.Context, deletes []*sdcpb.Path, i } // run reset flags processor to reset flags - resetFlagsProcessorParams := tree.NewResetFlagsProcessorParameters().SetDeleteFlag().SetNewFlag().SetUpdateFlag() - err = tree.NewResetFlagsProcessor(resetFlagsProcessorParams).Run(d.syncTree.GetRoot(), d.taskPool) + resetFlagsProcessorParams := processors.NewResetFlagsProcessorParameters().SetDeleteFlag().SetNewFlag().SetUpdateFlag() + err = processors.NewResetFlagsProcessor(resetFlagsProcessorParams).Run(d.syncTree.Entry, d.taskPool) if err != nil { return err } @@ -131,7 +134,7 @@ func (d *Datastore) performRevert(ctx context.Context, t *tree.RootEntry) error // if no deletes, check if we have updates if !performApply { - updList, err := t.ToProtoUpdates(ctx, true) + updList, err := ops.ToProtoUpdates(ctx, t.Entry, true) if err != nil { return err } @@ -141,7 +144,7 @@ func (d *Datastore) performRevert(ctx context.Context, t *tree.RootEntry) error if performApply { log.Info("reverting after sync") - resp, err := d.applyIntent(ctx, t) + resp, err := d.applyIntent(ctx, adapter.NewEntryOutputAdapter(t.Entry)) if err != nil { respJ := protojson.MarshalOptions{Multiline: false} respStr, _ := respJ.Marshal(resp) diff --git a/pkg/datastore/sync_test.go b/pkg/datastore/sync_test.go index 23c202d4..2d2f91fc 100644 --- a/pkg/datastore/sync_test.go +++ b/pkg/datastore/sync_test.go @@ -19,6 +19,7 @@ import ( "github.com/sdcio/data-server/pkg/tree/consts" "github.com/sdcio/data-server/pkg/tree/importer" jsonImporter "github.com/sdcio/data-server/pkg/tree/importer/json" + "github.com/sdcio/data-server/pkg/tree/processors" "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils/testhelper" sdcio_schema "github.com/sdcio/data-server/tests/sdcioygot" @@ -413,8 +414,8 @@ func TestApplyToRunning(t *testing.T) { fmt.Println(syncTree.String()) - resetFlagsProcessorParams := tree.NewResetFlagsProcessorParameters().SetNewFlag().SetUpdateFlag() - err = tree.NewResetFlagsProcessor(resetFlagsProcessorParams).Run(syncTree.GetRoot(), datastore.taskPool) + resetFlagsProcessorParams := processors.NewResetFlagsProcessorParameters().SetNewFlag().SetUpdateFlag() + err = processors.NewResetFlagsProcessor(resetFlagsProcessorParams).Run(syncTree.Entry, datastore.taskPool) if err != nil { t.Fatalf("failed to reset flags: %v", err) } diff --git a/pkg/datastore/target/gnmi/get.go b/pkg/datastore/target/gnmi/get.go index 7779679c..eb32d726 100644 --- a/pkg/datastore/target/gnmi/get.go +++ b/pkg/datastore/target/gnmi/get.go @@ -11,6 +11,7 @@ import ( "github.com/sdcio/data-server/pkg/tree" "github.com/sdcio/data-server/pkg/tree/consts" "github.com/sdcio/data-server/pkg/tree/importer/proto" + "github.com/sdcio/data-server/pkg/tree/ops" treetypes "github.com/sdcio/data-server/pkg/tree/types" dsutils "github.com/sdcio/data-server/pkg/utils" "github.com/sdcio/logger" @@ -143,7 +144,7 @@ func (s *GetSync) internalGetSync(req *sdcpb.GetDataRequest) { return } - result, err := s.syncTree.TreeExport(consts.RunningIntentName, consts.RunningValuesPrio) + result, err := ops.TreeExport(s.syncTree.Entry, consts.RunningIntentName, consts.RunningValuesPrio) if err != nil { log.Error(err, "failure exporting synctree") return @@ -180,7 +181,7 @@ func (s *GetSync) processNotifications(n []*sdcpb.Notification) error { for idx2, upd := range upds { _ = idx2 - _, err = s.syncTree.AddUpdateRecursive(s.ctx, upd.GetPath(), upd.GetUpdate(), uif) + _, err = ops.AddUpdateRecursive(s.ctx, s.syncTree.Entry, upd.GetPath(), upd.GetUpdate(), uif) if err != nil { log.Error(err, "failure adding update to synctree") } diff --git a/pkg/datastore/target/gnmi/gnmi.go b/pkg/datastore/target/gnmi/gnmi.go index 5974eff2..117f8341 100644 --- a/pkg/datastore/target/gnmi/gnmi.go +++ b/pkg/datastore/target/gnmi/gnmi.go @@ -173,7 +173,7 @@ func (t *gnmiTarget) Set(ctx context.Context, source targetTypes.TargetSource) ( switch strings.ToLower(t.cfg.GnmiOptions.Encoding) { case "json": - jsonData, err := source.ToJson(true) + jsonData, err := source.ToJson(ctx, true) if err != nil { return nil, err } @@ -188,7 +188,7 @@ func (t *gnmiTarget) Set(ctx context.Context, source targetTypes.TargetSource) ( } case "json_ietf": - jsonData, err := source.ToJsonIETF(true) + jsonData, err := source.ToJsonIETF(ctx, true) if err != nil { return nil, err } diff --git a/pkg/datastore/target/gnmi/stream.go b/pkg/datastore/target/gnmi/stream.go index 0fa71541..ad5b9ab3 100644 --- a/pkg/datastore/target/gnmi/stream.go +++ b/pkg/datastore/target/gnmi/stream.go @@ -16,6 +16,7 @@ import ( "github.com/sdcio/data-server/pkg/tree" "github.com/sdcio/data-server/pkg/tree/consts" "github.com/sdcio/data-server/pkg/tree/importer/proto" + "github.com/sdcio/data-server/pkg/tree/ops" treetypes "github.com/sdcio/data-server/pkg/tree/types" dsutils "github.com/sdcio/data-server/pkg/utils" "github.com/sdcio/logger" @@ -222,11 +223,11 @@ func (s *StreamSync) syncToRunning(syncTree *tree.RootEntry, m *sync.Mutex, logC defer m.Unlock() startTime := time.Now() - result, err := syncTree.TreeExport(consts.RunningIntentName, consts.RunningValuesPrio) + result, err := ops.TreeExport(syncTree.Entry, consts.RunningIntentName, consts.RunningValuesPrio) log.V(logger.VTrace).Info("exported tree", "tree", result.String()) if err != nil { - if errors.Is(err, tree.ErrorIntentNotPresent) { + if errors.Is(err, ops.ErrorIntentNotPresent) { log.Info("sync no config changes") // all good no data present return syncTree, nil diff --git a/pkg/datastore/target/netconf/nc.go b/pkg/datastore/target/netconf/nc.go index 376b4853..3628dd28 100644 --- a/pkg/datastore/target/netconf/nc.go +++ b/pkg/datastore/target/netconf/nc.go @@ -238,7 +238,7 @@ func filterRPCErrors(xml *etree.Document, severity string) ([]string, error) { func (t *ncTarget) setToDevice(ctx context.Context, commitDatastore string, source types.TargetSource) (*sdcpb.SetDataResponse, error) { log := logf.FromContext(ctx).WithValues("commit-datastore", commitDatastore) - xtree, err := source.ToXML(true, t.sbiConfig.NetconfOptions.IncludeNS, t.sbiConfig.NetconfOptions.OperationWithNamespace, t.sbiConfig.NetconfOptions.UseOperationRemove) + xtree, err := source.ToXML(ctx, true, t.sbiConfig.NetconfOptions.IncludeNS, t.sbiConfig.NetconfOptions.OperationWithNamespace, t.sbiConfig.NetconfOptions.UseOperationRemove) if err != nil { return nil, err } diff --git a/pkg/datastore/target/types/targetsource.go b/pkg/datastore/target/types/targetsource.go index 69d66d95..5c73696d 100644 --- a/pkg/datastore/target/types/targetsource.go +++ b/pkg/datastore/target/types/targetsource.go @@ -10,11 +10,11 @@ import ( type TargetSource interface { // ToJson returns the Tree contained structure as JSON // use e.g. json.MarshalIndent() on the returned struct - ToJson(onlyNewOrUpdated bool) (any, error) + ToJson(ctx context.Context, onlyNewOrUpdated bool) (any, error) // ToJsonIETF returns the Tree contained structure as JSON_IETF // use e.g. json.MarshalIndent() on the returned struct - ToJsonIETF(onlyNewOrUpdated bool) (any, error) - ToXML(onlyNewOrUpdated bool, honorNamespace bool, operationWithNamespace bool, useOperationRemove bool) (*etree.Document, error) + ToJsonIETF(ctx context.Context, onlyNewOrUpdated bool) (any, error) + ToXML(ctx context.Context, onlyNewOrUpdated bool, honorNamespace bool, operationWithNamespace bool, useOperationRemove bool) (*etree.Document, error) ToProtoUpdates(ctx context.Context, onlyNewOrUpdated bool) ([]*sdcpb.Update, error) ToProtoDeletes(ctx context.Context) ([]*sdcpb.Path, error) } diff --git a/pkg/datastore/transaction_rpc.go b/pkg/datastore/transaction_rpc.go index dd6eac57..4ba4b01f 100644 --- a/pkg/datastore/transaction_rpc.go +++ b/pkg/datastore/transaction_rpc.go @@ -11,9 +11,12 @@ import ( "github.com/sdcio/data-server/pkg/datastore/types" "github.com/sdcio/data-server/pkg/tree" "github.com/sdcio/data-server/pkg/tree/api" + "github.com/sdcio/data-server/pkg/tree/api/adapter" "github.com/sdcio/data-server/pkg/tree/consts" treeproto "github.com/sdcio/data-server/pkg/tree/importer/proto" "github.com/sdcio/data-server/pkg/tree/ops" + "github.com/sdcio/data-server/pkg/tree/ops/validation" + "github.com/sdcio/data-server/pkg/tree/processors" treetypes "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils" "github.com/sdcio/logger" @@ -25,7 +28,7 @@ import ( var ( ErrDatastoreLocked = errors.New("datastore is locked, other action is ongoing") ErrContextDone = errors.New("context is closed (done)") - ErrValidationError = errors.New("validation error") + ErrValidation = errors.New("validation error") ErrNoIntentsProvided = errors.New("no intents provided") ) @@ -120,7 +123,7 @@ func (d *Datastore) replaceIntent(ctx context.Context, transaction *types.Transa log.V(logger.VTrace).Info("populated tree", "tree", root.String()) // perform validation - validationResult, validationStats := root.Validate(ctx, d.config.Validation, d.taskPool) + validationResult, validationStats := validation.Validate(ctx, root.Entry, d.config.Validation, d.taskPool) validationResult.ErrorsStr() if validationResult.HasErrors() { return nil, validationResult.JoinErrors() @@ -131,7 +134,7 @@ func (d *Datastore) replaceIntent(ctx context.Context, transaction *types.Transa // we use the TargetSourceReplace, that adjustes the tree results in a way // that the whole config tree is getting replaced. - replaceRoot := types.NewTargetSourceReplace(root) + replaceRoot := types.NewTargetSourceReplace(adapter.NewEntryOutputAdapter(root.Entry)) // apply the resulting config to the device dataResp, err := d.applyIntent(ctx, replaceRoot) @@ -226,13 +229,13 @@ func (d *Datastore) lowlevelTransactionSet(ctx context.Context, transaction *typ // iterate through all the intents for _, intent := range transaction.GetNewIntents() { // update the TreeContext to reflect the actual owner (intent name) - lvs := ops.GetByOwner(root, intent.GetName()) + lvs := ops.GetByOwner(root.Entry, intent.GetName()) oldIntentContent := lvs.ToPathAndUpdateSlice() - ownerDeleteMarker := tree.NewOwnerDeleteMarker(tree.NewOwnerDeleteMarkerTaskConfig(intent.GetName(), intent.GetOnlyIntended())) + ownerDeleteMarker := processors.NewOwnerDeleteMarker(processors.NewOwnerDeleteMarkerTaskConfig(intent.GetName(), intent.GetOnlyIntended())) - err := ownerDeleteMarker.Run(root.GetRoot(), d.taskPool) + err := ownerDeleteMarker.Run(root.Entry, d.taskPool) if err != nil { return nil, err } @@ -272,7 +275,7 @@ func (d *Datastore) lowlevelTransactionSet(ctx context.Context, transaction *typ treeContext.NonRevertiveInfo().Add(intent.GetName(), intent.NonRevertive(), intent.GetRevertPaths()...) } - les := ops.GetByOwner(root, consts.RunningIntentName) + les := ops.GetByOwner(root.Entry, consts.RunningIntentName) transaction.GetOldRunning().AddUpdates(les.ToPathAndUpdateSlice()) @@ -286,7 +289,7 @@ func (d *Datastore) lowlevelTransactionSet(ctx context.Context, transaction *typ log.V(logger.VTrace).Info("populated tree", "tree", root.String()) // perform validation - validationResult, validationStats := root.Validate(ctx, d.config.Validation, d.taskPool) + validationResult, validationStats := validation.Validate(ctx, root.Entry, d.config.Validation, d.taskPool) log.V(logger.VDebug).Info("transaction validation stats", "transaction-id", transaction.GetTransactionId(), "stats", validationStats.String()) @@ -327,7 +330,7 @@ func (d *Datastore) lowlevelTransactionSet(ctx context.Context, transaction *typ // Error out if validation failed. if validationResult.HasErrors() { - return result, ErrValidationError + return result, ErrValidation } log.Info("transaction validation passed") @@ -339,7 +342,7 @@ func (d *Datastore) lowlevelTransactionSet(ctx context.Context, transaction *typ } // apply the resulting config to the device - dataResp, err := d.applyIntent(ctx, root) + dataResp, err := d.applyIntent(ctx, adapter.NewEntryOutputAdapter(root.Entry)) if err != nil { return nil, err } @@ -369,9 +372,9 @@ func (d *Datastore) lowlevelTransactionSet(ctx context.Context, transaction *typ delSl := deletesOwner.ToXPathSlice() log.V(logger.VTrace).Info("deletes owner", "deletes-owner", delSl) - protoIntent, err := root.TreeExport(intent.GetName(), intent.GetPriority()) + protoIntent, err := ops.TreeExport(root.Entry, intent.GetName(), intent.GetPriority()) switch { - case errors.Is(err, tree.ErrorIntentNotPresent): + case errors.Is(err, ops.ErrorIntentNotPresent): err = d.cacheClient.IntentDelete(ctx, intent.GetName(), intent.GetDeleteIgnoreNonExisting()) if err != nil { log.Error(err, "failed deleting intent from store") @@ -435,8 +438,8 @@ func (d *Datastore) writeBackSyncTree(ctx context.Context, updates api.LeafVaria } // export the synctree - newRunningIntent, err := d.syncTree.TreeExport(consts.RunningIntentName, consts.RunningValuesPrio) - if err != nil && err != tree.ErrorIntentNotPresent { + newRunningIntent, err := ops.TreeExport(d.syncTree.Entry, consts.RunningIntentName, consts.RunningValuesPrio) + if err != nil && err != ops.ErrorIntentNotPresent { return err } @@ -534,7 +537,7 @@ func (d *Datastore) TransactionSet(ctx context.Context, transactionId string, tr response, err := d.lowlevelTransactionSet(ctx, transaction, dryRun) // if it is a validation error, we need to send the response while not successing the transaction guard // since validation errors are transported in the response itself, not in the seperate error - if errors.Is(err, ErrValidationError) { + if errors.Is(err, ErrValidation) { log.Error(fmt.Errorf("%s", strings.Join(response.GetErrors(), ", ")), "transaction validation failed") return response, nil } diff --git a/pkg/datastore/transaction_rpc_test.go b/pkg/datastore/transaction_rpc_test.go index 4f4d4227..a6ef5153 100644 --- a/pkg/datastore/transaction_rpc_test.go +++ b/pkg/datastore/transaction_rpc_test.go @@ -18,6 +18,7 @@ import ( "github.com/sdcio/data-server/pkg/tree" "github.com/sdcio/data-server/pkg/tree/consts" jsonImporter "github.com/sdcio/data-server/pkg/tree/importer/json" + "github.com/sdcio/data-server/pkg/tree/processors" treetypes "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils/testhelper" sdcio_schema "github.com/sdcio/data-server/tests/sdcioygot" @@ -157,8 +158,8 @@ func TestTransactionSet_PreviouslyApplied(t *testing.T) { t.Fatalf("failed to finish insertion phase: %v", err) } // Reset flags on syncTree so everything is "existing" - resetFlagsProcessorParams := tree.NewResetFlagsProcessorParameters().SetNewFlag().SetUpdateFlag() - err = tree.NewResetFlagsProcessor(resetFlagsProcessorParams).Run(syncTreeRoot.GetRoot(), vpf) + resetFlagsProcessorParams := processors.NewResetFlagsProcessorParameters().SetNewFlag().SetUpdateFlag() + err = processors.NewResetFlagsProcessor(resetFlagsProcessorParams).Run(syncTreeRoot.Entry, vpf) if err != nil { t.Fatalf("failed to reset flags: %v", err) } diff --git a/pkg/datastore/tree_operation_test.go b/pkg/datastore/tree_operation_test.go index ea5a1009..c531df96 100644 --- a/pkg/datastore/tree_operation_test.go +++ b/pkg/datastore/tree_operation_test.go @@ -29,6 +29,8 @@ import ( "github.com/sdcio/data-server/pkg/tree" "github.com/sdcio/data-server/pkg/tree/consts" jsonImporter "github.com/sdcio/data-server/pkg/tree/importer/json" + "github.com/sdcio/data-server/pkg/tree/ops/validation" + "github.com/sdcio/data-server/pkg/tree/processors" "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils/testhelper" sdcio_schema "github.com/sdcio/data-server/tests/sdcioygot" @@ -1505,8 +1507,8 @@ func TestDatastore_populateTree(t *testing.T) { } sharedTaskPool := pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0)) - ownerDeleteMarker := tree.NewOwnerDeleteMarker(tree.NewOwnerDeleteMarkerTaskConfig(tt.intentName, false)) - err = ownerDeleteMarker.Run(root.GetRoot(), sharedTaskPool) + ownerDeleteMarker := processors.NewOwnerDeleteMarker(processors.NewOwnerDeleteMarkerTaskConfig(tt.intentName, false)) + err = ownerDeleteMarker.Run(root.Entry, sharedTaskPool) if err != nil { t.Error(err) return @@ -1527,7 +1529,7 @@ func TestDatastore_populateTree(t *testing.T) { } fmt.Println(root.String()) - validationResult, _ := root.Validate(ctx, validationConfig, sharedPool) + validationResult, _ := validation.Validate(ctx, root.Entry, validationConfig, sharedPool) fmt.Printf("Validation Errors:\n%v\n", strings.Join(validationResult.ErrorsStr(), "\n")) fmt.Printf("Tree:%s\n", root.String()) diff --git a/pkg/datastore/tree_operation_validation_test.go b/pkg/datastore/tree_operation_validation_test.go index 88acd4ef..652658f5 100644 --- a/pkg/datastore/tree_operation_validation_test.go +++ b/pkg/datastore/tree_operation_validation_test.go @@ -28,6 +28,7 @@ import ( "github.com/sdcio/data-server/pkg/pool" "github.com/sdcio/data-server/pkg/tree" json_importer "github.com/sdcio/data-server/pkg/tree/importer/json" + "github.com/sdcio/data-server/pkg/tree/ops/validation" "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils/testhelper" sdcio_schema "github.com/sdcio/data-server/tests/sdcioygot" @@ -210,7 +211,7 @@ func TestDatastore_validateTree(t *testing.T) { } sharedPool := pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0)) - validationResult, _ := root.Validate(ctx, validationConfig, sharedPool) + validationResult, _ := validation.Validate(ctx, root.Entry, validationConfig, sharedPool) t.Log(root.String()) diff --git a/pkg/datastore/types/target_source_replace.go b/pkg/datastore/types/target_source_replace.go index eb79b5d7..e1d5082f 100644 --- a/pkg/datastore/types/target_source_replace.go +++ b/pkg/datastore/types/target_source_replace.go @@ -38,9 +38,9 @@ func (t *TargetSourceReplace) ToProtoDeletes(ctx context.Context) ([]*sdcpb.Path // ToXML in the XML case, we need to add the XMLReplace operation to the root element // So the call is forwarded to the original TargetSource, the attribute is added and returned to the caller -func (t *TargetSourceReplace) ToXML(onlyNewOrUpdated bool, honorNamespace bool, operationWithNamespace bool, useOperationRemove bool) (*etree.Document, error) { +func (t *TargetSourceReplace) ToXML(ctx context.Context, onlyNewOrUpdated bool, honorNamespace bool, operationWithNamespace bool, useOperationRemove bool) (*etree.Document, error) { // forward call to original TargetSource - et, err := t.TargetSource.ToXML(onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove) + et, err := t.TargetSource.ToXML(ctx, onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove) if err != nil { return nil, err } diff --git a/pkg/server/intent.go b/pkg/server/intent.go index cd6607fd..2140bd54 100644 --- a/pkg/server/intent.go +++ b/pkg/server/intent.go @@ -76,12 +76,12 @@ func (s *Server) GetIntent(ctx context.Context, req *sdcpb.GetIntentRequest) (*s var j any switch req.GetFormat() { case sdcpb.Format_Intent_Format_JSON: - j, err = rsp.ToJson() + j, err = rsp.ToJson(ctx) if err != nil { return nil, err } case sdcpb.Format_Intent_Format_JSON_IETF: - j, err = rsp.ToJsonIETF() + j, err = rsp.ToJsonIETF(ctx) if err != nil { return nil, err } @@ -99,7 +99,7 @@ func (s *Server) GetIntent(ctx context.Context, req *sdcpb.GetIntentRequest) (*s }, nil case sdcpb.Format_Intent_Format_XML: - doc, err := rsp.ToXML() + doc, err := rsp.ToXML(ctx) if err != nil { return nil, err } diff --git a/pkg/tree/api/adapter/entryoutputadapter.go b/pkg/tree/api/adapter/entryoutputadapter.go new file mode 100644 index 00000000..d6b8b6d6 --- /dev/null +++ b/pkg/tree/api/adapter/entryoutputadapter.go @@ -0,0 +1,40 @@ +package adapter + +import ( + "context" + + "github.com/beevik/etree" + "github.com/sdcio/data-server/pkg/tree/api" + "github.com/sdcio/data-server/pkg/tree/ops" + sdcpb "github.com/sdcio/sdc-protos/sdcpb" +) + +type EntryOutputAdapter struct { + entry api.Entry +} + +func NewEntryOutputAdapter(e api.Entry) *EntryOutputAdapter { + return &EntryOutputAdapter{ + entry: e, + } +} + +func (t *EntryOutputAdapter) ToJson(ctx context.Context, onlyNewOrUpdated bool) (any, error) { + return ops.ToJson(ctx, t.entry, onlyNewOrUpdated) +} + +func (t *EntryOutputAdapter) ToJsonIETF(ctx context.Context, onlyNewOrUpdated bool) (any, error) { + return ops.ToJsonIETF(ctx, t.entry, onlyNewOrUpdated) +} + +func (t *EntryOutputAdapter) ToXML(ctx context.Context, onlyNewOrUpdated bool, honorNamespace bool, operationWithNamespace bool, useOperationRemove bool) (*etree.Document, error) { + return ops.ToXML(ctx, t.entry, onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove) +} + +func (t *EntryOutputAdapter) ToProtoUpdates(ctx context.Context, onlyNewOrUpdated bool) ([]*sdcpb.Update, error) { + return ops.ToProtoUpdates(ctx, t.entry, onlyNewOrUpdated) +} + +func (t *EntryOutputAdapter) ToProtoDeletes(ctx context.Context) ([]*sdcpb.Path, error) { + return ops.ToProtoDeletes(ctx, t.entry) +} diff --git a/pkg/tree/api/adapter/intentresponse.go b/pkg/tree/api/adapter/intentresponse.go new file mode 100644 index 00000000..7ab9f1fd --- /dev/null +++ b/pkg/tree/api/adapter/intentresponse.go @@ -0,0 +1,40 @@ +package adapter + +import ( + "context" + + "github.com/beevik/etree" + "github.com/sdcio/data-server/pkg/tree/api" + "github.com/sdcio/data-server/pkg/tree/ops" + sdcpb "github.com/sdcio/sdc-protos/sdcpb" +) + +type IntentResponseAdapter struct { + entry api.Entry +} + +func NewIntentResponseAdapter(e api.Entry) *IntentResponseAdapter { + return &IntentResponseAdapter{ + entry: e, + } +} + +func (t *IntentResponseAdapter) ToJson(ctx context.Context) (any, error) { + return ops.ToJson(ctx, t.entry, false) +} + +func (t *IntentResponseAdapter) ToJsonIETF(ctx context.Context) (any, error) { + return ops.ToJsonIETF(ctx, t.entry, false) +} + +func (t *IntentResponseAdapter) ToXML(ctx context.Context) (*etree.Document, error) { + return ops.ToXML(ctx, t.entry, false, true, false, false) +} + +func (t *IntentResponseAdapter) ToProtoUpdates(ctx context.Context) ([]*sdcpb.Update, error) { + return ops.ToProtoUpdates(ctx, t.entry, false) +} + +func (t *IntentResponseAdapter) ToProtoDeletes(ctx context.Context) ([]*sdcpb.Path, error) { + return ops.ToProtoDeletes(ctx, t.entry) +} diff --git a/pkg/tree/api/childMap.go b/pkg/tree/api/childMap.go new file mode 100644 index 00000000..8721fc11 --- /dev/null +++ b/pkg/tree/api/childMap.go @@ -0,0 +1,115 @@ +package api + +import ( + "maps" + "sort" + "sync" +) + +type ChildMap struct { + c map[string]Entry + mu sync.RWMutex +} + +func NewChildMap() *ChildMap { + return &ChildMap{ + c: map[string]Entry{}, + } +} + +// NewChildMapWithEntries creates a ChildMap with initial entries (for testing) +func NewChildMapWithEntries(entries map[string]Entry) *ChildMap { + c := &ChildMap{ + c: make(map[string]Entry, len(entries)), + } + for k, v := range entries { + c.c[k] = v + } + return c +} + +func (c *ChildMap) DeleteChilds(names []string) { + c.mu.Lock() + defer c.mu.Unlock() + for _, name := range names { + delete(c.c, name) + } +} + +func (c *ChildMap) DeleteChild(name string) { + c.mu.Lock() + defer c.mu.Unlock() + delete(c.c, name) +} + +func (c *ChildMap) Add(e Entry) { + c.mu.Lock() + defer c.mu.Unlock() + c.c[e.PathName()] = e +} + +func (c *ChildMap) GetEntry(s string) (e Entry, exists bool) { + c.mu.RLock() + defer c.mu.RUnlock() + e, exists = c.c[s] + return e, exists +} + +func (c *ChildMap) GetAllSorted() []Entry { + c.mu.RLock() + defer c.mu.RUnlock() + + childNames := c.SortedKeys() + result := make([]Entry, 0, len(c.c)) + // range over children + for _, childName := range childNames { + result = append(result, c.c[childName]) + } + + return result +} + +// ForEach iterates over all children while holding a read lock. +func (c *ChildMap) ForEach(fn func(name string, e Entry)) { + c.mu.RLock() + defer c.mu.RUnlock() + for name, child := range c.c { + fn(name, child) + } +} + +// GetAll returns a copy of the map of all entries in the child map +func (c *ChildMap) GetAll() map[string]Entry { + c.mu.RLock() + defer c.mu.RUnlock() + + result := make(map[string]Entry, len(c.c)) + maps.Copy(result, c.c) + return result +} + +// GetKeys returns the keys of the child map +func (c *ChildMap) GetKeys() []string { + c.mu.RLock() + defer c.mu.RUnlock() + + result := make([]string, 0, c.Length()) + for k := range c.c { + result = append(result, k) + } + return result +} + +// Length returns the number of entries in the child map +func (c *ChildMap) Length() int { + c.mu.RLock() + defer c.mu.RUnlock() + return len(c.c) +} + +// SortedKeys returns the keys of the child map in sorted order +func (c *ChildMap) SortedKeys() []string { + keys := c.GetKeys() + sort.Strings(keys) + return keys +} diff --git a/pkg/tree/childMap_test.go b/pkg/tree/api/childMap_test.go similarity index 53% rename from pkg/tree/childMap_test.go rename to pkg/tree/api/childMap_test.go index 430290ee..7ac13d12 100644 --- a/pkg/tree/childMap_test.go +++ b/pkg/tree/api/childMap_test.go @@ -1,12 +1,12 @@ -package tree +package api_test import ( - "sync" "testing" "github.com/sdcio/data-server/pkg/tree/api" ) + func Test_childMap_DeleteChilds(t *testing.T) { type fields struct { c map[string]api.Entry @@ -24,15 +24,9 @@ func Test_childMap_DeleteChilds(t *testing.T) { name: "Delete single entry", fields: fields{ c: map[string]api.Entry{ - "one": &sharedEntryAttributes{ - pathElemName: "one", - }, - "two": &sharedEntryAttributes{ - pathElemName: "two", - }, - "three": &sharedEntryAttributes{ - pathElemName: "three", - }, + "one": nil, + "two": nil, + "three": nil, }, }, args: args{ @@ -44,15 +38,9 @@ func Test_childMap_DeleteChilds(t *testing.T) { name: "Delete two entries", fields: fields{ c: map[string]api.Entry{ - "one": &sharedEntryAttributes{ - pathElemName: "one", - }, - "two": &sharedEntryAttributes{ - pathElemName: "two", - }, - "three": &sharedEntryAttributes{ - pathElemName: "three", - }, + "one": nil, + "two": nil, + "three": nil, }, }, args: args{ @@ -64,15 +52,9 @@ func Test_childMap_DeleteChilds(t *testing.T) { name: "Delete non-existing entry", fields: fields{ c: map[string]api.Entry{ - "one": &sharedEntryAttributes{ - pathElemName: "one", - }, - "two": &sharedEntryAttributes{ - pathElemName: "two", - }, - "three": &sharedEntryAttributes{ - pathElemName: "three", - }, + "one": nil, + "two": nil, + "three": nil, }, }, args: args{ @@ -83,13 +65,10 @@ func Test_childMap_DeleteChilds(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := &childMap{ - c: tt.fields.c, - mu: sync.RWMutex{}, - } + c := api.NewChildMapWithEntries(tt.fields.c) c.DeleteChilds(tt.args.names) - if len(c.c) != tt.expectedLength { - t.Errorf("expected %d elements got %d", tt.expectedLength, len(c.c)) + if c.Length() != tt.expectedLength { + t.Errorf("expected %d elements got %d", tt.expectedLength, c.Length()) } }) } @@ -112,15 +91,9 @@ func Test_childMap_DeleteChild(t *testing.T) { name: "Delete existing entry", fields: fields{ c: map[string]api.Entry{ - "one": &sharedEntryAttributes{ - pathElemName: "one", - }, - "two": &sharedEntryAttributes{ - pathElemName: "two", - }, - "three": &sharedEntryAttributes{ - pathElemName: "three", - }, + "one": nil, + "two": nil, + "three": nil, }, }, args: args{ @@ -132,15 +105,9 @@ func Test_childMap_DeleteChild(t *testing.T) { name: "Delete non-existing entry", fields: fields{ c: map[string]api.Entry{ - "one": &sharedEntryAttributes{ - pathElemName: "one", - }, - "two": &sharedEntryAttributes{ - pathElemName: "two", - }, - "three": &sharedEntryAttributes{ - pathElemName: "three", - }, + "one": nil, + "two": nil, + "three": nil, }, }, args: args{ @@ -151,13 +118,10 @@ func Test_childMap_DeleteChild(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := &childMap{ - c: tt.fields.c, - mu: sync.RWMutex{}, - } + c := api.NewChildMapWithEntries(tt.fields.c) c.DeleteChild(tt.args.name) - if len(c.c) != tt.expectedLength { - t.Errorf("expected %d elements got %d", tt.expectedLength, len(c.c)) + if c.Length() != tt.expectedLength { + t.Errorf("expected %d elements got %d", tt.expectedLength, c.Length()) } }) } diff --git a/pkg/tree/api/entry.go b/pkg/tree/api/entry.go index 699eaa4d..c2914076 100644 --- a/pkg/tree/api/entry.go +++ b/pkg/tree/api/entry.go @@ -2,14 +2,37 @@ package api import ( "context" + "fmt" - "github.com/beevik/etree" - "github.com/sdcio/data-server/pkg/config" "github.com/sdcio/data-server/pkg/tree/types" sdcpb "github.com/sdcio/sdc-protos/sdcpb" - "github.com/sdcio/sdc-protos/tree_persist" ) +// EntryFactory is a function type for creating new Entry instances +type EntryFactory func(ctx context.Context, parent Entry, pathElemName string, tc TreeContext) (Entry, error) + +var ( + newEntryFunc EntryFactory +) + +// RegisterEntryFactory registers the factory function for creating Entry instances +// This is called by the tree package during initialization +func RegisterEntryFactory(factory EntryFactory) { + if newEntryFunc != nil { + panic("EntryFactory already registered") + } + newEntryFunc = factory +} + +// NewEntry creates a new Entry instance as a child of the given parent +// The parent's AddChild method is called to register the new entry +func NewEntry(ctx context.Context, parent Entry, pathElemName string, tc TreeContext) (Entry, error) { + if newEntryFunc == nil { + return nil, fmt.Errorf("EntryFactory not registered") + } + return newEntryFunc(ctx, parent, pathElemName, tc) +} + // Entry is the primary Element of the Tree. type Entry interface { // PathName returns the last Path element, the name of the Entry @@ -18,13 +41,13 @@ type Entry interface { GetLevel() int // addChild Add a child entry AddChild(context.Context, Entry) error - // getOrCreateChilds retrieves the sub-child pointed at by the path. - // if the path does not exist in its full extend, the entries will be added along the way - // if the path does not point to a schema defined path an error will be raise - GetOrCreateChilds(ctx context.Context, path *sdcpb.Path) (Entry, error) - // AddUpdateRecursive Add the given cache.Update to the tree - AddUpdateRecursive(ctx context.Context, relativePath *sdcpb.Path, u *types.Update, flags *types.UpdateInsertFlags) (Entry, error) - AddUpdateRecursiveInternal(ctx context.Context, path *sdcpb.Path, idx int, u *types.Update, flags *types.UpdateInsertFlags) (Entry, error) + // // getOrCreateChilds retrieves the sub-child pointed at by the path. + // // if the path does not exist in its full extend, the entries will be added along the way + // // if the path does not point to a schema defined path an error will be raise + // // GetOrCreateChilds(ctx context.Context, path *sdcpb.Path) (Entry, error) + // // AddUpdateRecursive Add the given cache.Update to the tree + // AddUpdateRecursive(ctx context.Context, relativePath *sdcpb.Path, u *types.Update, flags *types.UpdateInsertFlags) (Entry, error) + // AddUpdateRecursiveInternal(ctx context.Context, path *sdcpb.Path, idx int, u *types.Update, flags *types.UpdateInsertFlags) (Entry, error) // StringIndent debug tree struct as indented string slice StringIndent(result []string) []string // GetHighesPrio return the new cache.Update entried from the tree that are the highes priority. @@ -39,14 +62,7 @@ type Entry interface { // // markOwnerDelete Sets the delete flag on all the LeafEntries belonging to the given owner. // MarkOwnerDelete(o string, onlyIntended bool) // GetDeletes returns the cache-updates that are not updated, have no lower priority value left and hence should be deleted completely - GetDeletes(entries []types.DeleteEntry, aggregatePaths bool) ([]types.DeleteEntry, error) - // Validate kicks off validation - ValidateLevel(ctx context.Context, resultChan chan<- *types.ValidationResultEntry, stats *types.ValidationStats, vCfg *config.Validation) - // validateMandatory the Mandatory schema field - ValidateMandatory(ctx context.Context, resultChan chan<- *types.ValidationResultEntry, stats *types.ValidationStats) - // validateMandatoryWithKeys is an internally used function that us called by validateMandatory in case - // the container has keys defined that need to be skipped before the mandatory attributes can be checked - ValidateMandatoryWithKeys(ctx context.Context, level int, attributes []string, choiceName string, resultChan chan<- *types.ValidationResultEntry) + GetDeletes(entries types.DeleteEntriesList, aggregatePaths bool) (types.DeleteEntriesList, error) // getHighestPrecedenceValueOfBranch returns the highes Precedence Value (lowest Priority value) of the brach that starts at this Entry GetHighestPrecedenceValueOfBranch(filter HighestPrecedenceFilter) int32 // GetSchema returns the *sdcpb.SchemaElem of the Entry @@ -59,20 +75,12 @@ type Entry interface { // GetParent returns the parent entry GetParent() Entry NavigateSdcpbPath(ctx context.Context, path *sdcpb.Path) (Entry, error) - // NavigateLeafRef follows the leafref and returns the referenced entry - NavigateLeafRef(ctx context.Context) ([]Entry, error) - // GetFirstAncestorWithSchema returns the first parent node which has a schema set. - // if the parent has no schema (is a key element in the tree) it will recurs the call to the parents parent. - // the level of recursion is indicated via the levelUp attribute - GetFirstAncestorWithSchema() (ancestor Entry, levelUp int) // SdcpbPath returns the sdcpb.Path struct for the Entry SdcpbPath() *sdcpb.Path - // GetSchemaKeys checks for the schema of the entry, and returns the defined keys - GetSchemaKeys() []string - // GetRootBasedEntryChain returns all the entries starting from the root down to the actual Entry. - GetRootBasedEntryChain() []Entry - // GetRoot returns the Trees Root Entry - GetRoot() Entry + // // // GetSchemaKeys checks for the schema of the entry, and returns the defined keys + // // GetSchemaKeys() []string + // // GetRootBasedEntryChain returns all the entries starting from the root down to the actual Entry. + // GetRootBasedEntryChain() []Entry // remainsToExist indicates if a LeafEntry for this entry will survive the update. // Since we add running to the tree, there will always be Entries, that will disappear in the // as part of the SetIntent process. We need to consider this, when evaluating e.g. LeafRefs. @@ -87,29 +95,15 @@ type Entry interface { // - remainsToExists() returns true, because they remain to exist even though implicitly. // - shouldDelete() returns false, because no explicit delete should be issued for them. CanDelete() bool + GetChildMap() *ChildMap GetChilds(types.DescendMethod) EntryMap GetChild(name string) (Entry, bool) // entry, exists FilterChilds(keys map[string]string) ([]Entry, error) - // ToJson returns the Tree contained structure as JSON - // use e.g. json.MarshalIndent() on the returned struct - ToJson(onlyNewOrUpdated bool) (any, error) - // ToJsonIETF returns the Tree contained structure as JSON_IETF - // use e.g. json.MarshalIndent() on the returned struct - ToJsonIETF(onlyNewOrUpdated bool) (any, error) - // toJsonInternal the internal function that produces JSON and JSON_IETF - // Not for external usage - ToJsonInternal(onlyNewOrUpdated bool, ietf bool) (j any, err error) - // ToXML returns the tree and its current state in the XML representation used by netconf - ToXML(onlyNewOrUpdated bool, honorNamespace bool, operationWithNamespace bool, useOperationRemove bool) (*etree.Document, error) - ToXmlInternal(parent *etree.Element, onlyNewOrUpdated bool, honorNamespace bool, operationWithNamespace bool, useOperationRemove bool) (doAdd bool, err error) - TreeExport(owner string) ([]*tree_persist.TreeElement, error) - // DeleteBranch Deletes from the tree, all elements of the PathSlice defined branch of the given owner - DeleteBranch(ctx context.Context, path *sdcpb.Path, owner string) (err error) - GetDeviations(ctx context.Context, ch chan<- *types.DeviationEntry, activeCase bool) + // // DeleteBranch Deletes from the tree, all elements of the PathSlice defined branch of the given owner + // DeleteBranch(ctx context.Context, path *sdcpb.Path, owner string) (err error) // GetListChilds collects all the childs of the list. In the tree we store them seperated into their key branches. // this is collecting all the last level key entries. GetListChilds() ([]Entry, error) - BreadthSearch(ctx context.Context, path *sdcpb.Path) ([]Entry, error) DeepCopy(tc TreeContext, parent Entry) (Entry, error) GetLeafVariants() *LeafVariants diff --git a/pkg/tree/childMap.go b/pkg/tree/childMap.go deleted file mode 100644 index 36bd4897..00000000 --- a/pkg/tree/childMap.go +++ /dev/null @@ -1,104 +0,0 @@ -package tree - -import ( - "iter" - "slices" - "sync" - - "github.com/sdcio/data-server/pkg/tree/api" -) - -type childMap struct { - c map[string]api.Entry - mu sync.RWMutex -} - -func newChildMap() *childMap { - return &childMap{ - c: map[string]api.Entry{}, - } -} - -func (c *childMap) Items() iter.Seq2[string, api.Entry] { - return func(yield func(string, api.Entry) bool) { - for i, v := range c.c { - if !yield(i, v) { - return - } - } - } -} - -func (c *childMap) DeleteChilds(names []string) { - c.mu.Lock() - defer c.mu.Unlock() - for _, name := range names { - delete(c.c, name) - } -} - -func (c *childMap) DeleteChild(name string) { - c.mu.Lock() - defer c.mu.Unlock() - delete(c.c, name) -} - -func (c *childMap) Add(e api.Entry) { - c.mu.Lock() - defer c.mu.Unlock() - c.c[e.PathName()] = e -} - -func (c *childMap) GetEntry(s string) (e api.Entry, exists bool) { - c.mu.RLock() - defer c.mu.RUnlock() - e, exists = c.c[s] - return e, exists -} - -func (c *childMap) GetAllSorted() []api.Entry { - c.mu.RLock() - defer c.mu.RUnlock() - - childNames := make([]string, 0, len(c.c)) - for name := range c.c { - childNames = append(childNames, name) - } - slices.Sort(childNames) - - result := make([]api.Entry, 0, len(c.c)) - // range over children - for _, childName := range childNames { - result = append(result, c.c[childName]) - } - - return result -} - -func (c *childMap) GetAll() map[string]api.Entry { - c.mu.RLock() - defer c.mu.RUnlock() - - result := make(map[string]api.Entry, len(c.c)) - for k, v := range c.c { - result[k] = v - } - return result -} - -func (c *childMap) GetKeys() []string { - c.mu.RLock() - defer c.mu.RUnlock() - - result := make([]string, 0, c.Length()) - for k := range c.c { - result = append(result, k) - } - return result -} - -func (c *childMap) Length() int { - c.mu.RLock() - defer c.mu.RUnlock() - return len(c.c) -} diff --git a/pkg/tree/entry_list.go b/pkg/tree/entry_list.go deleted file mode 100644 index 7701aea2..00000000 --- a/pkg/tree/entry_list.go +++ /dev/null @@ -1,5 +0,0 @@ -package tree - -import "github.com/sdcio/data-server/pkg/tree/api" - -type EntrySlice []api.Entry diff --git a/pkg/tree/entry_test.go b/pkg/tree/entry_test.go index ca9fef69..e44139e8 100644 --- a/pkg/tree/entry_test.go +++ b/pkg/tree/entry_test.go @@ -18,6 +18,8 @@ import ( "github.com/sdcio/data-server/pkg/tree/importer" jsonImporter "github.com/sdcio/data-server/pkg/tree/importer/json" "github.com/sdcio/data-server/pkg/tree/ops" + "github.com/sdcio/data-server/pkg/tree/ops/validation" + "github.com/sdcio/data-server/pkg/tree/processors" "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils/testhelper" sdcpb "github.com/sdcio/sdc-protos/sdcpb" @@ -25,17 +27,10 @@ import ( ) var ( - flagsNew *types.UpdateInsertFlags - flagsExisting *types.UpdateInsertFlags - flagsDelete *types.UpdateInsertFlags validationConfig = config.NewValidationConfig() ) func init() { - flagsNew = types.NewUpdateInsertFlags() - flagsNew.SetNewFlag() - flagsExisting = types.NewUpdateInsertFlags() - flagsDelete = types.NewUpdateInsertFlags().SetDeleteFlag() validationConfig.SetDisableConcurrency(true) } @@ -82,7 +77,7 @@ func Test_Entry(t *testing.T) { } for _, u := range []*types.PathAndUpdate{types.NewPathAndUpdate(p1, u1), types.NewPathAndUpdate(p2, u2), types.NewPathAndUpdate(p2, u3)} { - _, err = root.AddUpdateRecursive(ctx, u.GetPath(), u.GetUpdate(), flagsNew) + _, err = ops.AddUpdateRecursive(ctx, root.Entry, u.GetPath(), u.GetUpdate(), testhelper.FlagsNew) if err != nil { t.Error(err) } @@ -199,7 +194,7 @@ func Test_Entry_One(t *testing.T) { types.NewPathAndUpdate(p2, u3), types.NewPathAndUpdate(p2_1, u3_1), } { - _, err := root.AddUpdateRecursive(ctx, u.GetPath(), u.GetUpdate(), flagsNew) + _, err := ops.AddUpdateRecursive(ctx, root.Entry, u.GetPath(), u.GetUpdate(), testhelper.FlagsNew) if err != nil { t.Error(err) } @@ -214,7 +209,7 @@ func Test_Entry_One(t *testing.T) { t.Log(root.String()) t.Run("Test 1 - expected entries for owner1", func(t *testing.T) { - o1Le := ops.GetByOwner(root, owner1) + o1Le := ops.GetByOwner(root.Entry, owner1) o1 := api.LeafEntriesToUpdates(o1Le) // diff the result with the expected // if diff := testhelper.DiffUpdates([]*types.Update{u0o1, u2, u2_1, u1, u1_1}, o1); diff != "" { @@ -230,7 +225,7 @@ func Test_Entry_One(t *testing.T) { }) t.Run("Test 2 - expected entries for owner2", func(t *testing.T) { - o2Le := ops.GetByOwner(root, owner2) + o2Le := ops.GetByOwner(root.Entry, owner2) o2 := api.LeafEntriesToUpdates(o2Le) // diff the result with the expected if diff := testhelper.DiffUpdates([]*types.PathAndUpdate{ @@ -315,7 +310,7 @@ func Test_Entry_Two(t *testing.T) { // start test add "existing" data for _, u := range []*types.PathAndUpdate{types.NewPathAndUpdate(p1, u1)} { - _, err := root.AddUpdateRecursive(ctx, u.GetPath(), u.GetUpdate(), flagsExisting) + _, err := ops.AddUpdateRecursive(ctx, root.Entry, u.GetPath(), u.GetUpdate(), testhelper.FlagsExisting) if err != nil { t.Error(err) } @@ -336,7 +331,7 @@ func Test_Entry_Two(t *testing.T) { n1 := types.NewUpdate(nil, overwriteDesc, prio50, owner1, ts1) for _, u := range []*types.PathAndUpdate{types.NewPathAndUpdate(pn1, n1)} { - _, err = root.AddUpdateRecursive(ctx, u.GetPath(), u.GetUpdate(), flagsNew) + _, err = ops.AddUpdateRecursive(ctx, root.Entry, u.GetPath(), u.GetUpdate(), testhelper.FlagsNew) if err != nil { t.Error(err) } @@ -514,7 +509,7 @@ func Test_Entry_Three(t *testing.T) { types.NewPathAndUpdate(p2, u2), types.NewPathAndUpdate(p3, u3), types.NewPathAndUpdate(p4, u4)} { - _, err := root.AddUpdateRecursive(ctx, u.GetPath(), u.GetUpdate(), flagsExisting) + _, err := ops.AddUpdateRecursive(ctx, root.Entry, u.GetPath(), u.GetUpdate(), testhelper.FlagsExisting) if err != nil { t.Error(err) } @@ -523,7 +518,7 @@ func Test_Entry_Three(t *testing.T) { // start test add "existing" data as running for _, u := range []*types.PathAndUpdate{ types.NewPathAndUpdate(p1r, u1r), types.NewPathAndUpdate(p2r, u2r), types.NewPathAndUpdate(p3r, u3r), types.NewPathAndUpdate(p4r, u4r)} { - _, err := root.AddUpdateRecursive(ctx, u.GetPath(), u.GetUpdate(), flagsExisting) + _, err := ops.AddUpdateRecursive(ctx, root.Entry, u.GetPath(), u.GetUpdate(), testhelper.FlagsExisting) if err != nil { t.Error(err) } @@ -573,9 +568,9 @@ func Test_Entry_Three(t *testing.T) { // indicate that the intent is receiving an update // therefor invalidate all the present entries of the owner / intent sharedTaskPool := pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0)) - ownerDeleteMarker := NewOwnerDeleteMarker(NewOwnerDeleteMarkerTaskConfig(owner1, false)) + ownerDeleteMarker := processors.NewOwnerDeleteMarker(processors.NewOwnerDeleteMarkerTaskConfig(owner1, false)) - err = ownerDeleteMarker.Run(root.GetRoot(), sharedTaskPool) + err = ownerDeleteMarker.Run(root.Entry, sharedTaskPool) if err != nil { t.Error(err) return @@ -607,7 +602,7 @@ func Test_Entry_Three(t *testing.T) { n2 := types.NewUpdate(nil, overwriteDesc, prio50, owner1, ts1) for _, u := range []*types.PathAndUpdate{types.NewPathAndUpdate(pn1, n1), types.NewPathAndUpdate(pn2, n2)} { - _, err := root.AddUpdateRecursive(ctx, u.GetPath(), u.GetUpdate(), flagsNew) + _, err := ops.AddUpdateRecursive(ctx, root.Entry, u.GetPath(), u.GetUpdate(), testhelper.FlagsNew) if err != nil { t.Error(err) } @@ -810,7 +805,7 @@ func Test_Entry_Four(t *testing.T) { types.NewPathAndUpdate(p1o2, u1o2), types.NewPathAndUpdate(p2o2, u2o2), } { - _, err := root.AddUpdateRecursive(ctx, u.GetPath(), u.GetUpdate(), flagsExisting) + _, err := ops.AddUpdateRecursive(ctx, root.Entry, u.GetPath(), u.GetUpdate(), testhelper.FlagsExisting) if err != nil { t.Error(err) } @@ -847,9 +842,9 @@ func Test_Entry_Four(t *testing.T) { // indicate that the intent is receiving an update // therefor invalidate all the present entries of the owner / intent sharedTaskPool := pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0)) - ownerDeleteMarker := NewOwnerDeleteMarker(NewOwnerDeleteMarkerTaskConfig(owner1, false)) + ownerDeleteMarker := processors.NewOwnerDeleteMarker(processors.NewOwnerDeleteMarkerTaskConfig(owner1, false)) - err = ownerDeleteMarker.Run(root.GetRoot(), sharedTaskPool) + err = ownerDeleteMarker.Run(root.Entry, sharedTaskPool) if err != nil { t.Error(err) return @@ -882,7 +877,7 @@ func Test_Entry_Four(t *testing.T) { n2 := types.NewUpdate(nil, overwriteDesc, prio50, owner1, ts1) for _, u := range []*types.PathAndUpdate{types.NewPathAndUpdate(pn1, n1), types.NewPathAndUpdate(pn2, n2)} { - _, err := root.AddUpdateRecursive(ctx, u.GetPath(), u.GetUpdate(), flagsNew) + _, err := ops.AddUpdateRecursive(ctx, root.Entry, u.GetPath(), u.GetUpdate(), testhelper.FlagsNew) if err != nil { t.Error(err) } @@ -988,7 +983,7 @@ func Test_Validation_Leaflist_Min_Max(t *testing.T) { // start test add "existing" data for _, u := range []*types.PathAndUpdate{types.NewPathAndUpdate(p1, u1)} { - _, err := root.AddUpdateRecursive(ctx, u.GetPath(), u.GetUpdate(), flagsExisting) + _, err := ops.AddUpdateRecursive(ctx, root.Entry, u.GetPath(), u.GetUpdate(), testhelper.FlagsExisting) if err != nil { t.Fatal(err) } @@ -1002,7 +997,7 @@ func Test_Validation_Leaflist_Min_Max(t *testing.T) { t.Log(root.String()) sharedPool := pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0)) - validationResult, _ := root.Validate(context.TODO(), validationConfig, sharedPool) + validationResult, _ := validation.Validate(context.TODO(), root.Entry, validationConfig, sharedPool) // check if errors are received // If so, join them and return the cumulated errors @@ -1044,14 +1039,14 @@ func Test_Validation_Leaflist_Min_Max(t *testing.T) { // start test add "existing" data for _, u := range []*types.PathAndUpdate{types.NewPathAndUpdate(p1, u1)} { - _, err := root.AddUpdateRecursive(ctx, u.GetPath(), u.GetUpdate(), flagsExisting) + _, err := ops.AddUpdateRecursive(ctx, root.Entry, u.GetPath(), u.GetUpdate(), testhelper.FlagsExisting) if err != nil { t.Fatal(err) } } sharedPool := pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0)) - validationResult, _ := root.Validate(context.TODO(), validationConfig, sharedPool) + validationResult, _ := validation.Validate(context.TODO(), root.Entry, validationConfig, sharedPool) // check if errors are received // If so, join them and return the cumulated errors @@ -1099,14 +1094,14 @@ func Test_Validation_Leaflist_Min_Max(t *testing.T) { // start test add "existing" data for _, u := range []*types.PathAndUpdate{types.NewPathAndUpdate(p1, u1)} { - _, err := root.AddUpdateRecursive(ctx, u.GetPath(), u.GetUpdate(), flagsExisting) + _, err := ops.AddUpdateRecursive(ctx, root.Entry, u.GetPath(), u.GetUpdate(), testhelper.FlagsExisting) if err != nil { t.Fatal(err) } } sharedPool := pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0)) - validationResult, _ := root.Validate(context.TODO(), validationConfig, sharedPool) + validationResult, _ := validation.Validate(context.TODO(), root.Entry, validationConfig, sharedPool) // check if errors are received // If so, join them and return the cumulated errors @@ -1207,23 +1202,23 @@ func Test_Entry_Delete_Aggregation(t *testing.T) { types.NewPathAndUpdate(p5, u5), types.NewPathAndUpdate(p6, u6), } { - _, err := root.AddUpdateRecursive(ctx, u.GetPath(), u.GetUpdate(), flagsExisting) + _, err := ops.AddUpdateRecursive(ctx, root.Entry, u.GetPath(), u.GetUpdate(), testhelper.FlagsExisting) if err != nil { t.Fatal(err) } // add also as running runUpd := u.DeepCopy() runUpd.GetUpdate().SetOwner(RunningIntentName).SetPriority(RunningValuesPrio) - _, err = root.AddUpdateRecursive(ctx, runUpd.GetPath(), runUpd.GetUpdate(), flagsExisting) + _, err = ops.AddUpdateRecursive(ctx, root.Entry, runUpd.GetPath(), runUpd.GetUpdate(), testhelper.FlagsExisting) if err != nil { t.Fatal(err) } } sharedTaskPool := pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0)) - ownerDeleteMarker := NewOwnerDeleteMarker(NewOwnerDeleteMarkerTaskConfig(owner1, false)) + ownerDeleteMarker := processors.NewOwnerDeleteMarker(processors.NewOwnerDeleteMarkerTaskConfig(owner1, false)) - err = ownerDeleteMarker.Run(root.GetRoot(), sharedTaskPool) + err = ownerDeleteMarker.Run(root.Entry, sharedTaskPool) if err != nil { t.Error(err) return @@ -1249,7 +1244,7 @@ func Test_Entry_Delete_Aggregation(t *testing.T) { // start test add "new" / request data for _, u := range []*types.PathAndUpdate{types.NewPathAndUpdate(p1n, u1n), types.NewPathAndUpdate(p2n, u2n)} { - _, err := root.AddUpdateRecursive(ctx, u.GetPath(), u.GetUpdate(), flagsNew) + _, err := ops.AddUpdateRecursive(ctx, root.Entry, u.GetPath(), u.GetUpdate(), testhelper.FlagsNew) if err != nil { t.Fatal(err) } @@ -1301,10 +1296,10 @@ func TestLeafVariants_GetHighesPrio(t *testing.T) { func(t *testing.T) { lv := api.NewLeafVariants(&TreeContext{}, nil) - le1 := api.NewLeafEntry(types.NewUpdate(nil, nil, 2, owner1, ts), flagsExisting, nil) + le1 := api.NewLeafEntry(types.NewUpdate(nil, nil, 2, owner1, ts), testhelper.FlagsExisting, nil) lv.Add(le1) - le2 := api.NewLeafEntry(types.NewUpdate(nil, nil, 1, owner2, ts), flagsDelete, nil) + le2 := api.NewLeafEntry(types.NewUpdate(nil, nil, 1, owner2, ts), testhelper.FlagsDelete, nil) lv.Add(le2) le := lv.GetHighestPrecedence(true, false, false) @@ -1320,7 +1315,7 @@ func TestLeafVariants_GetHighesPrio(t *testing.T) { t.Run("Single entry thats also marked for deletion", func(t *testing.T) { lv := api.NewLeafVariants(&TreeContext{}, nil) - lv.Add(api.NewLeafEntry(types.NewUpdate(nil, nil, 1, owner1, ts), flagsDelete, nil)) + lv.Add(api.NewLeafEntry(types.NewUpdate(nil, nil, 1, owner1, ts), testhelper.FlagsDelete, nil)) le := lv.GetHighestPrecedence(true, false, false) if le != nil { @@ -1334,9 +1329,9 @@ func TestLeafVariants_GetHighesPrio(t *testing.T) { t.Run("New Low Prio IsUpdate OnlyChanged True", func(t *testing.T) { lv := api.NewLeafVariants(&TreeContext{}, nil) - lv.Add(api.NewLeafEntry(types.NewUpdate(nil, nil, 5, owner1, ts), flagsExisting, nil)) - lv.Add(api.NewLeafEntry(types.NewUpdate(nil, nil, 6, owner2, ts), flagsNew, nil)) - lv.Add(api.NewLeafEntry(types.NewUpdate(nil, nil, RunningValuesPrio, RunningIntentName, ts), flagsExisting, nil)) + lv.Add(api.NewLeafEntry(types.NewUpdate(nil, nil, 5, owner1, ts), testhelper.FlagsExisting, nil)) + lv.Add(api.NewLeafEntry(types.NewUpdate(nil, nil, 6, owner2, ts), testhelper.FlagsNew, nil)) + lv.Add(api.NewLeafEntry(types.NewUpdate(nil, nil, RunningValuesPrio, RunningIntentName, ts), testhelper.FlagsExisting, nil)) le := lv.GetHighestPrecedence(true, false, false) @@ -1351,9 +1346,9 @@ func TestLeafVariants_GetHighesPrio(t *testing.T) { t.Run("New Low Prio IsUpdate OnlyChanged False", func(t *testing.T) { lv := api.NewLeafVariants(&TreeContext{}, nil) - le1 := api.NewLeafEntry(types.NewUpdate(nil, nil, 5, owner1, ts), flagsExisting, nil) + le1 := api.NewLeafEntry(types.NewUpdate(nil, nil, 5, owner1, ts), testhelper.FlagsExisting, nil) lv.Add(le1) - le2 := api.NewLeafEntry(types.NewUpdate(nil, nil, 6, owner2, ts), flagsNew, nil) + le2 := api.NewLeafEntry(types.NewUpdate(nil, nil, 6, owner2, ts), testhelper.FlagsNew, nil) lv.Add(le2) le := lv.GetHighestPrecedence(false, false, false) @@ -1370,9 +1365,9 @@ func TestLeafVariants_GetHighesPrio(t *testing.T) { func(t *testing.T) { lv := api.NewLeafVariants(&TreeContext{}, nil) - lv.Add(api.NewLeafEntry(types.NewUpdate(nil, nil, 5, owner1, ts), flagsExisting, nil)) - lv.Add(api.NewLeafEntry(types.NewUpdate(nil, nil, 6, owner2, ts), flagsNew, nil)) - lv.Add(api.NewLeafEntry(types.NewUpdate(nil, nil, RunningValuesPrio, RunningIntentName, ts), flagsExisting, nil)) + lv.Add(api.NewLeafEntry(types.NewUpdate(nil, nil, 5, owner1, ts), testhelper.FlagsExisting, nil)) + lv.Add(api.NewLeafEntry(types.NewUpdate(nil, nil, 6, owner2, ts), testhelper.FlagsNew, nil)) + lv.Add(api.NewLeafEntry(types.NewUpdate(nil, nil, RunningValuesPrio, RunningIntentName, ts), testhelper.FlagsExisting, nil)) le := lv.GetHighestPrecedence(true, false, false) @@ -1387,9 +1382,9 @@ func TestLeafVariants_GetHighesPrio(t *testing.T) { t.Run("New Low Prio IsNew OnlyChanged == True, with running not existing", func(t *testing.T) { lv := api.NewLeafVariants(&TreeContext{}, nil) - le1 := api.NewLeafEntry(types.NewUpdate(nil, nil, 5, owner1, ts), flagsExisting, nil) + le1 := api.NewLeafEntry(types.NewUpdate(nil, nil, 5, owner1, ts), testhelper.FlagsExisting, nil) lv.Add(le1) - le2 := api.NewLeafEntry(types.NewUpdate(nil, nil, 6, owner2, ts), flagsNew, nil) + le2 := api.NewLeafEntry(types.NewUpdate(nil, nil, 6, owner2, ts), testhelper.FlagsNew, nil) lv.Add(le2) le := lv.GetHighestPrecedence(true, false, false) @@ -1403,9 +1398,9 @@ func TestLeafVariants_GetHighesPrio(t *testing.T) { t.Run("New Low Prio IsNew OnlyChanged == False", func(t *testing.T) { lv := api.NewLeafVariants(&TreeContext{}, nil) - le1 := api.NewLeafEntry(types.NewUpdate(nil, nil, 5, owner1, ts), flagsExisting, nil) + le1 := api.NewLeafEntry(types.NewUpdate(nil, nil, 5, owner1, ts), testhelper.FlagsExisting, nil) lv.Add(le1) - le2 := api.NewLeafEntry(types.NewUpdate(nil, nil, 6, owner2, ts), flagsNew, nil) + le2 := api.NewLeafEntry(types.NewUpdate(nil, nil, 6, owner2, ts), testhelper.FlagsNew, nil) lv.Add(le2) le := lv.GetHighestPrecedence(false, false, false) @@ -1433,9 +1428,9 @@ func TestLeafVariants_GetHighesPrio(t *testing.T) { t.Run("secondhighes populated if highes was first", func(t *testing.T) { lv := api.NewLeafVariants(&TreeContext{}, nil) - le1 := api.NewLeafEntry(types.NewUpdate(nil, nil, 1, owner1, ts), flagsDelete, nil) + le1 := api.NewLeafEntry(types.NewUpdate(nil, nil, 1, owner1, ts), testhelper.FlagsDelete, nil) lv.Add(le1) - le2 := api.NewLeafEntry(types.NewUpdate(nil, nil, 2, owner2, ts), flagsExisting, nil) + le2 := api.NewLeafEntry(types.NewUpdate(nil, nil, 2, owner2, ts), testhelper.FlagsExisting, nil) lv.Add(le2) le := lv.GetHighestPrecedence(true, false, false) if le != le2 { @@ -1496,37 +1491,37 @@ func Test_Schema_Population(t *testing.T) { t.Fatal(err) } - interf, err := newSharedEntryAttributes(ctx, root.Entry, "interface", tc) + interf, err := NewSharedEntryAttributes(ctx, root.Entry, "interface", tc) if err != nil { t.Error(err) } expectNotNil(t, interf.schema, "/interface schema") - e00, err := newSharedEntryAttributes(ctx, interf, "ethernet-1/1", tc) + e00, err := NewSharedEntryAttributes(ctx, interf, "ethernet-1/1", tc) if err != nil { t.Error(err) } expectNil(t, e00.schema, "/interface/ethernet-1/1 schema") - dk, err := newSharedEntryAttributes(ctx, root.Entry, "doublekey", tc) + dk, err := NewSharedEntryAttributes(ctx, root.Entry, "doublekey", tc) if err != nil { t.Error(err) } expectNotNil(t, dk.schema, "/doublekey schema") - dkk1, err := newSharedEntryAttributes(ctx, dk, "key1", tc) + dkk1, err := NewSharedEntryAttributes(ctx, dk, "key1", tc) if err != nil { t.Error(err) } expectNil(t, dkk1.schema, "/doublekey/key1 schema") - dkk2, err := newSharedEntryAttributes(ctx, dkk1, "key2", tc) + dkk2, err := NewSharedEntryAttributes(ctx, dkk1, "key2", tc) if err != nil { t.Error(err) } expectNil(t, dkk2.schema, "/doublekey/key2 schema") - dkkv, err := newSharedEntryAttributes(ctx, dkk2, "mandato", tc) + dkkv, err := NewSharedEntryAttributes(ctx, dkk2, "mandato", tc) if err != nil { t.Error(err) } @@ -1551,37 +1546,37 @@ func Test_sharedEntryAttributes_SdcpbPath(t *testing.T) { t.Fatal(err) } - interf, err := newSharedEntryAttributes(ctx, root.Entry, "interface", tc) + interf, err := NewSharedEntryAttributes(ctx, root.Entry, "interface", tc) if err != nil { t.Error(err) } - e00, err := newSharedEntryAttributes(ctx, interf, "ethernet-1/1", tc) + e00, err := NewSharedEntryAttributes(ctx, interf, "ethernet-1/1", tc) if err != nil { t.Error(err) } - e00desc, err := newSharedEntryAttributes(ctx, e00, "description", tc) + e00desc, err := NewSharedEntryAttributes(ctx, e00, "description", tc) if err != nil { t.Error(err) } - dk, err := newSharedEntryAttributes(ctx, root.Entry, "doublekey", tc) + dk, err := NewSharedEntryAttributes(ctx, root.Entry, "doublekey", tc) if err != nil { t.Error(err) } - dkk1, err := newSharedEntryAttributes(ctx, dk, "key1", tc) + dkk1, err := NewSharedEntryAttributes(ctx, dk, "key1", tc) if err != nil { t.Error(err) } - dkk2, err := newSharedEntryAttributes(ctx, dkk1, "key2", tc) + dkk2, err := NewSharedEntryAttributes(ctx, dkk1, "key2", tc) if err != nil { t.Error(err) } - dkkv, err := newSharedEntryAttributes(ctx, dkk2, "mandato", tc) + dkkv, err := NewSharedEntryAttributes(ctx, dkk2, "mandato", tc) if err != nil { t.Error(err) } @@ -1687,7 +1682,7 @@ func Test_Validation_String_Pattern(t *testing.T) { u1 := types.NewUpdate(nil, leafval, prio50, owner1, ts1) for _, u := range []*types.PathAndUpdate{types.NewPathAndUpdate(p1, u1)} { - _, err := root.AddUpdateRecursive(ctx, u.GetPath(), u.GetUpdate(), flagsNew) + _, err := ops.AddUpdateRecursive(ctx, root.Entry, u.GetPath(), u.GetUpdate(), testhelper.FlagsNew) if err != nil { t.Fatal(err) } @@ -1699,7 +1694,7 @@ func Test_Validation_String_Pattern(t *testing.T) { } sharedPool := pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0)) - validationResult, _ := root.Validate(context.TODO(), validationConfig, sharedPool) + validationResult, _ := validation.Validate(context.TODO(), root.Entry, validationConfig, sharedPool) // check if errors are received // If so, join them and return the cumulated errors @@ -1725,7 +1720,7 @@ func Test_Validation_String_Pattern(t *testing.T) { u1 := types.NewUpdate(nil, leafval, prio50, owner1, ts1) for _, u := range []*types.PathAndUpdate{types.NewPathAndUpdate(p1, u1)} { - _, err := root.AddUpdateRecursive(ctx, u.GetPath(), u.GetUpdate(), flagsNew) + _, err := ops.AddUpdateRecursive(ctx, root.Entry, u.GetPath(), u.GetUpdate(), testhelper.FlagsNew) if err != nil { t.Fatal(err) } @@ -1737,7 +1732,7 @@ func Test_Validation_String_Pattern(t *testing.T) { } sharedPool := pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0)) - validationResult, _ := root.Validate(context.TODO(), validationConfig, sharedPool) + validationResult, _ := validation.Validate(context.TODO(), root.Entry, validationConfig, sharedPool) // check if errors are received // If so, join them and return the cumulated errors @@ -1822,7 +1817,7 @@ func Test_Validation_Deref(t *testing.T) { u1 := types.NewUpdate(nil, leafval, prio50, owner1, ts1) for _, u := range []*types.PathAndUpdate{types.NewPathAndUpdate(p1, u1)} { - _, err := root.AddUpdateRecursive(ctx, u.GetPath(), u.GetUpdate(), flagsNew) + _, err := ops.AddUpdateRecursive(ctx, root.Entry, u.GetPath(), u.GetUpdate(), testhelper.FlagsNew) if err != nil { t.Fatal(err) } @@ -1834,7 +1829,7 @@ func Test_Validation_Deref(t *testing.T) { } sharedPool := pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0)) - validationResult, _ := root.Validate(context.TODO(), validationConfig, sharedPool) + validationResult, _ := validation.Validate(context.TODO(), root.Entry, validationConfig, sharedPool) // check if errors are received // If so, join them and return the cumulated errors @@ -1899,7 +1894,7 @@ func Test_Validation_MultiKey_Pattern(t *testing.T) { ts1, ) - _, err = root.AddUpdateRecursive(ctx, p1, u1, flagsNew) + _, err = ops.AddUpdateRecursive(ctx, root.Entry, p1, u1, testhelper.FlagsNew) if err != nil { t.Fatal(err) } @@ -1922,7 +1917,7 @@ func Test_Validation_MultiKey_Pattern(t *testing.T) { ts1, ) - _, err = root.AddUpdateRecursive(ctx, p2, u2, flagsNew) + _, err = ops.AddUpdateRecursive(ctx, root.Entry, p2, u2, testhelper.FlagsNew) if err != nil { t.Fatal(err) } @@ -1933,7 +1928,7 @@ func Test_Validation_MultiKey_Pattern(t *testing.T) { } sharedPool := pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0)) - validationResult, _ := root.Validate(context.TODO(), validationConfig, sharedPool) + validationResult, _ := validation.Validate(context.TODO(), root.Entry, validationConfig, sharedPool) // Should have no errors - all keys match their respective patterns if validationResult.HasErrors() { @@ -1975,7 +1970,7 @@ func Test_Validation_MultiKey_Pattern(t *testing.T) { // Pattern validation happens during AddUpdateRecursive (in checkAndCreateKeysAsLeafs) // so we expect an error here - _, err = root.AddUpdateRecursive(ctx, p1, u1, flagsNew) + _, err = ops.AddUpdateRecursive(ctx, root.Entry, p1, u1, testhelper.FlagsNew) if err == nil { t.Fatal("expected error for owner pattern mismatch, but got none") } @@ -2019,7 +2014,7 @@ func Test_Validation_MultiKey_Pattern(t *testing.T) { // Pattern validation happens during AddUpdateRecursive (in checkAndCreateKeysAsLeafs) // so we expect an error here - _, err = root.AddUpdateRecursive(ctx, p1, u1, flagsNew) + _, err = ops.AddUpdateRecursive(ctx, root.Entry, p1, u1, testhelper.FlagsNew) if err == nil { t.Fatal("expected error for next-hop pattern mismatch, but got none") } @@ -2051,7 +2046,7 @@ func Test_RevertNonRevertive(t *testing.T) { { name: "revertive path with ignored key", running: func() (importer.ImportConfigAdapter, error) { - c := config1() + c := testhelper.Config1() c.Interface["ethernet-1/1"].Description = ygot.String("test") c.NetworkInstance["default"].Description = ygot.String("test") @@ -2069,7 +2064,7 @@ func Test_RevertNonRevertive(t *testing.T) { return jsonImporter.NewJsonTreeImporter(jConf, RunningIntentName, RunningValuesPrio, false), err }, existing: func() ([]importer.ImportConfigAdapter, error) { - c := config1() + c := testhelper.Config1() sConf, err := ygot.EmitJSON(c, &ygot.EmitJSONConfig{ Format: ygot.RFC7951, SkipValidation: false}, @@ -2094,7 +2089,7 @@ func Test_RevertNonRevertive(t *testing.T) { { name: "revertive path /", running: func() (importer.ImportConfigAdapter, error) { - c := config1() + c := testhelper.Config1() c.Interface["ethernet-1/1"].Description = ygot.String("test") c.NetworkInstance["default"].Description = ygot.String("test") @@ -2112,7 +2107,7 @@ func Test_RevertNonRevertive(t *testing.T) { return jsonImporter.NewJsonTreeImporter(jConf, RunningIntentName, RunningValuesPrio, false), err }, existing: func() ([]importer.ImportConfigAdapter, error) { - c := config1() + c := testhelper.Config1() sConf, err := ygot.EmitJSON(c, &ygot.EmitJSONConfig{ Format: ygot.RFC7951, SkipValidation: false}, @@ -2137,9 +2132,9 @@ func Test_RevertNonRevertive(t *testing.T) { { name: "revertive path multiple intents, with ignored key", running: func() (importer.ImportConfigAdapter, error) { - c := config1() + c := testhelper.Config1() c.Patterntest = nil - err := ygot.MergeStructInto(c, config2()) + err := ygot.MergeStructInto(c, testhelper.Config2()) if err != nil { return nil, err } @@ -2159,7 +2154,7 @@ func Test_RevertNonRevertive(t *testing.T) { return jsonImporter.NewJsonTreeImporter(jConf, RunningIntentName, RunningValuesPrio, false), err }, existing: func() ([]importer.ImportConfigAdapter, error) { - c := config1() + c := testhelper.Config1() sConf, err := ygot.EmitJSON(c, &ygot.EmitJSONConfig{ Format: ygot.RFC7951, @@ -2174,7 +2169,7 @@ func Test_RevertNonRevertive(t *testing.T) { c1Importer := jsonImporter.NewJsonTreeImporter(jConf, owner1, owner1Prio, true) - c2 := config2() + c2 := testhelper.Config2() c2.Interface["ethernet-1/2"].Description = ygot.String("test") sConf, err = ygot.EmitJSON(c2, &ygot.EmitJSONConfig{ Format: ygot.RFC7951, @@ -2259,7 +2254,7 @@ func Test_RevertNonRevertive(t *testing.T) { } t.Logf("Deletes: %v", deletes) - j, err := root.ToJson(true) + j, err := ops.ToJson(ctx, root.Entry, true) if err != nil { t.Fatalf("failed to convert to JSON: %v", err) } diff --git a/pkg/tree/importer/proto/proto_tree_importer_test.go b/pkg/tree/importer/proto/proto_tree_importer_test.go index 910f3797..57c535f5 100644 --- a/pkg/tree/importer/proto/proto_tree_importer_test.go +++ b/pkg/tree/importer/proto/proto_tree_importer_test.go @@ -12,6 +12,7 @@ import ( "github.com/sdcio/data-server/pkg/pool" "github.com/sdcio/data-server/pkg/tree" jimport "github.com/sdcio/data-server/pkg/tree/importer/json" + "github.com/sdcio/data-server/pkg/tree/ops" "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils/testhelper" "go.uber.org/mock/gomock" @@ -142,7 +143,7 @@ func TestProtoTreeImporter(t *testing.T) { } t.Log(root.String()) - protoIntent, err := root.TreeExport("owner1", 5) + protoIntent, err := ops.TreeExport(root.Entry, "owner1", 5) if err != nil { t.Error(err) } diff --git a/pkg/tree/importer/xml/xml_tree_importer_test.go b/pkg/tree/importer/xml/xml_tree_importer_test.go index 10cf987f..28340f45 100644 --- a/pkg/tree/importer/xml/xml_tree_importer_test.go +++ b/pkg/tree/importer/xml/xml_tree_importer_test.go @@ -10,6 +10,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/sdcio/data-server/pkg/pool" "github.com/sdcio/data-server/pkg/tree" + "github.com/sdcio/data-server/pkg/tree/ops" "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils" "github.com/sdcio/data-server/pkg/utils/testhelper" @@ -111,7 +112,7 @@ func TestXmlTreeImporter(t *testing.T) { t.Error(err) } - result, err := root.ToXML(false, false, false, false) + result, err := ops.ToXML(ctx, root.Entry, false, false, false, false) if err != nil { t.Fatal(err) } diff --git a/pkg/tree/init.go b/pkg/tree/init.go new file mode 100644 index 00000000..91bf6900 --- /dev/null +++ b/pkg/tree/init.go @@ -0,0 +1,15 @@ +package tree + +import ( + "context" + + "github.com/sdcio/data-server/pkg/tree/api" +) + +func init() { + // Register the NewEntry factory with the api package + // This allows processors to create entries without importing the tree package directly + api.RegisterEntryFactory(func(ctx context.Context, parent api.Entry, pathElemName string, tc api.TreeContext) (api.Entry, error) { + return NewEntry(ctx, parent, pathElemName, tc) + }) +} diff --git a/pkg/tree/ops/checkandcreatekeysasleafs.go b/pkg/tree/ops/checkandcreatekeysasleafs.go new file mode 100644 index 00000000..07595d43 --- /dev/null +++ b/pkg/tree/ops/checkandcreatekeysasleafs.go @@ -0,0 +1,94 @@ +package ops + +import ( + "cmp" + "context" + "slices" + + "github.com/sdcio/data-server/pkg/tree/api" + "github.com/sdcio/data-server/pkg/tree/types" + sdcpb "github.com/sdcio/sdc-protos/sdcpb" +) + +func CheckAndCreateKeysAsLeafs(ctx context.Context, e api.Entry, intentName string, prio int32, insertFlag *types.UpdateInsertFlags) error { + // keys themselfes do not have a schema attached. + // keys must be added to the last keys level, since that is carrying the list elements data + // hence if the entry has a schema attached, there is nothing to be done, return. + if e.GetSchema() != nil { + return nil + } + + // get the first ancestor with a schema and how many levels up that is + ancestor, levelsUp := GetFirstAncestorWithSchema(e) + + // retrieve the container schema + ancestorContainerSchema := ancestor.GetSchema().GetContainer() + // if it is not a container, return + if ancestorContainerSchema == nil { + return nil + } + + // if we're in the last level of keys, then we need to add the defaults + if len(ancestorContainerSchema.Keys) == levelsUp { + keySorted := make([]*sdcpb.LeafSchema, 0, len(ancestor.GetSchema().GetContainer().Keys)) + // add key leafschemas to slice + keySorted = append(keySorted, ancestor.GetSchema().GetContainer().Keys...) + // sort keySorted slice + slices.SortFunc(keySorted, func(a, b *sdcpb.LeafSchema) int { + return cmp.Compare(b.Name, a.Name) + }) + + // iterate through the keys + var item api.Entry = e + + // construct the key path + // doing so outside the loop to reuse + path := &sdcpb.Path{ + IsRootBased: false, + } + + for _, k := range keySorted { + child, entryExists := e.GetChildMap().GetEntry(k.Name) + // if the key Leaf exists continue with next key + if entryExists { + // if it exists, we need to check that the entry for the owner exists. + lvs := GetByOwner(child, intentName) + if len(lvs) > 0 { + lvs[0].DropDeleteFlag() + // continue with parent Entry BEFORE continuing the loop + item = item.GetParent() + continue + } + } + + // convert the key value to the schema defined Typed_Value + tv, err := sdcpb.TVFromString(k.GetType(), item.PathName(), 0) + if err != nil { + return err + } + if !entryExists { + // create a new entry + child, err = api.NewEntry(ctx, e, k.Name, e.GetTreeContext()) + + if err != nil { + return err + } + } + // add the new child entry to s + err = e.AddChild(ctx, child) + if err != nil { + return err + } + + // Add the update to the tree + _, err = AddUpdateRecursive(ctx, child, path, types.NewUpdate(nil, tv, prio, intentName, 0), insertFlag) + if err != nil { + return err + } + + // continue with parent Entry + item = item.GetParent() + } + } + return nil +} diff --git a/pkg/tree/ops/containsonlydefaults.go b/pkg/tree/ops/containsonlydefaults.go new file mode 100644 index 00000000..c921f4a0 --- /dev/null +++ b/pkg/tree/ops/containsonlydefaults.go @@ -0,0 +1,46 @@ +package ops + +import ( + "context" + "slices" + + "github.com/sdcio/data-server/pkg/tree/api" + "github.com/sdcio/data-server/pkg/tree/consts" + "github.com/sdcio/data-server/pkg/tree/types" +) + +// containsOnlyDefaults checks for presence containers, if only default values are present, +// such that the Entry should also be treated as a presence container +func ContainsOnlyDefaults(ctx context.Context, e api.Entry) bool { + // if no schema is present, we must be in a key level + if e.GetSchema() == nil { + return false + } + contSchema := e.GetSchema().GetContainer() + if contSchema == nil { + return false + } + + // only if length of childs is (more) compared to the number of + // attributes carrying defaults, the presence condition can be met + if len(e.GetChilds(types.DescendMethodAll)) > len(contSchema.ChildsWithDefaults) { + return false + } + for k, v := range e.GetChilds(types.DescendMethodAll) { + // check if child name is part of ChildsWithDefaults + if !slices.Contains(contSchema.ChildsWithDefaults, k) { + return false + } + // check if the value is the default value + le, err := v.GetHighestPrecedenceLeafValue(ctx) + if err != nil { + return false + } + // if the owner is not Default return false + if le.Owner() != consts.DefaultsIntentName { + return false + } + } + + return true +} diff --git a/pkg/tree/default_value.go b/pkg/tree/ops/default_value.go similarity index 99% rename from pkg/tree/default_value.go rename to pkg/tree/ops/default_value.go index eb151a8a..30dd2478 100644 --- a/pkg/tree/default_value.go +++ b/pkg/tree/ops/default_value.go @@ -1,4 +1,4 @@ -package tree +package ops import ( "context" diff --git a/pkg/tree/default_value_test.go b/pkg/tree/ops/default_value_test.go similarity index 95% rename from pkg/tree/default_value_test.go rename to pkg/tree/ops/default_value_test.go index c07872da..74484c58 100644 --- a/pkg/tree/default_value_test.go +++ b/pkg/tree/ops/default_value_test.go @@ -1,4 +1,4 @@ -package tree +package ops_test import ( "context" @@ -8,6 +8,7 @@ import ( schemaClient "github.com/sdcio/data-server/pkg/datastore/clients/schema" "github.com/sdcio/data-server/pkg/tree/consts" + "github.com/sdcio/data-server/pkg/tree/ops" "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils/testhelper" sdcpb "github.com/sdcio/sdc-protos/sdcpb" @@ -90,7 +91,7 @@ func TestDefaultValueExists(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := DefaultValueExists(tt.schemaElem(t)); got != tt.want { + if got := ops.DefaultValueExists(tt.schemaElem(t)); got != tt.want { t.Errorf("DefaultValueExists() = %v, want %v", got, tt.want) } }) @@ -177,7 +178,7 @@ func TestDefaultValueRetrieve(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - val, err := DefaultValueRetrieve(ctx, tt.schemaElem(t), &sdcpb.Path{}) + val, err := ops.DefaultValueRetrieve(ctx, tt.schemaElem(t), &sdcpb.Path{}) if tt.wanterr { if err == nil { t.Fatalf("expected err, got non") diff --git a/pkg/tree/ops/deletebranch.go b/pkg/tree/ops/deletebranch.go new file mode 100644 index 00000000..2fb6be28 --- /dev/null +++ b/pkg/tree/ops/deletebranch.go @@ -0,0 +1,69 @@ +package ops + +import ( + "context" + + "github.com/sdcio/data-server/pkg/tree/api" + sdcpb "github.com/sdcio/sdc-protos/sdcpb" +) + +func DeleteBranch(ctx context.Context, e api.Entry, path *sdcpb.Path, owner string) error { + var entry api.Entry + var err error + + if path == nil { + return deleteBranchInternal(ctx, e, owner) + } + + // if the relativePath is present, we need to naviagate + entry, err = e.NavigateSdcpbPath(ctx, path) + if err != nil { + return err + } + err = DeleteBranch(ctx, entry, nil, owner) + if err != nil { + return err + } + + if entry == nil { + return nil + } + + // need to remove the leafvariants down from entry. + // however if the path points to a key, which is in fact getting deleted + // we also need to remove the key, which is the parent. Thats why we do it in this loop + // which is, forwarding entry to entry.GetParent() as a last step and depending on the remains + // return continuing to perform the delete forther up in the tree + // with remains initially set to false, we initially call DeleteSubtree on the referenced entry. + for entry.CanDeleteBranch(false) { + // forward the entry pointer to the parent + // depending on the remains var the DeleteSubtree is again called on that parent entry + entry = entry.GetParent() + if entry == nil { + // we made it all the way up to the root. So we have to return. + return nil + } + // calling DeleteSubtree with the empty string, because it should not delete the owner from the higher level keys, + // but what it will also do is delete possibly dangling key elements in the tree + entry.DeleteCanDeleteChilds(true) + } + + return nil +} + +func deleteBranchInternal(ctx context.Context, e api.Entry, owner string) error { + // delete possibly existing leafvariants for the owner + e.GetLeafVariants().DeleteByOwner(owner) + + // recurse the call + for childName, child := range e.GetChildMap().GetAll() { + err := DeleteBranch(ctx, child, nil, owner) + if err != nil { + return err + } + if child.CanDeleteBranch(false) { + e.GetChildMap().DeleteChild(childName) + } + } + return nil +} diff --git a/pkg/tree/ops/get_by_owner.go b/pkg/tree/ops/getbyowner.go similarity index 78% rename from pkg/tree/ops/get_by_owner.go rename to pkg/tree/ops/getbyowner.go index 59d188c0..10ae3255 100644 --- a/pkg/tree/ops/get_by_owner.go +++ b/pkg/tree/ops/getbyowner.go @@ -2,13 +2,12 @@ package ops import ( "github.com/sdcio/data-server/pkg/tree/api" - opsinterface "github.com/sdcio/data-server/pkg/tree/ops/interface" "github.com/sdcio/data-server/pkg/tree/types" ) // GetByOwner returns all the LeafEntries that belong to a certain owner. -func GetByOwner(e opsinterface.Entry, owner string) api.LeafVariantSlice { +func GetByOwner(e api.Entry, owner string) api.LeafVariantSlice { result := api.LeafVariantSlice{} result = getByOwnerInternal(e, owner, result) return result @@ -17,7 +16,7 @@ func GetByOwner(e opsinterface.Entry, owner string) api.LeafVariantSlice { // getByOwnerInternal is the internal function that performs the actual retrieval of the LeafEntries by owner. It is called recursively to traverse the tree. // It takes an additional result parameter that is used to accumulate the results during the recursive traversal. Since that LeafVariantSlice might grow during the traversal, // it is returned as a new slice to ensure that the changes are reflected in the caller. -func getByOwnerInternal(e opsinterface.Entry, owner string, result api.LeafVariantSlice) api.LeafVariantSlice { +func getByOwnerInternal(e api.Entry, owner string, result api.LeafVariantSlice) api.LeafVariantSlice { lv := e.GetLeafVariants().GetByOwner(owner) if lv != nil { result = append(result, lv) diff --git a/pkg/tree/ops/getdeviations.go b/pkg/tree/ops/getdeviations.go new file mode 100644 index 00000000..2db5c6b9 --- /dev/null +++ b/pkg/tree/ops/getdeviations.go @@ -0,0 +1,34 @@ +package ops + +import ( + "context" + + "github.com/sdcio/data-server/pkg/tree/api" + "github.com/sdcio/data-server/pkg/tree/types" +) + +func GetDeviations(ctx context.Context, e api.Entry, ch chan<- *types.DeviationEntry, activeCase bool) { + evalLeafvariants := true + // if s is a presence container but has active childs, it should not be treated as a presence + // container, hence the leafvariants should not be processed. For presence container with + // childs the TypedValue.empty_val in the presence container is irrelevant. + if e.GetSchema().GetContainer().GetIsPresence() && len(e.GetChilds(types.DescendMethodActiveChilds)) > 0 { + evalLeafvariants = false + } + + if evalLeafvariants { + // calculate Deviation on the LeafVariants + e.GetLeafVariants().GetDeviations(ctx, ch, activeCase) + } + + // get all active childs + activeChilds := e.GetChilds(types.DescendMethodActiveChilds) + + // iterate through all childs + for cName, c := range e.GetChildMap().GetAll() { + // check if c is a active child (choice / case) + _, isActiveChild := activeChilds[cName] + // recurse the call + GetDeviations(ctx, c, ch, isActiveChild) + } +} diff --git a/pkg/tree/ops/getfirstancesterwithschema.go b/pkg/tree/ops/getfirstancesterwithschema.go new file mode 100644 index 00000000..7c4756e9 --- /dev/null +++ b/pkg/tree/ops/getfirstancesterwithschema.go @@ -0,0 +1,23 @@ +package ops + +import "github.com/sdcio/data-server/pkg/tree/api" + +// GetAncestorSchema returns the schema of the parent node if the schema is set. +// if the parent has no schema (is a key element in the tree) it will continue up the tree. +// the level of traversal is indicated via the level return value +func GetFirstAncestorWithSchema(e api.Entry) (api.Entry, int) { + level := 0 + current := e + + // traverse up the tree until we find an ancestor with a schema + for !current.IsRoot() { + current = current.GetParent() + level++ + if current.GetSchema() != nil { + return current, level + } + } + + // reached root without finding a schema + return nil, 0 +} diff --git a/pkg/tree/ops/getorcreatechilds.go b/pkg/tree/ops/getorcreatechilds.go new file mode 100644 index 00000000..ab0cee6c --- /dev/null +++ b/pkg/tree/ops/getorcreatechilds.go @@ -0,0 +1,116 @@ +package ops + +import ( + "context" + "sort" + + "github.com/sdcio/data-server/pkg/tree/api" + "github.com/sdcio/data-server/pkg/tree/types" + sdcpb "github.com/sdcio/sdc-protos/sdcpb" +) + +func GetOrCreateChilds(ctx context.Context, e api.Entry, path *sdcpb.Path) (api.Entry, error) { + if path == nil || len(path.Elem) == 0 { + return e, nil + } + + var current api.Entry = e + for i, pe := range path.Elem { + // Step 1: Find or create the child for the path element name + newCurrent, exists := current.GetChilds(types.DescendMethodAll)[pe.Name] + if !exists { + var err error + child, err := api.NewEntry(ctx, current, pe.Name, e.GetTreeContext()) + if err != nil { + return nil, err + } + if err := current.AddChild(ctx, child); err != nil { + return nil, err + } + newCurrent = child + } + current = newCurrent + + // sort keys + keys := make([]string, 0, len(pe.Key)) + for key := range pe.Key { + keys = append(keys, key) + } + sort.Strings(keys) + + // Step 2: For each key, find or create the key child + for _, key := range keys { + newCurrent, exists = current.GetChilds(types.DescendMethodAll)[pe.Key[key]] + if !exists { + var err error + keyChild, err := api.NewEntry(ctx, current, pe.Key[key], e.GetTreeContext()) + if err != nil { + return nil, err + } + if err := current.AddChild(ctx, keyChild); err != nil { + return nil, err + } + newCurrent = keyChild + } + current = newCurrent + } + + // If this is the last PathElem, return the current node + if i == len(path.Elem)-1 { + return current, nil + } + } + + return current, nil +} + +// AddUpdateRecursive recursively adds the given cache.Update to the tree. Thereby creating all the entries along the path. +// if the entries along th path already exist, the existing entries are called to add the Update. +func AddUpdateRecursive(ctx context.Context, e api.Entry, path *sdcpb.Path, u *types.Update, flags *types.UpdateInsertFlags) (api.Entry, error) { + var err error + relPath := path + + if path.IsRootBased && !e.IsRoot() { + // calculate the relative path for the add + relPath, err = path.AbsToRelativePath(e.SdcpbPath()) + if err != nil { + return nil, err + } + } + return AddUpdateRecursiveInternal(ctx, e, relPath, 0, u, flags) +} + +func AddUpdateRecursiveInternal(ctx context.Context, s api.Entry, path *sdcpb.Path, idx int, u *types.Update, flags *types.UpdateInsertFlags) (api.Entry, error) { + // make sure all the keys are also present as leafs + err := CheckAndCreateKeysAsLeafs(ctx, s, u.Owner(), u.Priority(), flags) + if err != nil { + return nil, err + } + // end of path reached, add LeafEntry + // continue with recursive add otherwise + if path == nil || len(path.GetElem()) == 0 || idx >= len(path.GetElem()) { + // delegate update handling to leafVariants + s.GetLeafVariants().Add(api.NewLeafEntry(u, flags, s)) + return s, nil + } + + var e api.Entry + var x api.Entry = s + var exists bool + for name := range path.GetElem()[idx].PathElemNames() { + if e, exists = x.GetChilds(types.DescendMethodAll)[name]; !exists { + newE, err := api.NewEntry(ctx, x, name, s.GetTreeContext()) + if err != nil { + return nil, err + } + err = x.AddChild(ctx, newE) + if err != nil { + return nil, err + } + e = newE + } + x = e + } + + return AddUpdateRecursiveInternal(ctx, x, path, idx+1, u, flags) +} diff --git a/pkg/tree/ops/getroot.go b/pkg/tree/ops/getroot.go new file mode 100644 index 00000000..fe1b9f7a --- /dev/null +++ b/pkg/tree/ops/getroot.go @@ -0,0 +1,11 @@ +package ops + +import "github.com/sdcio/data-server/pkg/tree/api" + +// GetRoot returns the Trees Root Entry +func GetRoot(e api.Entry) api.Entry { + for !e.IsRoot() { + e = e.GetParent() + } + return e +} diff --git a/pkg/tree/ops/getrootbasedentrychain.go b/pkg/tree/ops/getrootbasedentrychain.go new file mode 100644 index 00000000..348ae4c8 --- /dev/null +++ b/pkg/tree/ops/getrootbasedentrychain.go @@ -0,0 +1,29 @@ +package ops + +import ( + "github.com/sdcio/data-server/pkg/tree/api" +) + +// GetRootBasedEntryChain returns all the entries starting from the root down to the actual Entry. +// The first element of the returned slice is the root, and the last element is the actual Entry. +func GetRootBasedEntryChain(current api.Entry) []api.Entry { + if current == nil { + return nil + } + + // Pre-allocate with exact size: level N means N+1 entries (including root at level 0) + // This avoids multiple allocations as append grows the slice. + size := current.GetLevel() + 1 + chain := make([]api.Entry, size) + + // Fill the slice backwards (from end to start) so it ends up in root-based order. + // This eliminates the need for a separate reverse operation. + idx := size - 1 + for current != nil { + chain[idx] = current + idx-- + current = current.GetParent() + } + + return chain +} diff --git a/pkg/tree/ops/getschemakeys.go b/pkg/tree/ops/getschemakeys.go new file mode 100644 index 00000000..3b94c834 --- /dev/null +++ b/pkg/tree/ops/getschemakeys.go @@ -0,0 +1,20 @@ +package ops + +import "github.com/sdcio/data-server/pkg/tree/api" + +// GetSchemaKeys checks for the schema of the entry, and returns the defined keys +func GetSchemaKeys(e api.Entry) []string { + if e.GetSchema() != nil { + // if the schema is a container schema, we need to process the aggregation logic + if contschema := e.GetSchema().GetContainer(); contschema != nil { + // if the level equals the amount of keys defined, we're at the right level, where the + // actual elements start (not in a key level within the tree) + var keys []string + for _, k := range contschema.GetKeys() { + keys = append(keys, k.Name) + } + return keys + } + } + return nil +} diff --git a/pkg/tree/ops/interface/opsentry.go b/pkg/tree/ops/interface/opsentry.go deleted file mode 100644 index faa607bb..00000000 --- a/pkg/tree/ops/interface/opsentry.go +++ /dev/null @@ -1,11 +0,0 @@ -package opsinterface - -import ( - "github.com/sdcio/data-server/pkg/tree/api" - "github.com/sdcio/data-server/pkg/tree/types" -) - -type Entry interface { - GetChilds(types.DescendMethod) api.EntryMap - GetLeafVariants() *api.LeafVariants -} diff --git a/pkg/tree/json.go b/pkg/tree/ops/json.go similarity index 70% rename from pkg/tree/json.go rename to pkg/tree/ops/json.go index baae11c6..1cb85b45 100644 --- a/pkg/tree/json.go +++ b/pkg/tree/ops/json.go @@ -1,6 +1,7 @@ -package tree +package ops import ( + "context" "fmt" "slices" @@ -10,8 +11,8 @@ import ( sdcpb "github.com/sdcio/sdc-protos/sdcpb" ) -func (s *sharedEntryAttributes) ToJson(onlyNewOrUpdated bool) (any, error) { - result, err := s.ToJsonInternal(onlyNewOrUpdated, false) +func ToJson(ctx context.Context, e api.Entry, onlyNewOrUpdated bool) (any, error) { + result, err := toJsonInternal(ctx, e, onlyNewOrUpdated, false) if err != nil { return nil, err } @@ -21,8 +22,8 @@ func (s *sharedEntryAttributes) ToJson(onlyNewOrUpdated bool) (any, error) { return result, err } -func (s *sharedEntryAttributes) ToJsonIETF(onlyNewOrUpdated bool) (any, error) { - result, err := s.ToJsonInternal(onlyNewOrUpdated, true) +func ToJsonIETF(ctx context.Context, e api.Entry, onlyNewOrUpdated bool) (any, error) { + result, err := toJsonInternal(ctx, e, onlyNewOrUpdated, true) if err != nil { return nil, err } @@ -36,18 +37,18 @@ func (s *sharedEntryAttributes) ToJsonIETF(onlyNewOrUpdated bool) (any, error) { // If the ietf parameter is set to true, JSON_IETF encoding is used. // The actualPrefix is used only for the JSON_IETF encoding and can be ignored for JSON // In the initial / users call with ietf == true, actualPrefix should be set to "" -func (s *sharedEntryAttributes) ToJsonInternal(onlyNewOrUpdated bool, ietf bool) (any, error) { - switch s.schema.GetSchema().(type) { +func toJsonInternal(ctx context.Context, e api.Entry, onlyNewOrUpdated bool, ietf bool) (any, error) { + switch e.GetSchema().GetSchema().(type) { case nil: // we're operating on a key level, no schema attached, but the // ancestor is a list with keys. result := map[string]any{} - for key, c := range s.GetChilds(types.DescendMethodActiveChilds) { - ancest, _ := s.GetFirstAncestorWithSchema() + for key, c := range e.GetChilds(types.DescendMethodActiveChilds) { + ancest, _ := GetFirstAncestorWithSchema(e) prefixedKey := jsonGetIetfPrefixConditional(key, c, ancest, ietf) // recurse the call - js, err := c.ToJsonInternal(onlyNewOrUpdated, ietf) + js, err := toJsonInternal(ctx, c, onlyNewOrUpdated, ietf) if err != nil { return nil, err } @@ -58,24 +59,24 @@ func (s *sharedEntryAttributes) ToJsonInternal(onlyNewOrUpdated bool, ietf bool) if len(result) == 0 { return nil, nil } - jsonAddKeyElements(s, result) + jsonAddKeyElements(e, result) return result, nil case *sdcpb.SchemaElem_Container: switch { - case len(s.GetSchemaKeys()) > 0: + case len(GetSchemaKeys(e)) > 0: // if the container contains keys, then it is a list // hence must be rendered as an array - childs, err := s.FilterChilds(nil) + childs, err := e.FilterChilds(nil) if err != nil { return nil, err } // Apply sorting of child entries - slices.SortFunc(childs, getListEntrySortFunc(s)) + slices.SortFunc(childs, getListEntrySortFunc(e)) result := make([]any, 0, len(childs)) for _, c := range childs { - j, err := c.ToJsonInternal(onlyNewOrUpdated, ietf) + j, err := toJsonInternal(ctx, c, onlyNewOrUpdated, ietf) if err != nil { return nil, err } @@ -87,14 +88,14 @@ func (s *sharedEntryAttributes) ToJsonInternal(onlyNewOrUpdated bool, ietf bool) return nil, nil } return result, nil - case s.schema.GetContainer().IsPresence && s.containsOnlyDefaults(): + case e.GetSchema().GetContainer().IsPresence && ContainsOnlyDefaults(ctx, e): // Presence container without any childs if onlyNewOrUpdated { // presence containers have leafvariantes with typedValue_Empty, so check that - if s.leafVariants.ShouldDelete() { + if e.GetLeafVariants().ShouldDelete() { return nil, nil } - le := s.leafVariants.GetHighestPrecedence(false, false, false) + le := e.GetLeafVariants().GetHighestPrecedence(false, false, false) if le == nil || onlyNewOrUpdated && !(le.IsNew || le.IsUpdated) { return nil, nil } @@ -103,9 +104,9 @@ func (s *sharedEntryAttributes) ToJsonInternal(onlyNewOrUpdated bool, ietf bool) default: // otherwise this is a map result := map[string]any{} - for key, c := range s.GetChilds(types.DescendMethodActiveChilds) { - prefixedKey := jsonGetIetfPrefixConditional(key, c, s, ietf) - js, err := c.ToJsonInternal(onlyNewOrUpdated, ietf) + for key, c := range e.GetChilds(types.DescendMethodActiveChilds) { + prefixedKey := jsonGetIetfPrefixConditional(key, c, e, ietf) + js, err := toJsonInternal(ctx, c, onlyNewOrUpdated, ietf) if err != nil { return nil, err } @@ -120,16 +121,16 @@ func (s *sharedEntryAttributes) ToJsonInternal(onlyNewOrUpdated bool, ietf bool) } case *sdcpb.SchemaElem_Leaflist, *sdcpb.SchemaElem_Field: - if s.leafVariants.CanDelete() { + if e.GetLeafVariants().CanDelete() { return nil, nil } - le := s.leafVariants.GetHighestPrecedence(onlyNewOrUpdated, false, false) + le := e.GetLeafVariants().GetHighestPrecedence(onlyNewOrUpdated, false, false) if le == nil { return nil, nil } return utils.GetJsonValue(le.Value(), ietf) } - return nil, fmt.Errorf("unable to convert to json (%s)", s.SdcpbPath().ToXPath(false)) + return nil, fmt.Errorf("unable to convert to json (%s)", e.SdcpbPath().ToXPath(false)) } // jsonAddIetfPrefixConditional adds the module name @@ -150,9 +151,9 @@ func jsonGetIetfPrefixConditional(key string, a api.Entry, b api.Entry, ietf boo func jsonAddKeyElements(s api.Entry, dict map[string]any) { // retrieve the parent schema, we need to extract the key names // values are the tree level names - parentSchema, levelsUp := s.GetFirstAncestorWithSchema() + parentSchema, levelsUp := GetFirstAncestorWithSchema(s) // from the parent we get the keys as slice - schemaKeys := parentSchema.GetSchemaKeys() + schemaKeys := GetSchemaKeys(parentSchema) var treeElem api.Entry = s // the keys do match the levels up in the tree in reverse order // hence we init i with levelUp and count down diff --git a/pkg/tree/json_test.go b/pkg/tree/ops/json_test.go similarity index 65% rename from pkg/tree/json_test.go rename to pkg/tree/ops/json_test.go index 289731eb..f3925f44 100644 --- a/pkg/tree/json_test.go +++ b/pkg/tree/ops/json_test.go @@ -1,4 +1,4 @@ -package tree +package ops_test import ( "context" @@ -10,7 +10,9 @@ import ( "github.com/google/go-cmp/cmp" "github.com/openconfig/ygot/ygot" "github.com/sdcio/data-server/pkg/pool" + "github.com/sdcio/data-server/pkg/tree" "github.com/sdcio/data-server/pkg/tree/consts" + "github.com/sdcio/data-server/pkg/tree/ops" "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils" "github.com/sdcio/data-server/pkg/utils/testhelper" @@ -35,12 +37,12 @@ func TestToJsonTable(t *testing.T) { ietf: false, onlyNewOrUpdated: false, runningConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, existingConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, expected: `{ "choices": { @@ -86,8 +88,8 @@ func TestToJsonTable(t *testing.T) { ietf: true, onlyNewOrUpdated: false, existingConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, expected: `{ "sdcio_model:patterntest": "hallo 00", @@ -133,12 +135,12 @@ func TestToJsonTable(t *testing.T) { ietf: false, onlyNewOrUpdated: true, runningConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, existingConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, expected: `{}`, @@ -148,12 +150,12 @@ func TestToJsonTable(t *testing.T) { ietf: true, onlyNewOrUpdated: true, existingConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, runningConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, expected: `{}`, @@ -163,16 +165,16 @@ func TestToJsonTable(t *testing.T) { ietf: false, onlyNewOrUpdated: true, existingConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, runningConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, newConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config2() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config2() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, expected: `{ "interface": [ @@ -205,16 +207,16 @@ func TestToJsonTable(t *testing.T) { ietf: true, onlyNewOrUpdated: true, existingConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, runningConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, newConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config2() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config2() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, expected: `{ "sdcio_model:patterntest": "hallo 99", @@ -247,17 +249,17 @@ func TestToJsonTable(t *testing.T) { ietf: true, onlyNewOrUpdated: true, existingConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, runningConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, newConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() + c := testhelper.Config1() c.Interface["ethernet-1/1"].Mtu = ygot.Uint16(1500) - return expandUpdateFromConfig(ctx, c, converter) + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, expected: `{ "sdcio_model_if:interface": [ @@ -272,12 +274,12 @@ func TestToJsonTable(t *testing.T) { name: "JSON - presence", onlyNewOrUpdated: true, existingConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, runningConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, expected: `{ "network-instance": [ @@ -290,8 +292,8 @@ func TestToJsonTable(t *testing.T) { ] }`, newConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - upds, err := expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + upds, err := testhelper.ExpandUpdateFromConfig(ctx, c, converter) if err != nil { return nil, err } @@ -312,17 +314,17 @@ func TestToJsonTable(t *testing.T) { name: "JSON - empty", onlyNewOrUpdated: true, existingConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, runningConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, newConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() + c := testhelper.Config1() - upds, err := expandUpdateFromConfig(ctx, c, converter) + upds, err := testhelper.ExpandUpdateFromConfig(ctx, c, converter) if err != nil { return nil, err } @@ -338,20 +340,20 @@ func TestToJsonTable(t *testing.T) { ietf: true, onlyNewOrUpdated: true, existingConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, runningConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, newConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() + c := testhelper.Config1() c.Identityref = &sdcio_schema.SdcioModel_Identityref{ CryptoA: sdcio_schema.SdcioModelIdentityBase_CryptoAlg_des3, CryptoB: sdcio_schema.SdcioModelIdentityBase_CryptoAlg_otherAlgo, } - return expandUpdateFromConfig(ctx, c, converter) + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, expected: `{ "sdcio_model_identity:identityref": { @@ -381,8 +383,8 @@ func TestToJsonTable(t *testing.T) { ctx := context.Background() - tc := NewTreeContext(scb, pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0))) - root, err := NewTreeRoot(ctx, tc) + tc := tree.NewTreeContext(scb, pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0))) + root, err := tree.NewTreeRoot(ctx, tc) if err != nil { t.Fatal(err) } @@ -394,7 +396,7 @@ func TestToJsonTable(t *testing.T) { t.Error(err) } - err = addToRoot(ctx, root, updsRunning, flagsOld, consts.RunningIntentName, consts.RunningValuesPrio) + err = testhelper.AddToRoot(ctx, root.Entry, updsRunning, flagsOld, consts.RunningIntentName, consts.RunningValuesPrio) if err != nil { t.Fatal(err) } @@ -405,7 +407,7 @@ func TestToJsonTable(t *testing.T) { t.Error(err) } - err = addToRoot(ctx, root, updsExisting, flagsOld, owner, 5) + err = testhelper.AddToRoot(ctx, root.Entry, updsExisting, flagsOld, owner, 5) if err != nil { t.Fatal(err) } @@ -415,7 +417,7 @@ func TestToJsonTable(t *testing.T) { if err != nil { t.Error(err) } - err = addToRoot(ctx, root, updsNew, flagsNew, owner, 5) + err = testhelper.AddToRoot(ctx, root.Entry, updsNew, flagsNew, owner, 5) if err != nil { t.Fatal(err) } @@ -430,12 +432,12 @@ func TestToJsonTable(t *testing.T) { var jsonStruct any if tt.ietf { - jsonStruct, err = root.ToJsonIETF(tt.onlyNewOrUpdated) + jsonStruct, err = ops.ToJsonIETF(ctx, root.Entry, tt.onlyNewOrUpdated) if err != nil { t.Fatal(err) } } else { - jsonStruct, err = root.ToJson(tt.onlyNewOrUpdated) + jsonStruct, err = ops.ToJson(ctx, root.Entry, tt.onlyNewOrUpdated) if err != nil { t.Fatal(err) } @@ -460,107 +462,3 @@ func TestToJsonTable(t *testing.T) { }) } } - -func config1() *sdcio_schema.Device { - return &sdcio_schema.Device{ - Interface: map[string]*sdcio_schema.SdcioModel_Interface{ - "ethernet-1/1": { - AdminState: sdcio_schema.SdcioModelIf_AdminState_enable, - Description: ygot.String("Foo"), - Name: ygot.String("ethernet-1/1"), - Subinterface: map[uint32]*sdcio_schema.SdcioModel_Interface_Subinterface{ - 0: { - Description: ygot.String("Subinterface 0"), - Type: sdcio_schema.SdcioModelCommon_SiType_routed, - Index: ygot.Uint32(0), - }, - }, - }, - }, - Choices: &sdcio_schema.SdcioModel_Choices{ - Case1: &sdcio_schema.SdcioModel_Choices_Case1{ - CaseElem: &sdcio_schema.SdcioModel_Choices_Case1_CaseElem{ - Elem: ygot.String("foocaseval"), - }, - }, - }, - Leaflist: &sdcio_schema.SdcioModel_Leaflist{ - Entry: []string{ - "foo", - "bar", - }, - }, - Patterntest: ygot.String("hallo 00"), - NetworkInstance: map[string]*sdcio_schema.SdcioModel_NetworkInstance{ - "default": { - AdminState: sdcio_schema.SdcioModelNi_AdminState_disable, - Description: ygot.String("Default NI"), - Type: sdcio_schema.SdcioModelNi_NiType_default, - Name: ygot.String("default"), - }, - }, - } -} - -func config2() *sdcio_schema.Device { - return &sdcio_schema.Device{ - Interface: map[string]*sdcio_schema.SdcioModel_Interface{ - "ethernet-1/2": { - AdminState: sdcio_schema.SdcioModelIf_AdminState_enable, - Description: ygot.String("Foo"), - Name: ygot.String("ethernet-1/2"), - Subinterface: map[uint32]*sdcio_schema.SdcioModel_Interface_Subinterface{ - 5: { - Description: ygot.String("Subinterface 5"), - Type: sdcio_schema.SdcioModelCommon_SiType_routed, - Index: ygot.Uint32(5), - }, - }, - }, - }, - Patterntest: ygot.String("hallo 99"), - NetworkInstance: map[string]*sdcio_schema.SdcioModel_NetworkInstance{ - "other": { - AdminState: sdcio_schema.SdcioModelNi_AdminState_enable, - Description: ygot.String("Other NI"), - Type: sdcio_schema.SdcioModelNi_NiType_ip_vrf, - Name: ygot.String("other"), - }, - }, - } - -} - -func expandUpdateFromConfig(ctx context.Context, conf *sdcio_schema.Device, converter *utils.Converter) ([]*sdcpb.Update, error) { - if conf == nil { - return nil, nil - } - - strJson, err := ygot.EmitJSON(conf, &ygot.EmitJSONConfig{ - Format: ygot.RFC7951, - SkipValidation: true, - }) - if err != nil { - return nil, err - } - - return converter.ExpandUpdate(ctx, - &sdcpb.Update{ - Path: &sdcpb.Path{ - Elem: []*sdcpb.PathElem{}, - }, - Value: &sdcpb.TypedValue{Value: &sdcpb.TypedValue_JsonVal{JsonVal: []byte(strJson)}}, - }) -} - -func addToRoot(ctx context.Context, root *RootEntry, updates []*sdcpb.Update, flags *types.UpdateInsertFlags, owner string, prio int32) error { - for _, upd := range updates { - cacheUpd := types.NewUpdate(nil, upd.Value, prio, owner, 0) - - _, err := root.AddUpdateRecursive(ctx, upd.GetPath(), cacheUpd, flags) - if err != nil { - return err - } - } - return nil -} diff --git a/pkg/tree/ops/proto.go b/pkg/tree/ops/proto.go new file mode 100644 index 00000000..973cd9e5 --- /dev/null +++ b/pkg/tree/ops/proto.go @@ -0,0 +1,25 @@ +package ops + +import ( + "context" + + "github.com/sdcio/data-server/pkg/tree/api" + "github.com/sdcio/data-server/pkg/tree/types" + sdcpb "github.com/sdcio/sdc-protos/sdcpb" +) + +func ToProtoUpdates(ctx context.Context, e api.Entry, onlyNewOrUpdated bool) ([]*sdcpb.Update, error) { + result := api.LeafVariantSlice{} + result = e.GetHighestPrecedence(result, onlyNewOrUpdated, false, true) + return result.ToSdcpbUpdateSlice(), nil +} + +func ToProtoDeletes(ctx context.Context, e api.Entry) ([]*sdcpb.Path, error) { + result := []types.DeleteEntry{} + deletes, err := e.GetDeletes(result, true) + if err != nil { + return nil, err + } + + return deletes.SdcpbPaths(), nil +} diff --git a/pkg/tree/ops/treeexport.go b/pkg/tree/ops/treeexport.go new file mode 100644 index 00000000..56a9030f --- /dev/null +++ b/pkg/tree/ops/treeexport.go @@ -0,0 +1,102 @@ +package ops + +import ( + "fmt" + + "github.com/sdcio/data-server/pkg/tree/api" + "github.com/sdcio/sdc-protos/tree_persist" +) + +var ( + ErrorIntentNotPresent = fmt.Errorf("intent not present") +) + +func TreeExport(e api.Entry, owner string, priority int32) (*tree_persist.Intent, error) { + treeExport, err := treeExportLevel(e, owner) + if err != nil { + return nil, err + } + + explicitDeletes := e.GetTreeContext().ExplicitDeletes().GetByIntentName(owner).ToPathSlice() + + var rootExportEntry *tree_persist.TreeElement + if len(treeExport) != 0 { + rootExportEntry = treeExport[0] + } + + if rootExportEntry != nil || len(explicitDeletes) > 0 { + return &tree_persist.Intent{ + IntentName: owner, + Root: rootExportEntry, + Priority: priority, + NonRevertive: e.GetTreeContext().NonRevertiveInfo().IsGenerallyNonRevertive(owner), + ExplicitDeletes: explicitDeletes, + }, nil + } + return nil, ErrorIntentNotPresent +} + +// treeExportLevel exports the tree starting at the given entry for the given owner. It returns a slice of TreeElements which represent the exported tree. +func treeExportLevel(e api.Entry, owner string) ([]*tree_persist.TreeElement, error) { + var lvResult []byte + var childResults []*tree_persist.TreeElement + var err error + + le := e.GetLeafVariants().GetByOwner(owner) + + if le != nil && !le.Delete { + lvResult, err = le.ValueAsBytes() + if err != nil { + return nil, err + } + } + + if len(GetSchemaKeys(e)) > 0 { + children, err := e.FilterChilds(nil) + if err != nil { + return nil, err + } + result := []*tree_persist.TreeElement{} + for _, c := range children { + childexport, err := treeExportLevel(c, owner) + if err != nil { + return nil, err + } + if len(childexport) == 0 { + // no childs belonging to the given owner + continue + } + if len(childexport) > 1 { + return nil, fmt.Errorf("unexpected value") + } + childexport[0].Name = e.PathName() + + result = append(result, childexport...) + } + if len(result) > 0 { + return result, nil + } + } else { + for _, c := range e.GetChildMap().GetAll() { + childExport, err := treeExportLevel(c, owner) + if err != nil { + return nil, err + } + if len(childExport) > 0 { + childResults = append(childResults, childExport...) + } + + } + if lvResult != nil || len(childResults) > 0 { + return []*tree_persist.TreeElement{ + { + Name: e.PathName(), + Childs: childResults, + LeafVariant: lvResult, + }, + }, nil + } + } + + return nil, nil +} diff --git a/pkg/tree/sorter.go b/pkg/tree/ops/utils.go similarity index 94% rename from pkg/tree/sorter.go rename to pkg/tree/ops/utils.go index 06fc9fde..55792e39 100644 --- a/pkg/tree/sorter.go +++ b/pkg/tree/ops/utils.go @@ -1,4 +1,4 @@ -package tree +package ops import ( "github.com/sdcio/data-server/pkg/tree/api" @@ -8,7 +8,7 @@ import ( func getListEntrySortFunc(parent api.Entry) func(a, b api.Entry) int { // return the comparison function return func(a, b api.Entry) int { - keys := parent.GetSchemaKeys() + keys := GetSchemaKeys(parent) var cmpResult int for _, v := range keys { achild, exists := a.GetChilds(types.DescendMethodAll)[v] diff --git a/pkg/tree/ops/validate.go b/pkg/tree/ops/validate.go deleted file mode 100644 index 3704f478..00000000 --- a/pkg/tree/ops/validate.go +++ /dev/null @@ -1,7 +0,0 @@ -package ops - -import opsinterface "github.com/sdcio/data-server/pkg/tree/ops/interface" - -func Validate(e opsinterface.Entry) error { - return nil -} diff --git a/pkg/tree/processor_validate.go b/pkg/tree/ops/validation/processor_validate.go similarity index 93% rename from pkg/tree/processor_validate.go rename to pkg/tree/ops/validation/processor_validate.go index 01d49ed7..7332ddb4 100644 --- a/pkg/tree/processor_validate.go +++ b/pkg/tree/ops/validation/processor_validate.go @@ -1,4 +1,4 @@ -package tree +package validation import ( "context" @@ -57,7 +57,8 @@ func (t *validateTask) Run(ctx context.Context, submit func(pool.Task) error) er } // validate the mandatory statement on this entry if t.e.RemainsToExist() { - t.e.ValidateLevel(ctx, t.parameters.resultChan, t.parameters.stats, t.parameters.vCfg) + validateLevel(ctx, t.e, t.parameters.resultChan, t.parameters.stats, t.parameters.vCfg) + for _, c := range t.e.GetChilds(types.DescendMethodActiveChilds) { submit(newValidateTask(c, t.parameters)) } diff --git a/pkg/tree/ops/validation/validate.go b/pkg/tree/ops/validation/validate.go new file mode 100644 index 00000000..9f2c2458 --- /dev/null +++ b/pkg/tree/ops/validation/validate.go @@ -0,0 +1,401 @@ +package validation + +import ( + "context" + "errors" + "fmt" + "math" + "regexp" + "slices" + "strings" + "sync" + "unicode/utf8" + + "github.com/sdcio/data-server/pkg/config" + "github.com/sdcio/data-server/pkg/pool" + "github.com/sdcio/data-server/pkg/tree/api" + "github.com/sdcio/data-server/pkg/tree/types" + "github.com/sdcio/logger" + sdcpb "github.com/sdcio/sdc-protos/sdcpb" + sdcpbutils "github.com/sdcio/sdc-protos/utils" +) + +var ( + ErrValidationError = errors.New("validation error") +) + +func Validate(ctx context.Context, e api.Entry, vCfg *config.Validation, taskpoolFactory pool.VirtualPoolFactory) (types.ValidationResults, *types.ValidationStats) { + // perform validation + // we use a channel and cumulate all the errors + validationResultEntryChan := make(chan *types.ValidationResultEntry, 10) + validationStats := types.NewValidationStats() + + // create a ValidationResult struct + validationResult := types.ValidationResults{} + + syncWait := &sync.WaitGroup{} + syncWait.Add(1) + go func() { + // read from the validationResult channel + for e := range validationResultEntryChan { + validationResult.AddEntry(e) + } + syncWait.Done() + }() + + validationProcessor := NewValidateProcessor(NewValidateProcessorConfig(validationResultEntryChan, validationStats, vCfg)) + validationProcessor.Run(taskpoolFactory, e) + close(validationResultEntryChan) + + syncWait.Wait() + return validationResult, validationStats +} + +// Validate is the highlevel function to perform validation. +// it will multiplex all the different Validations that need to happen +func validateLevel(ctx context.Context, e api.Entry, resultChan chan<- *types.ValidationResultEntry, stats *types.ValidationStats, vCfg *config.Validation) { + // validate the mandatory statement on this entry + if e.RemainsToExist() { + // TODO: Validate Enums + if !vCfg.DisabledValidators.Mandatory { + validateMandatory(ctx, e, resultChan, stats) + } + if !vCfg.DisabledValidators.Leafref { + validateLeafRefs(ctx, e, resultChan, stats) + } + if !vCfg.DisabledValidators.LeafrefMinMaxAttributes { + validateLeafListMinMaxAttributes(e, resultChan, stats) + } + if !vCfg.DisabledValidators.Pattern { + validatePattern(e, resultChan, stats) + } + if !vCfg.DisabledValidators.MustStatement { + validateMustStatements(ctx, e, resultChan, stats) + } + if !vCfg.DisabledValidators.Length { + validateLength(e, resultChan, stats) + } + if !vCfg.DisabledValidators.Range { + validateRange(e, resultChan, stats) + } + if !vCfg.DisabledValidators.MaxElements { + validateMinMaxElements(e, resultChan, stats) + } + } +} + +// validateRange int and uint types (Leaf and Leaflist) define ranges which configured values must lay in. +// validateRange does check this condition. +func validateRange(e api.Entry, resultChan chan<- *types.ValidationResultEntry, stats *types.ValidationStats) { + + // if no schema present or Field and LeafList Types do not contain any ranges, return there is nothing to check + if e.GetSchema() == nil || (len(e.GetSchema().GetField().GetType().GetRange()) == 0 && len(e.GetSchema().GetLeaflist().GetType().GetRange()) == 0) { + return + } + + lv := e.GetLeafVariants().GetHighestPrecedence(false, true, false) + if lv == nil { + return + } + + tv := lv.Update.Value() + + var tvs []*sdcpb.TypedValue + var typeSchema *sdcpb.SchemaLeafType + // ranges are defined on Leafs or LeafLists. + switch { + case len(e.GetSchema().GetField().GetType().GetRange()) != 0: + // if it is a leaf, extract the value add it as a single value to the tvs slice and check it further down + tvs = []*sdcpb.TypedValue{tv} + // we also need the Field/Leaf Type schema + typeSchema = e.GetSchema().GetField().GetType() + case len(e.GetSchema().GetLeaflist().GetType().GetRange()) != 0: + // if it is a leaflist, extract the values them to the tvs slice and check them further down + tvs = tv.GetLeaflistVal().GetElement() + // we also need the Field/Leaf Type schema + typeSchema = e.GetSchema().GetLeaflist().GetType() + default: + // if no ranges exist return + return + } + + // range through the tvs and check that they are in range + for _, tv := range tvs { + // we need to distinguish between unsigned and singned ints + switch typeSchema.TypeName { + case "uint8", "uint16", "uint32", "uint64": + // procede with the unsigned ints + urnges := sdcpbutils.NewRnges[uint64]() + // add the defined ranges to the ranges struct + for _, r := range typeSchema.GetRange() { + urnges.AddRange(r.Min.Value, r.Max.Value) + } + stats.Add(types.StatTypeRange, uint32(len(typeSchema.GetRange()))) + + // check the value lays within any of the ranges + if !urnges.IsWithinAnyRange(tv.GetUintVal()) { + resultChan <- types.NewValidationResultEntry(lv.Owner(), fmt.Errorf("path %s, value %d not within any of the expected ranges %s", e.SdcpbPath().ToXPath(false), tv.GetUintVal(), urnges.String()), types.ValidationResultEntryTypeError) + } + + case "int8", "int16", "int32", "int64": + // procede with the signed ints + srnges := sdcpbutils.NewRnges[int64]() + for _, r := range typeSchema.GetRange() { + // get the value + min := int64(r.GetMin().GetValue()) + max := int64(r.GetMax().GetValue()) + // take care of the minus sign + if r.Min.Negative { + min = min * -1 + } + if r.Max.Negative { + max = max * -1 + } + // add the defined ranges to the ranges struct + srnges.AddRange(min, max) + } + stats.Add(types.StatTypeRange, uint32(len(typeSchema.GetRange()))) + // check the value lays within any of the ranges + if !srnges.IsWithinAnyRange(tv.GetIntVal()) { + resultChan <- types.NewValidationResultEntry(lv.Owner(), fmt.Errorf("path %s, value %d not within any of the expected ranges %s", e.SdcpbPath().ToXPath(false), tv.GetIntVal(), srnges.String()), types.ValidationResultEntryTypeError) + } + } + } +} + +func validateMinMaxElements(e api.Entry, resultChan chan<- *types.ValidationResultEntry, stats *types.ValidationStats) { + var contSchema *sdcpb.ContainerSchema + if contSchema = e.GetSchema().GetContainer(); contSchema == nil { + // if it is not a container, return + return + } + if len(contSchema.GetKeys()) == 0 { + // if it is not a list, return + return + } + + // get all the childs, skipping the key levels + childs, err := e.GetListChilds() + if err != nil { + resultChan <- types.NewValidationResultEntry("unknown", fmt.Errorf("error getting childs for min/max-elements check %v", err), types.ValidationResultEntryTypeError) + } + + intMin := int(contSchema.GetMinElements()) + intMax := int(contSchema.GetMaxElements()) + + // early exit if no specific min/max defined + if intMin <= 0 && intMax <= 0 { + return + } + + // define function to figure out associated owners / intents + + ownersSet := map[string]struct{}{} + for _, child := range childs { + childAttributes := child.GetChilds(types.DescendMethodActiveChilds) + keyName := contSchema.GetKeys()[0].GetName() + if keyAttr, ok := childAttributes[keyName]; ok { + highestPrec := keyAttr.GetHighestPrecedence(nil, false, false, false) + if len(highestPrec) > 0 { + owner := highestPrec[0].Update.Owner() + ownersSet[owner] = struct{}{} + } + } + } + // dedup the owners + owners := []string{} + for k := range ownersSet { + owners = append(owners, k) + } + + if len(childs) < intMin { + for _, owner := range owners { + resultChan <- types.NewValidationResultEntry(owner, fmt.Errorf("Min-Elements violation on %s expected %d actual %d", e.SdcpbPath().ToXPath(false), intMin, len(childs)), types.ValidationResultEntryTypeError) + } + } + if intMax > 0 && len(childs) > intMax { + for _, owner := range owners { + resultChan <- types.NewValidationResultEntry(owner, fmt.Errorf("Max-Elements violation on %s expected %d actual %d", e.SdcpbPath().ToXPath(false), intMax, len(childs)), types.ValidationResultEntryTypeError) + } + } + stats.Add(types.StatTypeMinMaxElementsList, 1) +} + +// validateLeafListMinMaxAttributes validates the Min-, and Max-Elements attribute of the Entry if it is a Leaflists. +func validateLeafListMinMaxAttributes(e api.Entry, resultChan chan<- *types.ValidationResultEntry, stats *types.ValidationStats) { + if schema := e.GetSchema().GetLeaflist(); schema != nil { + if schema.GetMinElements() > 0 || schema.GetMaxElements() < math.MaxUint64 { + if lv := e.GetLeafVariants().GetHighestPrecedence(false, true, false); lv != nil { + tv := lv.Update.Value() + + if val := tv.GetLeaflistVal(); val != nil { + // check minelements if set + if schema.GetMinElements() > 0 && len(val.GetElement()) < int(schema.GetMinElements()) { + resultChan <- types.NewValidationResultEntry(lv.Owner(), fmt.Errorf("leaflist %s defines %d min-elements but only %d elements are present", e.SdcpbPath().ToXPath(false), schema.MinElements, len(val.GetElement())), types.ValidationResultEntryTypeError) + } + // check maxelements if set + if uint64(len(val.GetElement())) > uint64(schema.GetMaxElements()) { + resultChan <- types.NewValidationResultEntry(lv.Owner(), fmt.Errorf("leaflist %s defines %d max-elements but %d elements are present", e.SdcpbPath().ToXPath(false), schema.GetMaxElements(), len(val.GetElement())), types.ValidationResultEntryTypeError) + } + } + stats.Add(types.StatTypeMinMaxElementsLeaflist, 1) + } + } + } +} + +func validateLength(e api.Entry, resultChan chan<- *types.ValidationResultEntry, stats *types.ValidationStats) { + if schema := e.GetSchema().GetField(); schema != nil { + + if len(schema.GetType().GetLength()) == 0 { + return + } + + lv := e.GetLeafVariants().GetHighestPrecedence(false, true, false) + if lv == nil { + return + } + value := lv.Value().GetStringVal() + actualLength := utf8.RuneCountInString(value) + + for _, lengthDef := range schema.GetType().GetLength() { + + if lengthDef.Min.Value <= uint64(actualLength) && uint64(actualLength) <= lengthDef.Max.Value { + // continue if the length is within the range + continue + } + // this is already the failure case + lenghts := []string{} + for _, lengthDef := range schema.GetType().GetLength() { + lenghts = append(lenghts, fmt.Sprintf("%d..%d", lengthDef.Min.Value, lengthDef.Max.Value)) + } + resultChan <- types.NewValidationResultEntry(lv.Owner(), fmt.Errorf("error length of Path: %s, Value: %s not within allowed length %s", e.SdcpbPath().ToXPath(false), value, strings.Join(lenghts, ", ")), types.ValidationResultEntryTypeError) + } + stats.Add(types.StatTypeLength, uint32(len(schema.GetType().GetLength()))) + } +} + +func validatePattern(e api.Entry, resultChan chan<- *types.ValidationResultEntry, stats *types.ValidationStats) { + if schema := e.GetSchema().GetField(); schema != nil { + if len(schema.GetType().GetPatterns()) == 0 { + return + } + + lv := e.GetLeafVariants().GetHighestPrecedence(false, true, false) + if lv == nil { + return + } + value := lv.Value().GetStringVal() + for _, pattern := range schema.GetType().GetPatterns() { + if p := pattern.GetPattern(); p != "" { + matched, err := regexp.MatchString(p, value) + if err != nil { + resultChan <- types.NewValidationResultEntry(lv.Owner(), fmt.Errorf("failed compiling regex %s defined for %s", p, e.SdcpbPath().ToXPath(false)), types.ValidationResultEntryTypeError) + continue + } + if (!matched && !pattern.Inverted) || (pattern.GetInverted() && matched) { + resultChan <- types.NewValidationResultEntry(lv.Owner(), fmt.Errorf("value %s of %s does not match regex %s (inverted: %t)", value, e.SdcpbPath().ToXPath(false), p, pattern.GetInverted()), types.ValidationResultEntryTypeError) + } + } + + } + stats.Add(types.StatTypePattern, uint32(len(schema.GetType().GetPatterns()))) + } +} + +// validateMandatory validates that all the mandatory attributes, +// defined by the schema are present either in the tree or in the index. +func validateMandatory(ctx context.Context, e api.Entry, resultChan chan<- *types.ValidationResultEntry, stats *types.ValidationStats) { + log := logger.FromContext(ctx) + if !e.RemainsToExist() { + return + } + schema := e.GetSchema() + if schema != nil { + switch schema.GetSchema().(type) { + case *sdcpb.SchemaElem_Container: + containerSchema := schema.GetContainer() + for _, c := range containerSchema.GetMandatoryChildrenConfig() { + attributes := []string{} + choiceName := "" + // check if it is a ChildContainer + if slices.Contains(containerSchema.GetChildren(), c.Name) { + attributes = append(attributes, c.Name) + } + + // check if it is a Key + if slices.ContainsFunc(containerSchema.GetKeys(), func(x *sdcpb.LeafSchema) bool { + return x.Name == c.Name + }) { + attributes = append(attributes, c.Name) + } + + // check if it is a Field + if slices.ContainsFunc(containerSchema.GetFields(), func(x *sdcpb.LeafSchema) bool { + return x.Name == c.Name + }) { + attributes = append(attributes, c.Name) + } + + // otherwise it will probably be a choice + if len(attributes) == 0 { + choice := containerSchema.GetChoiceInfo().GetChoiceByName(c.Name) + if choice != nil { + attributes = append(attributes, choice.GetAllAttributes()...) + choiceName = c.Name + } + } + + if len(attributes) == 0 { + log.Error(ErrValidationError, "mandatory attribute could not be found as child, field or choice", "path", e.SdcpbPath().ToXPath(false), "attribute", c.Name) + } + + validateMandatoryWithKeys(ctx, e, len(containerSchema.GetKeys()), attributes, choiceName, resultChan) + } + stats.Add(types.StatTypeMandatory, uint32(len(containerSchema.GetMandatoryChildrenConfig()))) + } + } +} + +// validateMandatoryWithKeys steps down the tree, passing the key levels and checking the existence of the mandatory. +// attributes is a string slice, it will be checked that at least of the the given attributes is defined +// !Not checking all of these are defined (call multiple times with single entry in attributes for that matter)! +func validateMandatoryWithKeys(ctx context.Context, e api.Entry, level int, attributes []string, choiceName string, resultChan chan<- *types.ValidationResultEntry) { + if e.ShouldDelete() { + return + } + // need to step down the tree until we're beyond the key levels to check the mandatory attributes, if level is > 0, we are still in the key levels + if level > 0 { + for _, c := range e.GetChilds(types.DescendMethodActiveChilds) { + validateMandatoryWithKeys(ctx, c, level-1, attributes, choiceName, resultChan) + } + return + } + + success := false + existsInTree := false + var v api.Entry + // iterate over the attributes make sure any of these exists + for _, attr := range attributes { + // first check if the mandatory value is set via the intent, e.g. part of the tree already + v, existsInTree = e.GetChilds(types.DescendMethodActiveChilds)[attr] + // if exists and remains to Exist + if existsInTree && v.RemainsToExist() { + // set success to true and break the loop + success = true + break + } + } + // if not the path exists in the tree and is not to be deleted, then lookup in the paths index of the store + // and see if such path exists, if not raise the error + if !success { + // if it is not a choice + if choiceName == "" { + resultChan <- types.NewValidationResultEntry("unknown", fmt.Errorf("error mandatory child %s does not exist, path: %s", attributes, e.SdcpbPath().ToXPath(false)), types.ValidationResultEntryTypeError) + return + } + // if it is a mandatory choice + resultChan <- types.NewValidationResultEntry("unknown", fmt.Errorf("error mandatory choice %s [attributes: %s] does not exist, path: %s", choiceName, attributes, e.SdcpbPath().ToXPath(false)), types.ValidationResultEntryTypeError) + return + } +} diff --git a/pkg/tree/validation_entry_leafref.go b/pkg/tree/ops/validation/validation_entry_leafref.go similarity index 76% rename from pkg/tree/validation_entry_leafref.go rename to pkg/tree/ops/validation/validation_entry_leafref.go index 1bc4b541..635c4692 100644 --- a/pkg/tree/validation_entry_leafref.go +++ b/pkg/tree/ops/validation/validation_entry_leafref.go @@ -1,4 +1,4 @@ -package tree +package validation import ( "context" @@ -6,11 +6,12 @@ import ( "strings" "github.com/sdcio/data-server/pkg/tree/api" + "github.com/sdcio/data-server/pkg/tree/ops" "github.com/sdcio/data-server/pkg/tree/types" sdcpb "github.com/sdcio/sdc-protos/sdcpb" ) -func (s *sharedEntryAttributes) BreadthSearch(ctx context.Context, sdcpbPath *sdcpb.Path) ([]api.Entry, error) { +func breadthSearch(ctx context.Context, e api.Entry, sdcpbPath *sdcpb.Path) ([]api.Entry, error) { var err error var resultEntries []api.Entry var processEntries []api.Entry @@ -20,9 +21,9 @@ func (s *sharedEntryAttributes) BreadthSearch(ctx context.Context, sdcpbPath *sd lrefPath := types.NewLrefPath(sdcpbPath) if sdcpbPath.GetIsRootBased() { - processEntries = []api.Entry{s.GetRoot()} + processEntries = []api.Entry{ops.GetRoot(e)} } else { - var entry api.Entry = s + var entry api.Entry = e dotdotcount := 0 sdcpbUp := []*sdcpb.PathElem{} // process the .. instructions @@ -49,7 +50,7 @@ func (s *sharedEntryAttributes) BreadthSearch(ctx context.Context, sdcpbPath *sd for _, elem := range lrefPath { resultEntries = []api.Entry{} - err := s.resolve_leafref_key_path(ctx, elem.Keys) + err := resolveLeafrefKeyPath(ctx, e, elem.Keys) if err != nil { return nil, err } @@ -73,7 +74,7 @@ func (s *sharedEntryAttributes) BreadthSearch(ctx context.Context, sdcpbPath *sd } var childs []api.Entry // if the entry is a list with keys, try filtering the entries based on the keys - if len(entry.GetSchemaKeys()) > 0 { + if len(ops.GetSchemaKeys(entry)) > 0 { // filter the keys childs, err = entry.FilterChilds(elem.KeysToMap()) if err != nil { @@ -92,8 +93,8 @@ func (s *sharedEntryAttributes) BreadthSearch(ctx context.Context, sdcpbPath *sd return resultEntries, nil } -// NavigateLeafRef -func (s *sharedEntryAttributes) NavigateLeafRef(ctx context.Context) ([]api.Entry, error) { +// navigateLeafRef +func navigateLeafRef(ctx context.Context, e api.Entry) ([]api.Entry, error) { // leafref path takes as an argument a string that MUST refer to a leaf or leaf-list node. // e.g. @@ -109,15 +110,15 @@ func (s *sharedEntryAttributes) NavigateLeafRef(ctx context.Context) ([]api.Entr var lref string switch { - case s.GetSchema().GetField().GetType().GetLeafref() != "": - lref = s.schema.GetField().GetType().GetLeafref() - case s.GetSchema().GetLeaflist().GetType().GetLeafref() != "": - lref = s.GetSchema().GetLeaflist().GetType().GetLeafref() + case e.GetSchema().GetField().GetType().GetLeafref() != "": + lref = e.GetSchema().GetField().GetType().GetLeafref() + case e.GetSchema().GetLeaflist().GetType().GetLeafref() != "": + lref = e.GetSchema().GetLeaflist().GetType().GetLeafref() default: - return nil, fmt.Errorf("error not a leafref %s", s.SdcpbPath()) + return nil, fmt.Errorf("error not a leafref %s", e.SdcpbPath()) } - lv := s.leafVariants.GetHighestPrecedence(false, true, false) + lv := e.GetLeafVariants().GetHighestPrecedence(false, true, false) if lv == nil { return nil, fmt.Errorf("no leafvariant found") } @@ -129,7 +130,7 @@ func (s *sharedEntryAttributes) NavigateLeafRef(ctx context.Context) ([]api.Entr return nil, err } - foundEntries, err := s.BreadthSearch(ctx, lrefPath) + foundEntries, err := breadthSearch(ctx, e, lrefPath) if err != nil { return nil, err } @@ -163,7 +164,7 @@ func (s *sharedEntryAttributes) NavigateLeafRef(ctx context.Context) ([]api.Entr return resultEntries, nil } -func (s *sharedEntryAttributes) resolve_leafref_key_path(ctx context.Context, keys map[string]*types.LrefPathElemKeyValue) error { +func resolveLeafrefKeyPath(ctx context.Context, e api.Entry, keys map[string]*types.LrefPathElemKeyValue) error { // resolve keys for k, v := range keys { if v.DoNotResolve || !strings.Contains(v.Value, "/") || !strings.Contains(v.Value, "current") { @@ -184,7 +185,7 @@ func (s *sharedEntryAttributes) resolve_leafref_key_path(ctx context.Context, ke } keyp.SetIsRootBased(isRootPath) - keyValue, err := s.NavigateSdcpbPath(ctx, keyp) + keyValue, err := e.NavigateSdcpbPath(ctx, keyp) if err != nil { return err } @@ -200,30 +201,30 @@ func (s *sharedEntryAttributes) resolve_leafref_key_path(ctx context.Context, ke return nil } -func (s *sharedEntryAttributes) validateLeafRefs(ctx context.Context, resultChan chan<- *types.ValidationResultEntry, stats *types.ValidationStats) { - if s.ShouldDelete() { +func validateLeafRefs(ctx context.Context, e api.Entry, resultChan chan<- *types.ValidationResultEntry, stats *types.ValidationStats) { + if e.ShouldDelete() { return } - lref := s.schema.GetField().GetType().GetLeafref() - if s.schema == nil || lref == "" { + lref := e.GetSchema().GetField().GetType().GetLeafref() + if e.GetSchema() == nil || lref == "" { return } - entry, err := s.NavigateLeafRef(ctx) + entry, err := navigateLeafRef(ctx, e) if err != nil || len(entry) == 0 { // check if the OptionalInstance (!require-instances [https://datatracker.ietf.org/doc/html/rfc7950#section-9.9.3]) - if s.schema.GetField().GetType().GetOptionalInstance() { - generateOptionalWarning(ctx, s, lref, resultChan) + if e.GetSchema().GetField().GetType().GetOptionalInstance() { + generateOptionalWarning(ctx, e, lref, resultChan) return } owner := "unknown" - highest := s.leafVariants.GetHighestPrecedence(false, false, false) + highest := e.GetLeafVariants().GetHighestPrecedence(false, false, false) if highest != nil { owner = highest.Owner() } - pathStr := s.SdcpbPath().ToXPath(false) + pathStr := e.SdcpbPath().ToXPath(false) // if required, issue error valRes := types.NewValidationResultEntry(owner, fmt.Errorf("missing leaf reference: failed resolving leafref %s for %s: %v", lref, pathStr, err), types.ValidationResultEntryTypeError) resultChan <- valRes @@ -232,18 +233,18 @@ func (s *sharedEntryAttributes) validateLeafRefs(ctx context.Context, resultChan // Only if the value remains, even after the SetIntent made it through, the LeafRef can be considered resolved. if entry[0].ShouldDelete() { - lv := s.leafVariants.GetHighestPrecedence(false, true, false) + lv := e.GetLeafVariants().GetHighestPrecedence(false, true, false) if lv == nil { return } // check if the OptionalInstance (!require-instances [https://datatracker.ietf.org/doc/html/rfc7950#section-9.9.3]) - if s.schema.GetField().GetType().GetOptionalInstance() { - generateOptionalWarning(ctx, s, lref, resultChan) + if e.GetSchema().GetField().GetType().GetOptionalInstance() { + generateOptionalWarning(ctx, e, lref, resultChan) return } // if required, issue error - resultChan <- types.NewValidationResultEntry(lv.Owner(), fmt.Errorf("missing leaf reference: failed resolving leafref %s for %s to path %s LeafVariant %v", lref, s.SdcpbPath().ToXPath(false), s.SdcpbPath().ToXPath(false), lv), types.ValidationResultEntryTypeError) + resultChan <- types.NewValidationResultEntry(lv.Owner(), fmt.Errorf("missing leaf reference: failed resolving leafref %s for %s to path %s LeafVariant %v", lref, e.SdcpbPath().ToXPath(false), e.SdcpbPath().ToXPath(false), lv), types.ValidationResultEntryTypeError) return } stats.Add(types.StatTypeLeafRef, 1) diff --git a/pkg/tree/validation_entry_leafref_test.go b/pkg/tree/ops/validation/validation_entry_leafref_test.go similarity index 91% rename from pkg/tree/validation_entry_leafref_test.go rename to pkg/tree/ops/validation/validation_entry_leafref_test.go index 6ed11249..0ce28248 100644 --- a/pkg/tree/validation_entry_leafref_test.go +++ b/pkg/tree/ops/validation/validation_entry_leafref_test.go @@ -1,4 +1,4 @@ -package tree +package validation_test import ( "context" @@ -8,9 +8,13 @@ import ( "testing" "github.com/openconfig/ygot/ygot" + "github.com/sdcio/data-server/pkg/config" schemaClient "github.com/sdcio/data-server/pkg/datastore/clients/schema" "github.com/sdcio/data-server/pkg/pool" + "github.com/sdcio/data-server/pkg/tree" + jsonImporter "github.com/sdcio/data-server/pkg/tree/importer/json" + "github.com/sdcio/data-server/pkg/tree/ops/validation" "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils/testhelper" sdcio_schema "github.com/sdcio/data-server/tests/sdcioygot" @@ -215,9 +219,9 @@ func Test_sharedEntryAttributes_validateLeafRefs(t *testing.T) { t.Fatal(err) } scb := schemaClient.NewSchemaClientBound(schema, sc) - tc := NewTreeContext(scb, pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0))) + tc := tree.NewTreeContext(scb, pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0))) - root, err := NewTreeRoot(ctx, tc) + root, err := tree.NewTreeRoot(ctx, tc) if err != nil { t.Fatal(err) } @@ -256,24 +260,21 @@ func Test_sharedEntryAttributes_validateLeafRefs(t *testing.T) { t.Error(err) } - s, ok := e.(*sharedEntryAttributes) - if !ok { - t.Errorf("failed to assert type *sharedEntryAttributes") - } - // make sure we're looking at a leafref - if s.schema.GetField().GetType().GetType() != "leafref" { + if e.GetSchema().GetField().GetType().GetType() != "leafref" { t.Fatalf("referenced field %s not a leafref, fix test.", tt.lrefNodePath.ToXPath(false)) } - resultChan := make(chan<- *types.ValidationResultEntry, 20) - stats := types.NewValidationStats() - s.validateLeafRefs(ctx, resultChan, stats) + valConf := config.NewValidationConfig() + valConf.DisabledValidators.DisableAll() + valConf.DisabledValidators.Leafref = false + valConf.DisableConcurrency = true // disable concurrency for deterministic test results - if len(resultChan) != tt.expectedResultLen { - t.Fatalf("expected %d, got %d errors on leafref validation", tt.expectedResultLen, len(resultChan)) - } + result, _ := validation.Validate(ctx, e, valConf, pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0))) + if len(result) != tt.expectedResultLen { + t.Fatalf("expected %d, got %d errors on leafref validation", tt.expectedResultLen, len(result)) + } }) } } diff --git a/pkg/tree/validation_entry_must.go b/pkg/tree/ops/validation/validation_entry_must.go similarity index 76% rename from pkg/tree/validation_entry_must.go rename to pkg/tree/ops/validation/validation_entry_must.go index 17c83568..853e472e 100644 --- a/pkg/tree/validation_entry_must.go +++ b/pkg/tree/ops/validation/validation_entry_must.go @@ -1,10 +1,11 @@ -package tree +package validation import ( "context" "fmt" "strings" + "github.com/sdcio/data-server/pkg/tree/api" "github.com/sdcio/data-server/pkg/tree/types" logf "github.com/sdcio/logger" sdcpb "github.com/sdcio/sdc-protos/sdcpb" @@ -12,16 +13,16 @@ import ( "github.com/sdcio/yang-parser/xpath/grammars/expr" ) -func (s *sharedEntryAttributes) validateMustStatements(ctx context.Context, resultChan chan<- *types.ValidationResultEntry, stats *types.ValidationStats) { +func validateMustStatements(ctx context.Context, e api.Entry, resultChan chan<- *types.ValidationResultEntry, stats *types.ValidationStats) { log := logf.FromContext(ctx) // if no schema, then there is nothing to be done, return - if s.schema == nil { + if e.GetSchema() == nil { return } var mustStatements []*sdcpb.MustStatement - switch schem := s.GetSchema().GetSchema().(type) { + switch schem := e.GetSchema().GetSchema().(type) { case *sdcpb.SchemaElem_Container: mustStatements = schem.Container.GetMustStatements() case *sdcpb.SchemaElem_Leaflist: @@ -42,7 +43,7 @@ func (s *sharedEntryAttributes) validateMustStatements(ctx context.Context, resu prog, err := lexer.CreateProgram(exprStr) if err != nil { owner := "unknown" - highest := s.leafVariants.GetHighestPrecedence(false, false, false) + highest := e.GetLeafVariants().GetHighestPrecedence(false, false, false) if highest != nil { owner = highest.Owner() } @@ -52,7 +53,7 @@ func (s *sharedEntryAttributes) validateMustStatements(ctx context.Context, resu machine := xpath.NewMachine(exprStr, prog, exprStr) // run the must statement evaluation virtual machine - yctx := xpath.NewCtxFromCurrent(ctx, machine, newYangParserEntryAdapter(ctx, s)) + yctx := xpath.NewCtxFromCurrent(ctx, machine, newYangParserEntryAdapter(ctx, e)) yctx.SetDebug(false) res1 := yctx.Run() @@ -60,16 +61,18 @@ func (s *sharedEntryAttributes) validateMustStatements(ctx context.Context, resu result, err := res1.GetBoolResult() if !result || err != nil { if err == nil { - err = fmt.Errorf("error path: %s, must-statement [%s] %s", s.SdcpbPath().ToXPath(false), must.Statement, must.Error) + err = fmt.Errorf("error path: %s, must-statement [%s] %s", e.SdcpbPath().ToXPath(false), must.Statement, must.Error) } if strings.Contains(err.Error(), "Stack underflow") { - log.Error(err, "stack underflow", "path", s.SdcpbPath().ToXPath(false), "must-expression", exprStr) + log.Error(err, "stack underflow", "path", e.SdcpbPath().ToXPath(false), "must-expression", exprStr) continue } owner := "unknown" + // must statement might be assigned on a container, hence we might not have any LeafVariants - if s.leafVariants.Length() > 0 { - highest := s.leafVariants.GetHighestPrecedence(false, false, false) + leafVariants := e.GetLeafVariants() + if leafVariants.Length() > 0 { + highest := leafVariants.GetHighestPrecedence(false, false, false) if highest != nil { owner = highest.Owner() } diff --git a/pkg/tree/validation_range_test.go b/pkg/tree/ops/validation/validation_range_test.go similarity index 86% rename from pkg/tree/validation_range_test.go rename to pkg/tree/ops/validation/validation_range_test.go index 7b2d39c7..228c911c 100644 --- a/pkg/tree/validation_range_test.go +++ b/pkg/tree/ops/validation/validation_range_test.go @@ -1,4 +1,4 @@ -package tree +package validation_test import ( "context" @@ -8,8 +8,11 @@ import ( "testing" "github.com/openconfig/ygot/ygot" + "github.com/sdcio/data-server/pkg/config" "github.com/sdcio/data-server/pkg/pool" + "github.com/sdcio/data-server/pkg/tree" json_importer "github.com/sdcio/data-server/pkg/tree/importer/json" + "github.com/sdcio/data-server/pkg/tree/ops/validation" "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils/testhelper" sdcio_schema "github.com/sdcio/data-server/tests/sdcioygot" @@ -17,6 +20,17 @@ import ( "go.uber.org/mock/gomock" ) +var ( + // TypedValue Bool True and false + TypedValueTrue = &sdcpb.TypedValue{Value: &sdcpb.TypedValue_BoolVal{BoolVal: true}} + TypedValueFalse = &sdcpb.TypedValue{Value: &sdcpb.TypedValue_BoolVal{BoolVal: false}} + validationConfig = config.NewValidationConfig() +) + +func init() { + validationConfig.SetDisableConcurrency(true) +} + func TestValidate_Range_SDC_Schema(t *testing.T) { ctx := context.TODO() @@ -27,9 +41,9 @@ func TestValidate_Range_SDC_Schema(t *testing.T) { t.Error(err) } - tc := NewTreeContext(scb, pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0))) + tc := tree.NewTreeContext(scb, pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0))) - root, err := NewTreeRoot(ctx, tc) + root, err := tree.NewTreeRoot(ctx, tc) if err != nil { t.Error(err) @@ -80,7 +94,7 @@ func TestValidate_Range_SDC_Schema(t *testing.T) { valConf := validationConfig.DeepCopy() - validationResult, _ := root.Validate(ctx, valConf, sharedPool) + validationResult, _ := validation.Validate(ctx, root.Entry, valConf, sharedPool) t.Logf("Validation Errors:\n%s", strings.Join(validationResult.ErrorsStr(), "\n")) t.Log(root.String()) @@ -157,10 +171,10 @@ func TestValidate_RangesSigned(t *testing.T) { t.Run(tt.name, func(t *testing.T) { // the tree context - tc := NewTreeContext(scb, pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0))) + tc := tree.NewTreeContext(scb, pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0))) // the tree root - root, err := NewTreeRoot(ctx, tc) + root, err := tree.NewTreeRoot(ctx, tc) if err != nil { t.Error(err) } @@ -195,7 +209,7 @@ func TestValidate_RangesSigned(t *testing.T) { t.Error(err) } - validationResult, _ := root.Validate(ctx, validationConfig, sharedPool) + validationResult, _ := validation.Validate(ctx, root.Entry, validationConfig, sharedPool) t.Logf("Validation Errors:\n%s", strings.Join(validationResult.ErrorsStr(), "\n")) t.Log(root.String()) @@ -293,10 +307,10 @@ func TestValidate_RangesUnSigned(t *testing.T) { t.Run(tt.name, func(t *testing.T) { // the tree context - tc := NewTreeContext(scb, pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0))) + tc := tree.NewTreeContext(scb, pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0))) // the tree root - root, err := NewTreeRoot(ctx, tc) + root, err := tree.NewTreeRoot(ctx, tc) if err != nil { t.Error(err) } @@ -331,7 +345,7 @@ func TestValidate_RangesUnSigned(t *testing.T) { } // run validation - validationResults, _ := root.Validate(ctx, validationConfig, sharedPool) + validationResults, _ := validation.Validate(ctx, root.Entry, validationConfig, sharedPool) t.Logf("Validation Errors:\n%s", strings.Join(validationResults.ErrorsStr(), "\n")) t.Log(root.String()) diff --git a/pkg/tree/yang-parser-adapter.go b/pkg/tree/ops/validation/yang-parser-adapter.go similarity index 97% rename from pkg/tree/yang-parser-adapter.go rename to pkg/tree/ops/validation/yang-parser-adapter.go index aa4d78f3..cca51f11 100644 --- a/pkg/tree/yang-parser-adapter.go +++ b/pkg/tree/ops/validation/yang-parser-adapter.go @@ -1,4 +1,4 @@ -package tree +package validation import ( "context" @@ -82,7 +82,7 @@ func (y *yangParserEntryAdapter) GetValue() (xpath.Datum, error) { } func (y *yangParserEntryAdapter) BreadthSearch(ctx context.Context, path *sdcpb.Path) ([]xpath.Entry, error) { - entries, err := y.e.BreadthSearch(ctx, path) + entries, err := breadthSearch(ctx, y.e, path) if err != nil { return nil, err } @@ -96,7 +96,7 @@ func (y *yangParserEntryAdapter) BreadthSearch(ctx context.Context, path *sdcpb. } func (y *yangParserEntryAdapter) FollowLeafRef() (xpath.Entry, error) { - entries, err := y.e.NavigateLeafRef(y.ctx) + entries, err := navigateLeafRef(y.ctx, y.e) if err != nil { return nil, err } diff --git a/pkg/tree/xml.go b/pkg/tree/ops/xml.go similarity index 71% rename from pkg/tree/xml.go rename to pkg/tree/ops/xml.go index feddf708..50b5e971 100644 --- a/pkg/tree/xml.go +++ b/pkg/tree/ops/xml.go @@ -1,7 +1,8 @@ -package tree +package ops import ( "cmp" + "context" "fmt" "slices" "sort" @@ -17,34 +18,34 @@ import ( // If honorNamespace is set, the xml elements will carry their respective namespace attributes. // If operationWithNamespace is set, the operation attributes added to the to be deleted alements will also carry the Netconf Base namespace. // If useOperationRemove is set, the remove operation will be used for deletes, instead of the delete operation. -func (s *sharedEntryAttributes) ToXML(onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove bool) (*etree.Document, error) { +func ToXML(ctx context.Context, e api.Entry, onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove bool) (*etree.Document, error) { doc := etree.NewDocument() - _, err := s.ToXmlInternal(&doc.Element, onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove) + _, err := toXmlInternal(ctx, e, &doc.Element, onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove) if err != nil { return nil, err } return doc, nil } -func (s *sharedEntryAttributes) ToXmlInternal(parent *etree.Element, onlyNewOrUpdated bool, honorNamespace bool, operationWithNamespace bool, useOperationRemove bool) (doAdd bool, err error) { +func toXmlInternal(ctx context.Context, e api.Entry, parent *etree.Element, onlyNewOrUpdated bool, honorNamespace bool, operationWithNamespace bool, useOperationRemove bool) (doAdd bool, err error) { - switch s.schema.GetSchema().(type) { + switch e.GetSchema().GetSchema().(type) { case nil: // This case represents a key level element. So no schema present. all child attributes need to be adedd directly to the parent element, since the key levels are not visible in the resulting xml. - if s.ShouldDelete() { + if e.ShouldDelete() { // If the element is to be deleted // add the delete operation to the parent element utils.AddXMLOperation(parent, utils.XMLOperationDelete, operationWithNamespace, useOperationRemove) // retrieve the parent schema, we need to extract the key names // values are the tree level names - xmlAddKeyElements(s, parent) + xmlAddKeyElements(e, parent) return true, nil } // if the entry remains so exist, we need to add it to the xml doc overallDoAdd := false - childs := s.GetChilds(types.DescendMethodActiveChilds) + childs := e.GetChilds(types.DescendMethodActiveChilds) keys := make([]string, 0, len(childs)) for k := range childs { @@ -52,11 +53,11 @@ func (s *sharedEntryAttributes) ToXmlInternal(parent *etree.Element, onlyNewOrUp } // Perform ordering of attributes - schemaParent, _ := s.GetFirstAncestorWithSchema() + schemaParent, _ := GetFirstAncestorWithSchema(e) if schemaParent == nil { - return false, fmt.Errorf("no ancestor has schema for %v", s) + return false, fmt.Errorf("no ancestor has schema for %v", e) } - schemaKeys := schemaParent.GetSchemaKeys() + schemaKeys := GetSchemaKeys(schemaParent) slices.SortFunc(keys, func(a, b string) int { aIdx := slices.Index(schemaKeys, a) bIdx := slices.Index(schemaKeys, b) @@ -77,7 +78,7 @@ func (s *sharedEntryAttributes) ToXmlInternal(parent *etree.Element, onlyNewOrUp for _, k := range keys { // recurse the call // no additional element is created, since we're on a key level, so add to parent element - doAdd, err := childs[k].ToXmlInternal(parent, onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove) + doAdd, err := toXmlInternal(ctx, childs[k], parent, onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove) if err != nil { return false, err } @@ -89,26 +90,26 @@ func (s *sharedEntryAttributes) ToXmlInternal(parent *etree.Element, onlyNewOrUp case *sdcpb.SchemaElem_Container: overallDoAdd := false switch { - case len(s.GetSchemaKeys()) > 0: + case len(GetSchemaKeys(e)) > 0: // the container represents a list // if the container contains keys, then it is a list // hence must be rendered as an array - childs, err := s.FilterChilds(nil) + childs, err := e.FilterChilds(nil) if err != nil { return false, err } // Apply sorting - slices.SortFunc(childs, getListEntrySortFunc(s)) + slices.SortFunc(childs, getListEntrySortFunc(e)) // go through the childs creating the xml elements for _, child := range childs { // create the element for the child, that in the recursed call will appear as parent - newElem := etree.NewElement(s.PathName()) + newElem := etree.NewElement(e.PathName()) // process the honorNamespace instruction - xmlAddNamespaceConditional(s, s.parent, newElem, honorNamespace) + xmlAddNamespaceConditional(e, e.GetParent(), newElem, honorNamespace) // recurse the call - doAdd, err := child.ToXmlInternal(newElem, onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove) + doAdd, err := toXmlInternal(ctx, child, newElem, onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove) if err != nil { return false, err } @@ -119,7 +120,7 @@ func (s *sharedEntryAttributes) ToXmlInternal(parent *etree.Element, onlyNewOrUp replaceAttr := newElem.SelectAttr("nc:operation") if replaceAttr != nil && replaceAttr.Value == string(utils.XMLOperationReplace) { // If we are replacing, we need to have all child values added to the xml tree else they will be removed from the device - err := xmlAddAllChildValues(child, newElem, honorNamespace, operationWithNamespace, useOperationRemove) + err := xmlAddAllChildValues(ctx, child, newElem, honorNamespace, operationWithNamespace, useOperationRemove) if err != nil { return false, err } @@ -131,43 +132,43 @@ func (s *sharedEntryAttributes) ToXmlInternal(parent *etree.Element, onlyNewOrUp } } return overallDoAdd, nil - case s.ShouldDelete(): + case e.ShouldDelete(): // s is meant to be removed // if delete, create the element as child of parent - newElem := parent.CreateElement(s.pathElemName) + newElem := parent.CreateElement(e.PathName()) // add namespace if we create doc with namespace and the actual namespace differs from the parent namespace - xmlAddNamespaceConditional(s, s.parent, newElem, honorNamespace) + xmlAddNamespaceConditional(e, e.GetParent(), newElem, honorNamespace) // add the delete / remove operation utils.AddXMLOperation(newElem, utils.XMLOperationDelete, operationWithNamespace, useOperationRemove) return true, nil - case s.GetSchema().GetContainer().IsPresence && s.containsOnlyDefaults(): + case e.GetSchema().GetContainer().IsPresence && ContainsOnlyDefaults(ctx, e): // process presence containers with no childs if onlyNewOrUpdated { // presence containers have leafvariantes with typedValue_Empty, so check that - if s.leafVariants.ShouldDelete() { + if e.GetLeafVariants().ShouldDelete() { return false, nil } - le := s.leafVariants.GetHighestPrecedence(false, false, false) + le := e.GetLeafVariants().GetHighestPrecedence(false, false, false) if le == nil || onlyNewOrUpdated && !(le.IsNew || le.IsUpdated) { return false, nil } } - newElem := parent.CreateElement(s.PathName()) + newElem := parent.CreateElement(e.PathName()) // process the honorNamespace instruction - xmlAddNamespaceConditional(s, s.parent, newElem, honorNamespace) + xmlAddNamespaceConditional(e, e.GetParent(), newElem, honorNamespace) return true, nil default: // the container represents a map // So create the element that the tree entry represents - newElem := etree.NewElement(s.PathName()) + newElem := etree.NewElement(e.PathName()) // Apply sorting of childs - keys := s.childs.GetKeys() - if s.parent == nil { + keys := e.GetChildMap().GetKeys() + if e.GetParent() == nil { slices.Sort(keys) } else { - cldrn := s.schema.GetContainer().GetChildren() + cldrn := e.GetSchema().GetContainer().GetChildren() slices.SortFunc(keys, func(a, b string) int { return cmp.Compare(slices.Index(cldrn, a), slices.Index(cldrn, b)) }) @@ -176,23 +177,23 @@ func (s *sharedEntryAttributes) ToXmlInternal(parent *etree.Element, onlyNewOrUp // iterate through all the childs for _, k := range keys { - // for namespace attr creation we need to handle the root node (s.parent == nil) specially - if s.parent != nil { + // for namespace attr creation we need to handle the root node (e.GetParent() == nil) specially + if e.GetParent() != nil { // only if not the root level, we can check if parent namespace != actual elements namespace // so if we need to add namespaces, check if they are equal, if not add the namespace attribute - xmlAddNamespaceConditional(s, s.parent, newElem, honorNamespace) + xmlAddNamespaceConditional(e, e.GetParent(), newElem, honorNamespace) } else { // if this is the root node, we take the given element from the parent parameter as p // avoiding wrongly adding an additional level in the xml doc. newElem = parent } // recurse the call to all the children - child, exists := s.childs.GetEntry(k) + child, exists := e.GetChildMap().GetEntry(k) if !exists { - return false, fmt.Errorf("child %s does not exist for %s", k, s.SdcpbPath().ToXPath(false)) + return false, fmt.Errorf("child %s does not exist for %s", k, e.SdcpbPath().ToXPath(false)) } // TODO: Do we also need to xmlAddAllChildValues here too? - doAdd, err := child.ToXmlInternal(newElem, onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove) + doAdd, err := toXmlInternal(ctx, child, newElem, onlyNewOrUpdated, honorNamespace, operationWithNamespace, useOperationRemove) if err != nil { return false, err } @@ -201,9 +202,9 @@ func (s *sharedEntryAttributes) ToXmlInternal(parent *etree.Element, onlyNewOrUp // so we keep track via overAllDoAdd overallDoAdd = doAdd || overallDoAdd } - // so if there is at least a child and the s.parent is not nil (root node) + // so if there is at least a child and the e.GetParent() is not nil (root node) // then add p to the parent as a child - if overallDoAdd && s.parent != nil { + if overallDoAdd && e.GetParent() != nil { parent.AddChild(newElem) } return overallDoAdd, nil @@ -211,31 +212,31 @@ func (s *sharedEntryAttributes) ToXmlInternal(parent *etree.Element, onlyNewOrUp case *sdcpb.SchemaElem_Leaflist, *sdcpb.SchemaElem_Field: // check if the element remains to exist - if s.ShouldDelete() { + if e.ShouldDelete() { // if not, add the remove / delete op - utils.AddXMLOperation(parent.CreateElement(s.pathElemName), utils.XMLOperationDelete, operationWithNamespace, useOperationRemove) + utils.AddXMLOperation(parent.CreateElement(e.PathName()), utils.XMLOperationDelete, operationWithNamespace, useOperationRemove) // see case nil for an explanation of this, it is basically the same - if s.parent.GetSchema() == nil { - xmlAddKeyElements(s.parent, parent) + if e.GetParent().GetSchema() == nil { + xmlAddKeyElements(e.GetParent(), parent) } return true, nil } // if the Field or Leaflist remains to exist // get highes Precedence value - le := s.leafVariants.GetHighestPrecedence(onlyNewOrUpdated, false, false) + le := e.GetLeafVariants().GetHighestPrecedence(onlyNewOrUpdated, false, false) if le == nil { return false, nil } ns := "" // process the namespace attribute - if s.parent == nil || (honorNamespace && !namespaceIsEqual(s, s.parent)) { - ns = utils.GetNamespaceFromGetSchema(s.GetSchema()) + if e.GetParent() == nil || (honorNamespace && !namespaceIsEqual(e, e.GetParent())) { + ns = utils.GetNamespaceFromGetSchema(e.GetSchema()) } // convert value to XML and add to parent - utils.TypedValueToXML(parent, le.Value(), s.PathName(), ns, onlyNewOrUpdated, operationWithNamespace, useOperationRemove) + utils.TypedValueToXML(parent, le.Value(), e.PathName(), ns, onlyNewOrUpdated, operationWithNamespace, useOperationRemove) return true, nil } - return false, fmt.Errorf("unable to convert to xml (%s)", s.SdcpbPath().ToXPath(false)) + return false, fmt.Errorf("unable to convert to xml (%s)", e.SdcpbPath().ToXPath(false)) } // namespaceIsEqual takes the two given Entries, gets the namespace @@ -250,7 +251,7 @@ func namespaceIsEqual(a api.Entry, b api.Entry) bool { // if schema is nil, we're in a key level in the tree, so search up the chain for // the first ancestor that contains a schema. if schema == nil { - ancest, _ := a.GetFirstAncestorWithSchema() + ancest, _ := GetFirstAncestorWithSchema(a) schema = ancest.GetSchema() } // add the namespace to the array @@ -273,10 +274,10 @@ func xmlAddNamespaceConditional(a api.Entry, b api.Entry, elem *etree.Element, h func xmlAddKeyElements(s api.Entry, parent *etree.Element) { // retrieve the parent schema, we need to extract the key names // values are the tree level names - parentSchema, levelsUp := s.GetFirstAncestorWithSchema() + parentSchema, levelsUp := GetFirstAncestorWithSchema(s) // from the parent we get the keys as slice - schemaKeys := parentSchema.GetSchemaKeys() + schemaKeys := GetSchemaKeys(parentSchema) //issue #364: sort the slice sort.Strings(schemaKeys) @@ -296,9 +297,9 @@ func xmlAddKeyElements(s api.Entry, parent *etree.Element) { } } -func xmlAddAllChildValues(s api.Entry, parent *etree.Element, honorNamespace bool, operationWithNamespace bool, useOperationRemove bool) error { +func xmlAddAllChildValues(ctx context.Context, s api.Entry, parent *etree.Element, honorNamespace bool, operationWithNamespace bool, useOperationRemove bool) error { parent.Child = make([]etree.Token, 0) - _, err := s.ToXmlInternal(parent, false, honorNamespace, operationWithNamespace, useOperationRemove) + _, err := toXmlInternal(ctx, s, parent, false, honorNamespace, operationWithNamespace, useOperationRemove) if err != nil { return err } diff --git a/pkg/tree/xml_test.go b/pkg/tree/ops/xml_test.go similarity index 81% rename from pkg/tree/xml_test.go rename to pkg/tree/ops/xml_test.go index b627f5f9..4dc70b9b 100644 --- a/pkg/tree/xml_test.go +++ b/pkg/tree/ops/xml_test.go @@ -1,4 +1,4 @@ -package tree +package ops_test import ( "context" @@ -10,7 +10,11 @@ import ( "github.com/google/go-cmp/cmp" "github.com/openconfig/ygot/ygot" "github.com/sdcio/data-server/pkg/pool" + "github.com/sdcio/data-server/pkg/tree" "github.com/sdcio/data-server/pkg/tree/consts" + "github.com/sdcio/data-server/pkg/tree/ops" + "github.com/sdcio/data-server/pkg/tree/processors" + "github.com/sdcio/data-server/pkg/utils" "github.com/sdcio/data-server/pkg/utils/testhelper" sdcio_schema "github.com/sdcio/data-server/tests/sdcioygot" @@ -35,8 +39,8 @@ func TestToXMLTable(t *testing.T) { name: "XML All", onlyNewOrUpdated: false, existingConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, expected: ` @@ -72,12 +76,12 @@ func TestToXMLTable(t *testing.T) { name: "XML - no new", onlyNewOrUpdated: true, runningConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, existingConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, expected: ``, }, @@ -86,16 +90,16 @@ func TestToXMLTable(t *testing.T) { onlyNewOrUpdated: true, skip: false, existingConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, runningConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, newConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config2() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config2() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, expected: ` @@ -128,12 +132,12 @@ func TestToXMLTable(t *testing.T) { name: "XML - delete ethernet-1_1, honor namespace, operatin With namespace, remove", onlyNewOrUpdated: true, existingConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, runningConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, expected: ` ethernet-1/1 @@ -143,21 +147,21 @@ func TestToXMLTable(t *testing.T) { operationWithNamespace: true, useOperationRemove: true, newConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() + c := testhelper.Config1() delete(c.Interface, "ethernet-1/1") - return expandUpdateFromConfig(ctx, c, converter) + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, }, { name: "XML - honor namespace, operatin With namespace", onlyNewOrUpdated: true, existingConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, runningConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, expected: ` ethernet-1/1 @@ -167,21 +171,21 @@ func TestToXMLTable(t *testing.T) { operationWithNamespace: true, useOperationRemove: false, newConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() + c := testhelper.Config1() delete(c.Interface, "ethernet-1/1") - return expandUpdateFromConfig(ctx, c, converter) + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, }, { name: "XML - delete certain ethernet-1_1 attributes update another", onlyNewOrUpdated: true, existingConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, runningConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, expected: ` @@ -197,23 +201,23 @@ func TestToXMLTable(t *testing.T) { operationWithNamespace: true, useOperationRemove: true, newConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() + c := testhelper.Config1() c.Interface["ethernet-1/1"].Description = nil c.Interface["ethernet-1/1"].Subinterface[0].Description = nil c.Interface["ethernet-1/1"].Subinterface[0].Type = sdcio_schema.SdcioModelCommon_SiType_bridged - return expandUpdateFromConfig(ctx, c, converter) + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, }, { name: "XML - delete ethernet-1_1 add ethernet-1_2", onlyNewOrUpdated: true, existingConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, runningConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, skip: false, expected: ` @@ -231,7 +235,7 @@ func TestToXMLTable(t *testing.T) { operationWithNamespace: false, useOperationRemove: false, newConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() + c := testhelper.Config1() delete(c.Interface, "ethernet-1/1") c.Interface["ethernet-1/2"] = &sdcio_schema.SdcioModel_Interface{ AdminState: sdcio_schema.SdcioModelIf_AdminState_enable, @@ -240,19 +244,19 @@ func TestToXMLTable(t *testing.T) { } c.Patterntest = nil c.Choices.Case1.CaseElem.Elem = nil - return expandUpdateFromConfig(ctx, c, converter) + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, }, { name: "XML - replace direct leaf and choice", onlyNewOrUpdated: true, existingConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, runningConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, expected: ` @@ -266,30 +270,30 @@ func TestToXMLTable(t *testing.T) { operationWithNamespace: true, useOperationRemove: true, newConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() + c := testhelper.Config1() c.Patterntest = nil c.Choices.Case1.CaseElem.Elem = nil c.Choices.Case2 = &sdcio_schema.SdcioModel_Choices_Case2{ Log: ygot.Bool(true), } - return expandUpdateFromConfig(ctx, c, converter) + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, }, { name: "XML - empty", onlyNewOrUpdated: true, existingConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, runningConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, newConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() + c := testhelper.Config1() - upds, err := expandUpdateFromConfig(ctx, c, converter) + upds, err := testhelper.ExpandUpdateFromConfig(ctx, c, converter) if err != nil { return nil, err } @@ -306,7 +310,7 @@ func TestToXMLTable(t *testing.T) { name: "XML - presence", onlyNewOrUpdated: true, existingConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - //c := config1() + //c := testhelper.Config1() c := &sdcio_schema.Device{ NetworkInstance: map[string]*sdcio_schema.SdcioModel_NetworkInstance{ "default": { @@ -317,7 +321,7 @@ func TestToXMLTable(t *testing.T) { }, }, } - return expandUpdateFromConfig(ctx, c, converter) + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, expected: ` default @@ -330,7 +334,7 @@ func TestToXMLTable(t *testing.T) { operationWithNamespace: true, useOperationRemove: true, runningConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - // c := config1() + // c := testhelper.Config1() c := &sdcio_schema.Device{ NetworkInstance: map[string]*sdcio_schema.SdcioModel_NetworkInstance{ "default": { @@ -341,10 +345,10 @@ func TestToXMLTable(t *testing.T) { }, }, } - return expandUpdateFromConfig(ctx, c, converter) + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, newConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - // c := config1() + // c := testhelper.Config1() c := &sdcio_schema.Device{ NetworkInstance: map[string]*sdcio_schema.SdcioModel_NetworkInstance{ "default": { @@ -355,7 +359,7 @@ func TestToXMLTable(t *testing.T) { }, }, } - upds, err := expandUpdateFromConfig(ctx, c, converter) + upds, err := testhelper.ExpandUpdateFromConfig(ctx, c, converter) if err != nil { return nil, err } @@ -377,12 +381,12 @@ func TestToXMLTable(t *testing.T) { name: "XML - replace choice", onlyNewOrUpdated: true, existingConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, runningConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, expected: ` @@ -395,25 +399,25 @@ func TestToXMLTable(t *testing.T) { operationWithNamespace: true, useOperationRemove: true, newConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() + c := testhelper.Config1() c.Choices.Case1 = nil c.Choices.Case2 = &sdcio_schema.SdcioModel_Choices_Case2{ Log: ygot.Bool(true), } - return expandUpdateFromConfig(ctx, c, converter) + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, }, { name: "XML - device returns leaflist out of order", onlyNewOrUpdated: true, existingConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() - return expandUpdateFromConfig(ctx, c, converter) + c := testhelper.Config1() + return testhelper.ExpandUpdateFromConfig(ctx, c, converter) }, runningConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { - c := config1() + c := testhelper.Config1() slices.Reverse(c.Leaflist.Entry) - upds, err := expandUpdateFromConfig(ctx, c, converter) + upds, err := testhelper.ExpandUpdateFromConfig(ctx, c, converter) if err != nil { return nil, err } @@ -551,8 +555,8 @@ func TestToXMLTable(t *testing.T) { converter := utils.NewConverter(scb) - tc := NewTreeContext(scb, pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0))) - root, err := NewTreeRoot(ctx, tc) + tc := tree.NewTreeContext(scb, pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0))) + root, err := tree.NewTreeRoot(ctx, tc) if err != nil { t.Fatal(err) } @@ -561,7 +565,7 @@ func TestToXMLTable(t *testing.T) { if err != nil { t.Error(err) } - err = addToRoot(ctx, root, existingUpds, flagsExisting, owner, 5) + err = testhelper.AddToRoot(ctx, root.Entry, existingUpds, testhelper.FlagsExisting, owner, 5) if err != nil { t.Fatal(err) } @@ -571,9 +575,9 @@ func TestToXMLTable(t *testing.T) { if tt.newConfig != nil { sharedTaskPool := pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0)) - ownerDeleteMarker := NewOwnerDeleteMarker(NewOwnerDeleteMarkerTaskConfig(owner, false)) + ownerDeleteMarker := processors.NewOwnerDeleteMarker(processors.NewOwnerDeleteMarkerTaskConfig(owner, false)) - err = ownerDeleteMarker.Run(root.GetRoot(), sharedTaskPool) + err = ownerDeleteMarker.Run(root.Entry, sharedTaskPool) if err != nil { t.Error(err) return @@ -583,7 +587,7 @@ func TestToXMLTable(t *testing.T) { if err != nil { t.Error(err) } - err = addToRoot(ctx, root, newUpds, flagsNew, owner, 5) + err = testhelper.AddToRoot(ctx, root.Entry, newUpds, testhelper.FlagsNew, owner, 5) if err != nil { t.Fatal(err) } @@ -595,7 +599,7 @@ func TestToXMLTable(t *testing.T) { if err != nil { t.Error(err) } - err = addToRoot(ctx, root, runningUpds, flagsExisting, consts.RunningIntentName, consts.RunningValuesPrio) + err = testhelper.AddToRoot(ctx, root.Entry, runningUpds, testhelper.FlagsExisting, consts.RunningIntentName, consts.RunningValuesPrio) if err != nil { t.Fatal(err) } @@ -610,7 +614,7 @@ func TestToXMLTable(t *testing.T) { t.Error(err) } - xmlDoc, err := root.ToXML(tt.onlyNewOrUpdated, tt.honorNamespace, tt.operationWithNamespace, tt.useOperationRemove) + xmlDoc, err := ops.ToXML(ctx, root.Entry, tt.onlyNewOrUpdated, tt.honorNamespace, tt.operationWithNamespace, tt.useOperationRemove) if err != nil { t.Fatal(err) } diff --git a/pkg/tree/processor_blame_config.go b/pkg/tree/processors/processor_blame_config.go similarity index 99% rename from pkg/tree/processor_blame_config.go rename to pkg/tree/processors/processor_blame_config.go index 80a76f18..28b6ea35 100644 --- a/pkg/tree/processor_blame_config.go +++ b/pkg/tree/processors/processor_blame_config.go @@ -1,4 +1,4 @@ -package tree +package processors import ( "context" diff --git a/pkg/tree/processor_blame_config_test.go b/pkg/tree/processors/processor_blame_config_test.go similarity index 86% rename from pkg/tree/processor_blame_config_test.go rename to pkg/tree/processors/processor_blame_config_test.go index 09c477cd..70c7831a 100644 --- a/pkg/tree/processor_blame_config_test.go +++ b/pkg/tree/processors/processor_blame_config_test.go @@ -1,4 +1,4 @@ -package tree +package processors_test import ( "context" @@ -8,7 +8,9 @@ import ( "github.com/google/go-cmp/cmp" "github.com/openconfig/ygot/ygot" "github.com/sdcio/data-server/pkg/pool" + "github.com/sdcio/data-server/pkg/tree" "github.com/sdcio/data-server/pkg/tree/consts" + "github.com/sdcio/data-server/pkg/tree/processors" "github.com/sdcio/data-server/pkg/utils/testhelper" sdcio_schema "github.com/sdcio/data-server/tests/sdcioygot" sdcpb "github.com/sdcio/sdc-protos/sdcpb" @@ -24,14 +26,14 @@ func Test_sharedEntryAttributes_BlameConfig(t *testing.T) { tests := []struct { name string - r func(t *testing.T) *RootEntry + r func(t *testing.T) *tree.RootEntry includeDefaults bool want []byte wantErr bool }{ { name: "without defaults", - r: func(t *testing.T) *RootEntry { + r: func(t *testing.T) *tree.RootEntry { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() @@ -40,14 +42,14 @@ func Test_sharedEntryAttributes_BlameConfig(t *testing.T) { t.Fatal(err) } - tc := NewTreeContext(scb, pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0))) - root, err := NewTreeRoot(ctx, tc) + tc := tree.NewTreeContext(scb, pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0))) + root, err := tree.NewTreeRoot(ctx, tc) if err != nil { t.Fatal(err) } - conf1 := config1() - _, err = loadYgotStructIntoTreeRoot(ctx, conf1, root, owner1, 5, false, flagsNew) + conf1 := testhelper.Config1() + _, err = testhelper.LoadYgotStructIntoTreeRoot(ctx, conf1, root.Entry, owner1, 5, false, flagsNew) if err != nil { t.Fatal(err) } @@ -63,7 +65,7 @@ func Test_sharedEntryAttributes_BlameConfig(t *testing.T) { }, { name: "with defaults", - r: func(t *testing.T) *RootEntry { + r: func(t *testing.T) *tree.RootEntry { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() @@ -72,14 +74,14 @@ func Test_sharedEntryAttributes_BlameConfig(t *testing.T) { t.Fatal(err) } - tc := NewTreeContext(scb, pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0))) - root, err := NewTreeRoot(ctx, tc) + tc := tree.NewTreeContext(scb, pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0))) + root, err := tree.NewTreeRoot(ctx, tc) if err != nil { t.Fatal(err) } - conf1 := config1() - _, err = loadYgotStructIntoTreeRoot(ctx, conf1, root, owner1, 5, false, flagsNew) + conf1 := testhelper.Config1() + _, err = testhelper.LoadYgotStructIntoTreeRoot(ctx, conf1, root.Entry, owner1, 5, false, flagsNew) if err != nil { t.Fatal(err) } @@ -96,7 +98,7 @@ func Test_sharedEntryAttributes_BlameConfig(t *testing.T) { }, { name: "with defaults multiple intents", - r: func(t *testing.T) *RootEntry { + r: func(t *testing.T) *tree.RootEntry { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() @@ -105,25 +107,25 @@ func Test_sharedEntryAttributes_BlameConfig(t *testing.T) { t.Fatal(err) } - tc := NewTreeContext(scb, pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0))) - root, err := NewTreeRoot(ctx, tc) + tc := tree.NewTreeContext(scb, pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0))) + root, err := tree.NewTreeRoot(ctx, tc) if err != nil { t.Fatal(err) } - conf1 := config1() - _, err = loadYgotStructIntoTreeRoot(ctx, conf1, root, owner1, 5, false, flagsNew) + conf1 := testhelper.Config1() + _, err = testhelper.LoadYgotStructIntoTreeRoot(ctx, conf1, root.Entry, owner1, 5, false, flagsNew) if err != nil { t.Fatal(err) } - conf2 := config2() - _, err = loadYgotStructIntoTreeRoot(ctx, conf2, root, owner2, 10, false, flagsNew) + conf2 := testhelper.Config2() + _, err = testhelper.LoadYgotStructIntoTreeRoot(ctx, conf2, root.Entry, owner2, 10, false, flagsNew) if err != nil { t.Fatal(err) } - running := config1() + running := testhelper.Config1() running.Interface["ethernet-1/1"].Description = ygot.String("Changed Description") running.Interface["ethernet-1/3"] = &sdcio_schema.SdcioModel_Interface{ @@ -133,7 +135,7 @@ func Test_sharedEntryAttributes_BlameConfig(t *testing.T) { running.Patterntest = ygot.String("hallo 0") - _, err = loadYgotStructIntoTreeRoot(ctx, running, root, consts.RunningIntentName, consts.RunningValuesPrio, false, flagsExisting) + _, err = testhelper.LoadYgotStructIntoTreeRoot(ctx, running, root.Entry, consts.RunningIntentName, consts.RunningValuesPrio, false, flagsExisting) if err != nil { t.Fatal(err) } @@ -156,7 +158,7 @@ func Test_sharedEntryAttributes_BlameConfig(t *testing.T) { sharedPool := pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0)) vPool := sharedPool.NewVirtualPool(pool.VirtualFailFast) - bp := NewBlameConfigProcessor(NewBlameConfigProcessorConfig(tt.includeDefaults)) + bp := processors.NewBlameConfigProcessor(processors.NewBlameConfigProcessorConfig(tt.includeDefaults)) got, err := bp.Run(ctx, treeRoot.Entry, vPool) if err != nil { t.Errorf("BlameConfig() error %s", err) diff --git a/pkg/tree/processor_error_collection_test.go b/pkg/tree/processors/processor_error_collection_test.go similarity index 99% rename from pkg/tree/processor_error_collection_test.go rename to pkg/tree/processors/processor_error_collection_test.go index a0cb142f..cf473460 100644 --- a/pkg/tree/processor_error_collection_test.go +++ b/pkg/tree/processors/processor_error_collection_test.go @@ -1,4 +1,4 @@ -package tree +package processors_test import ( "context" diff --git a/pkg/tree/processor_explicit_delete.go b/pkg/tree/processors/processor_explicit_delete.go similarity index 99% rename from pkg/tree/processor_explicit_delete.go rename to pkg/tree/processors/processor_explicit_delete.go index 4a002cfe..de166243 100644 --- a/pkg/tree/processor_explicit_delete.go +++ b/pkg/tree/processors/processor_explicit_delete.go @@ -1,4 +1,4 @@ -package tree +package processors import ( "context" diff --git a/pkg/tree/processor_explicit_delete_test.go b/pkg/tree/processors/processor_explicit_delete_test.go similarity index 82% rename from pkg/tree/processor_explicit_delete_test.go rename to pkg/tree/processors/processor_explicit_delete_test.go index 3c6aac6a..ff10b93b 100644 --- a/pkg/tree/processor_explicit_delete_test.go +++ b/pkg/tree/processors/processor_explicit_delete_test.go @@ -1,4 +1,4 @@ -package tree +package processors_test import ( "context" @@ -9,6 +9,7 @@ import ( "github.com/openconfig/ygot/ygot" schemaClient "github.com/sdcio/data-server/pkg/datastore/clients/schema" "github.com/sdcio/data-server/pkg/pool" + "github.com/sdcio/data-server/pkg/tree" "github.com/sdcio/data-server/pkg/tree/api" "github.com/sdcio/data-server/pkg/tree/consts" "github.com/sdcio/data-server/pkg/tree/ops" @@ -30,7 +31,7 @@ func TestExplicitDeleteVisitor_Visit(t *testing.T) { tests := []struct { name string - root func() *RootEntry + root func() *tree.RootEntry owner string priority int32 explicitDeletes *sdcpb.PathSet @@ -41,7 +42,7 @@ func TestExplicitDeleteVisitor_Visit(t *testing.T) { name: "No Deletes", owner: owner2, priority: 50, - root: func() *RootEntry { + root: func() *tree.RootEntry { // create a gomock controller controller := gomock.NewController(t) defer controller.Finish() @@ -51,14 +52,14 @@ func TestExplicitDeleteVisitor_Visit(t *testing.T) { t.Fatal(err) } scb := schemaClient.NewSchemaClientBound(schema, sc) - tc := NewTreeContext(scb, pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0))) + tc := tree.NewTreeContext(scb, pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0))) - root, err := NewTreeRoot(ctx, tc) + root, err := tree.NewTreeRoot(ctx, tc) if err != nil { t.Error(err) } - _, err = loadYgotStructIntoTreeRoot(ctx, config1(), root, owner1, owner1Prio, false, flagsNew) + _, err = testhelper.LoadYgotStructIntoTreeRoot(ctx, testhelper.Config1(), root.Entry, owner1, owner1Prio, false, flagsNew) if err != nil { t.Error(err) } @@ -69,7 +70,7 @@ func TestExplicitDeleteVisitor_Visit(t *testing.T) { name: "Delete Field", owner: owner2, priority: 50, - root: func() *RootEntry { + root: func() *tree.RootEntry { // create a gomock controller controller := gomock.NewController(t) defer controller.Finish() @@ -79,19 +80,19 @@ func TestExplicitDeleteVisitor_Visit(t *testing.T) { t.Fatal(err) } scb := schemaClient.NewSchemaClientBound(schema, sc) - tc := NewTreeContext(scb, pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0))) + tc := tree.NewTreeContext(scb, pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0))) - root, err := NewTreeRoot(ctx, tc) + root, err := tree.NewTreeRoot(ctx, tc) if err != nil { t.Error(err) } - _, err = loadYgotStructIntoTreeRoot(ctx, config1(), root, owner1, owner1Prio, false, flagsExisting) + _, err = testhelper.LoadYgotStructIntoTreeRoot(ctx, testhelper.Config1(), root.Entry, owner1, owner1Prio, false, flagsExisting) if err != nil { t.Error(err) } - _, err = loadYgotStructIntoTreeRoot(ctx, config1(), root, consts.RunningIntentName, consts.RunningValuesPrio, false, flagsExisting) + _, err = testhelper.LoadYgotStructIntoTreeRoot(ctx, testhelper.Config1(), root.Entry, consts.RunningIntentName, consts.RunningValuesPrio, false, flagsExisting) if err != nil { t.Error(err) } @@ -121,7 +122,7 @@ func TestExplicitDeleteVisitor_Visit(t *testing.T) { name: "Delete Branch - existing owner data", owner: owner2, priority: 50, - root: func() *RootEntry { + root: func() *tree.RootEntry { // create a gomock controller controller := gomock.NewController(t) defer controller.Finish() @@ -131,30 +132,32 @@ func TestExplicitDeleteVisitor_Visit(t *testing.T) { t.Fatal(err) } scb := schemaClient.NewSchemaClientBound(schema, sc) - tc := NewTreeContext(scb, pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0))) + tc := tree.NewTreeContext(scb, pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0))) - root, err := NewTreeRoot(ctx, tc) + root, err := tree.NewTreeRoot(ctx, tc) if err != nil { t.Error(err) } - _, err = loadYgotStructIntoTreeRoot(ctx, config1(), root, owner1, owner1Prio, false, flagsExisting) + _, err = testhelper.LoadYgotStructIntoTreeRoot(ctx, testhelper.Config1(), root.Entry, owner1, owner1Prio, false, flagsExisting) if err != nil { t.Error(err) } - _, err = loadYgotStructIntoTreeRoot(ctx, config1(), root, consts.RunningIntentName, consts.RunningValuesPrio, false, flagsExisting) + _, err = testhelper.LoadYgotStructIntoTreeRoot(ctx, testhelper.Config1(), root.Entry, consts.RunningIntentName, consts.RunningValuesPrio, false, flagsExisting) if err != nil { t.Error(err) } - loadYgotStructIntoTreeRoot(ctx, &sdcio_schema.Device{Interface: map[string]*sdcio_schema.SdcioModel_Interface{ + _, err = testhelper.LoadYgotStructIntoTreeRoot(ctx, &sdcio_schema.Device{Interface: map[string]*sdcio_schema.SdcioModel_Interface{ "ethernet-1/1": { Name: ygot.String("ethernet-1/1"), Description: ygot.String("mydesc"), }, - }}, root, owner2, owner2Prio, false, flagsNew) - + }}, root.Entry, owner2, owner2Prio, false, flagsNew) + if err != nil { + t.Error(err) + } return root }, explicitDeletes: sdcpb.NewPathSet(). @@ -290,7 +293,7 @@ func TestExplicitDeleteVisitor_Visit(t *testing.T) { t.Log(root.String()) - lvs := ops.GetByOwner(root, owner2) + lvs := ops.GetByOwner(root.Entry, owner2) equal, err := lvs.Equal(tt.expectedLeafVariants) if err != nil { t.Error(err) diff --git a/pkg/tree/processor_importer.go b/pkg/tree/processors/processor_importer.go similarity index 96% rename from pkg/tree/processor_importer.go rename to pkg/tree/processors/processor_importer.go index 34a6c2fa..542a711a 100644 --- a/pkg/tree/processor_importer.go +++ b/pkg/tree/processors/processor_importer.go @@ -1,4 +1,4 @@ -package tree +package processors import ( "context" @@ -7,9 +7,9 @@ import ( "sync" "github.com/sdcio/data-server/pkg/pool" - "github.com/sdcio/data-server/pkg/tree/api" treeimporter "github.com/sdcio/data-server/pkg/tree/importer" + "github.com/sdcio/data-server/pkg/tree/ops" "github.com/sdcio/data-server/pkg/tree/types" sdcpb "github.com/sdcio/sdc-protos/sdcpb" "google.golang.org/protobuf/types/known/emptypb" @@ -107,7 +107,7 @@ func (task importConfigTask) Run(ctx context.Context, submit func(pool.Task) err var actual api.Entry = task.entry var keyChild api.Entry - keys := task.entry.GetSchemaKeys() + keys := ops.GetSchemaKeys(task.entry) slices.Sort(keys) for _, k := range keys { ktrans := task.importerElement.GetElement(k) @@ -119,7 +119,7 @@ func (task importConfigTask) Run(ctx context.Context, submit func(pool.Task) err return err } if keyChild, exists = actual.GetChild(kv); !exists { - keyChild, err = NewEntry(ctx, actual, kv, task.params.treeContext) + keyChild, err = api.NewEntry(ctx, actual, kv, task.params.treeContext) if err != nil { return err } @@ -148,7 +148,7 @@ func (task importConfigTask) Run(ctx context.Context, submit func(pool.Task) err child, exists := task.entry.GetChild(childElt.GetName()) if !exists { var err error - child, err = NewEntry(ctx, task.entry, childElt.GetName(), task.params.treeContext) + child, err = api.NewEntry(ctx, task.entry, childElt.GetName(), task.params.treeContext) if err != nil { return fmt.Errorf("error inserting %s at %s: %w", childElt.GetName(), task.entry.SdcpbPath().ToXPath(false), err) } diff --git a/pkg/tree/processor_mark_owner_delete.go b/pkg/tree/processors/processor_mark_owner_delete.go similarity index 99% rename from pkg/tree/processor_mark_owner_delete.go rename to pkg/tree/processors/processor_mark_owner_delete.go index 655a362d..10d160cf 100644 --- a/pkg/tree/processor_mark_owner_delete.go +++ b/pkg/tree/processors/processor_mark_owner_delete.go @@ -1,4 +1,4 @@ -package tree +package processors import ( "context" diff --git a/pkg/tree/processor_remove_deleted.go b/pkg/tree/processors/processor_remove_deleted.go similarity index 99% rename from pkg/tree/processor_remove_deleted.go rename to pkg/tree/processors/processor_remove_deleted.go index 09556a20..dbec3156 100644 --- a/pkg/tree/processor_remove_deleted.go +++ b/pkg/tree/processors/processor_remove_deleted.go @@ -1,4 +1,4 @@ -package tree +package processors import ( "context" diff --git a/pkg/tree/processor_reset_flags.go b/pkg/tree/processors/processor_reset_flags.go similarity index 99% rename from pkg/tree/processor_reset_flags.go rename to pkg/tree/processors/processor_reset_flags.go index b38bd442..607f5e66 100644 --- a/pkg/tree/processor_reset_flags.go +++ b/pkg/tree/processors/processor_reset_flags.go @@ -1,4 +1,4 @@ -package tree +package processors import ( "context" diff --git a/pkg/tree/processor_reset_flags_test.go b/pkg/tree/processors/processor_reset_flags_test.go similarity index 79% rename from pkg/tree/processor_reset_flags_test.go rename to pkg/tree/processors/processor_reset_flags_test.go index 0619ae83..0c535bc0 100644 --- a/pkg/tree/processor_reset_flags_test.go +++ b/pkg/tree/processors/processor_reset_flags_test.go @@ -1,4 +1,4 @@ -package tree +package processors_test import ( "context" @@ -8,7 +8,10 @@ import ( schemaClient "github.com/sdcio/data-server/pkg/datastore/clients/schema" "github.com/sdcio/data-server/pkg/pool" + "github.com/sdcio/data-server/pkg/tree" . "github.com/sdcio/data-server/pkg/tree/consts" + "github.com/sdcio/data-server/pkg/tree/ops" + "github.com/sdcio/data-server/pkg/tree/processors" "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils/testhelper" sdcpb "github.com/sdcio/sdc-protos/sdcpb" @@ -16,6 +19,17 @@ import ( "github.com/stretchr/testify/require" ) +var ( + flagsNew *types.UpdateInsertFlags + flagsExisting *types.UpdateInsertFlags +) + +func init() { + flagsNew = types.NewUpdateInsertFlags() + flagsNew.SetNewFlag() + flagsExisting = types.NewUpdateInsertFlags() +} + // TestResetFlagsProcessorRun tests the Run method of ResetFlagsProcessor func TestResetFlagsProcessorRun(t *testing.T) { tests := []struct { @@ -24,7 +38,7 @@ func TestResetFlagsProcessorRun(t *testing.T) { newFlag bool updateFlag bool wantErr bool - tree func() *RootEntry + tree func() *tree.RootEntry adjustCount int64 }{ { @@ -34,7 +48,7 @@ func TestResetFlagsProcessorRun(t *testing.T) { updateFlag: true, wantErr: false, adjustCount: 4, - tree: func() *RootEntry { + tree: func() *tree.RootEntry { ctx := context.Background() @@ -43,14 +57,14 @@ func TestResetFlagsProcessorRun(t *testing.T) { t.Fatal(err) } scb := schemaClient.NewSchemaClientBound(schema, sc) - tc := NewTreeContext(scb, pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0))) + tc := tree.NewTreeContext(scb, pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0))) - root, err := NewTreeRoot(ctx, tc) + root, err := tree.NewTreeRoot(ctx, tc) if err != nil { t.Fatalf("failed to create new tree root: %v", err) } - _, err = root.AddUpdateRecursive(ctx, + _, err = ops.AddUpdateRecursive(ctx, root.Entry, &sdcpb.Path{Elem: []*sdcpb.PathElem{ sdcpb.NewPathElem("interface", map[string]string{"name": "ethernet-1/1"}), sdcpb.NewPathElem("description", nil)}, @@ -62,7 +76,7 @@ func TestResetFlagsProcessorRun(t *testing.T) { t.Fatalf("failed to add update recursive: %v", err) } - _, err = root.AddUpdateRecursive(ctx, + _, err = ops.AddUpdateRecursive(ctx, root.Entry, &sdcpb.Path{Elem: []*sdcpb.PathElem{ sdcpb.NewPathElem("interface", map[string]string{"name": "ethernet-1/2"}), sdcpb.NewPathElem("description", nil)}, @@ -90,7 +104,7 @@ func TestResetFlagsProcessorRun(t *testing.T) { ctx := context.Background() // Create processor parameters - params := NewResetFlagsProcessorParameters() + params := processors.NewResetFlagsProcessorParameters() if tt.deleteFlag { params.SetDeleteFlag() @@ -103,7 +117,7 @@ func TestResetFlagsProcessorRun(t *testing.T) { } // Create processor - processor := NewResetFlagsProcessor(params) + processor := processors.NewResetFlagsProcessor(params) require.NotNil(t, processor) // Create a mock entry for testing @@ -117,7 +131,7 @@ func TestResetFlagsProcessorRun(t *testing.T) { fmt.Println(root.String()) - processorErr := processor.Run(root.GetRoot(), taskPool) + processorErr := processor.Run(root.Entry, taskPool) if (processorErr != nil) != tt.wantErr { t.Errorf("ResetFlagsProcessor.Run() error = %v, wantErr %v", processorErr, tt.wantErr) return diff --git a/pkg/tree/proto.go b/pkg/tree/proto.go deleted file mode 100644 index 7bd11cb7..00000000 --- a/pkg/tree/proto.go +++ /dev/null @@ -1,21 +0,0 @@ -package tree - -import ( - "context" - - sdcpb "github.com/sdcio/sdc-protos/sdcpb" -) - -func (r *RootEntry) ToProtoUpdates(ctx context.Context, onlyNewOrUpdated bool) ([]*sdcpb.Update, error) { - upds := r.GetHighestPrecedence(onlyNewOrUpdated) - return upds.ToSdcpbUpdateSlice(), nil -} - -func (r *RootEntry) ToProtoDeletes(ctx context.Context) ([]*sdcpb.Path, error) { - deletes, err := r.GetDeletes(true) - if err != nil { - return nil, err - } - - return deletes.SdcpbPaths(), nil -} diff --git a/pkg/tree/root_entry.go b/pkg/tree/root_entry.go index 1e4b9316..a1e6e795 100644 --- a/pkg/tree/root_entry.go +++ b/pkg/tree/root_entry.go @@ -5,18 +5,16 @@ import ( "fmt" "os" "strings" - "sync" - "github.com/sdcio/data-server/pkg/config" "github.com/sdcio/data-server/pkg/pool" "github.com/sdcio/data-server/pkg/tree/api" "github.com/sdcio/data-server/pkg/tree/importer" "github.com/sdcio/data-server/pkg/tree/ops" + "github.com/sdcio/data-server/pkg/tree/processors" "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils" logf "github.com/sdcio/logger" sdcpb "github.com/sdcio/sdc-protos/sdcpb" - "github.com/sdcio/sdc-protos/tree_persist" ) // RootEntry the root of the cache.Update tree @@ -24,13 +22,9 @@ type RootEntry struct { api.Entry } -var ( - ErrorIntentNotPresent = fmt.Errorf("intent not present") -) - // NewTreeRoot Instantiate a new Tree Root element. func NewTreeRoot(ctx context.Context, tc api.TreeContext) (*RootEntry, error) { - sea, err := newSharedEntryAttributes(ctx, nil, "", tc) + sea, err := NewSharedEntryAttributes(ctx, nil, "", tc) if err != nil { return nil, err } @@ -66,7 +60,7 @@ func (r *RootEntry) AddUpdatesRecursive(ctx context.Context, us []*types.PathAnd var err error for idx, u := range us { _ = idx - _, err = r.Entry.AddUpdateRecursive(ctx, u.GetPath(), u.GetUpdate(), flags) + _, err = ops.AddUpdateRecursive(ctx, r.Entry, u.GetPath(), u.GetUpdate(), flags) if err != nil { return err } @@ -75,11 +69,11 @@ func (r *RootEntry) AddUpdatesRecursive(ctx context.Context, us []*types.PathAnd } func (r *RootEntry) ImportConfig(ctx context.Context, basePath *sdcpb.Path, importer importer.ImportConfigAdapter, flags *types.UpdateInsertFlags, poolFactory pool.VirtualPoolFactory) (*types.ImportStats, error) { - e, err := r.Entry.GetOrCreateChilds(ctx, basePath) + e, err := ops.GetOrCreateChilds(ctx, r.Entry, basePath) if err != nil { return nil, err } - ImportConfigProcessor := NewImportConfigProcessor(importer, flags) + ImportConfigProcessor := processors.NewImportConfigProcessor(importer, flags) err = ImportConfigProcessor.Run(ctx, e, poolFactory) if err != nil { return nil, err @@ -91,33 +85,6 @@ func (r *RootEntry) SetNonRevertiveIntent(intentName string, nonRevertive bool) r.GetTreeContext().NonRevertiveInfo().Add(intentName, nonRevertive) } -func (r *RootEntry) Validate(ctx context.Context, vCfg *config.Validation, taskpoolFactory pool.VirtualPoolFactory) (types.ValidationResults, *types.ValidationStats) { - // perform validation - // we use a channel and cumulate all the errors - validationResultEntryChan := make(chan *types.ValidationResultEntry, 10) - validationStats := types.NewValidationStats() - - // create a ValidationResult struct - validationResult := types.ValidationResults{} - - syncWait := &sync.WaitGroup{} - syncWait.Add(1) - go func() { - // read from the validationResult channel - for e := range validationResultEntryChan { - validationResult.AddEntry(e) - } - syncWait.Done() - }() - - validationProcessor := NewValidateProcessor(NewValidateProcessorConfig(validationResultEntryChan, validationStats, vCfg)) - validationProcessor.Run(taskpoolFactory, r.Entry) - close(validationResultEntryChan) - - syncWait.Wait() - return validationResult, validationStats -} - // String returns the string representation of the Tree. func (r *RootEntry) String() string { s := []string{} @@ -166,35 +133,6 @@ func (r *RootEntry) GetAncestorSchema() (*sdcpb.SchemaElem, int) { return nil, 0 } -func (r *RootEntry) GetDeviations(ctx context.Context, ch chan<- *types.DeviationEntry) { - r.Entry.GetDeviations(ctx, ch, true) -} - -func (r *RootEntry) TreeExport(owner string, priority int32) (*tree_persist.Intent, error) { - treeExport, err := r.Entry.TreeExport(owner) - if err != nil { - return nil, err - } - - explicitDeletes := r.GetTreeContext().ExplicitDeletes().GetByIntentName(owner).ToPathSlice() - - var rootExportEntry *tree_persist.TreeElement - if len(treeExport) != 0 { - rootExportEntry = treeExport[0] - } - - if rootExportEntry != nil || len(explicitDeletes) > 0 { - return &tree_persist.Intent{ - IntentName: owner, - Root: rootExportEntry, - Priority: priority, - NonRevertive: r.GetTreeContext().NonRevertiveInfo().IsGenerallyNonRevertive(owner), - ExplicitDeletes: explicitDeletes, - }, nil - } - return nil, ErrorIntentNotPresent -} - // getByOwnerFiltered returns the Tree content filtered by owner, whilst allowing to filter further // via providing additional LeafEntryFilter func (r *RootEntry) getByOwnerFiltered(owner string, f ...api.LeafEntryFilter) []*api.LeafEntry { @@ -220,7 +158,7 @@ NEXTELEMENT: // DeleteSubtree Deletes from the tree, all elements of the PathSlice defined branch of the given owner. Return values are remainsToExist and error if an error occured. func (r *RootEntry) DeleteBranchPaths(ctx context.Context, deletes types.DeleteEntriesList, intentName string) error { for _, del := range deletes { - err := r.DeleteBranch(ctx, del.SdcpbPath(), intentName) + err := ops.DeleteBranch(ctx, r.Entry, del.SdcpbPath(), intentName) if err != nil { return err } @@ -230,12 +168,12 @@ func (r *RootEntry) DeleteBranchPaths(ctx context.Context, deletes types.DeleteE func (r *RootEntry) FinishInsertionPhase(ctx context.Context) error { log := logf.FromContext(ctx) - edpsc := ExplicitDeleteProcessorStatCollection{} + edpsc := processors.ExplicitDeleteProcessorStatCollection{} // apply the explicit deletes for deletePathPrio := range r.GetTreeContext().ExplicitDeletes().Items() { - params := NewExplicitDeleteTaskParameters(deletePathPrio.GetOwner(), deletePathPrio.GetPrio()) + params := processors.NewExplicitDeleteTaskParameters(deletePathPrio.GetOwner(), deletePathPrio.GetPrio()) for path := range deletePathPrio.PathItems() { @@ -245,7 +183,7 @@ func (r *RootEntry) FinishInsertionPhase(ctx context.Context) error { if err != nil { log.Error(nil, "Applying explicit delete - path not found, skipping", "severity", "WARN", "path", path.ToXPath(false)) } - edp := NewExplicitDeleteProcessor(params) + edp := processors.NewExplicitDeleteProcessor(params) err = edp.Run(ctx, entry, r.GetTreeContext().PoolFactory()) if err != nil { return err diff --git a/pkg/tree/root_entry_test.go b/pkg/tree/root_entry_test.go index 6c465c80..3ae53958 100644 --- a/pkg/tree/root_entry_test.go +++ b/pkg/tree/root_entry_test.go @@ -14,6 +14,8 @@ import ( "github.com/sdcio/data-server/pkg/pool" "github.com/sdcio/data-server/pkg/tree/api" jsonImporter "github.com/sdcio/data-server/pkg/tree/importer/json" + "github.com/sdcio/data-server/pkg/tree/ops" + "github.com/sdcio/data-server/pkg/tree/processors" "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils/testhelper" sdcio_schema "github.com/sdcio/data-server/tests/sdcioygot" @@ -46,7 +48,7 @@ func TestRootEntry_TreeExport(t *testing.T) { result := &sharedEntryAttributes{ parent: nil, pathElemName: "", - childs: newChildMap(), + childs: api.NewChildMap(), schemaMutex: sync.RWMutex{}, cacheMutex: sync.Mutex{}, treeContext: tc, @@ -94,7 +96,7 @@ func TestRootEntry_TreeExport(t *testing.T) { result := &sharedEntryAttributes{ parent: nil, pathElemName: "", - childs: newChildMap(), + childs: api.NewChildMap(), schemaMutex: sync.RWMutex{}, cacheMutex: sync.Mutex{}, treeContext: tc, @@ -105,7 +107,7 @@ func TestRootEntry_TreeExport(t *testing.T) { interf := &sharedEntryAttributes{ parent: result, pathElemName: "interface", - childs: newChildMap(), + childs: api.NewChildMap(), schemaMutex: sync.RWMutex{}, cacheMutex: sync.Mutex{}, } @@ -159,7 +161,7 @@ func TestRootEntry_TreeExport(t *testing.T) { result := &sharedEntryAttributes{ parent: nil, pathElemName: "", - childs: newChildMap(), + childs: api.NewChildMap(), schemaMutex: sync.RWMutex{}, cacheMutex: sync.Mutex{}, treeContext: tc, @@ -170,7 +172,7 @@ func TestRootEntry_TreeExport(t *testing.T) { interf := &sharedEntryAttributes{ parent: result, pathElemName: "interface", - childs: newChildMap(), + childs: api.NewChildMap(), schemaMutex: sync.RWMutex{}, cacheMutex: sync.Mutex{}, } @@ -203,7 +205,7 @@ func TestRootEntry_TreeExport(t *testing.T) { system := &sharedEntryAttributes{ parent: result, pathElemName: "system", - childs: newChildMap(), + childs: api.NewChildMap(), schemaMutex: sync.RWMutex{}, cacheMutex: sync.Mutex{}, } @@ -257,7 +259,7 @@ func TestRootEntry_TreeExport(t *testing.T) { result := &sharedEntryAttributes{ parent: nil, pathElemName: "", - childs: newChildMap(), + childs: api.NewChildMap(), schemaMutex: sync.RWMutex{}, cacheMutex: sync.Mutex{}, treeContext: tc, @@ -268,7 +270,7 @@ func TestRootEntry_TreeExport(t *testing.T) { interf := &sharedEntryAttributes{ parent: result, pathElemName: "interface", - childs: newChildMap(), + childs: api.NewChildMap(), schemaMutex: sync.RWMutex{}, cacheMutex: sync.Mutex{}, } @@ -301,7 +303,7 @@ func TestRootEntry_TreeExport(t *testing.T) { r := &RootEntry{ Entry: tt.sharedEntryAttributes(), } - got, err := r.TreeExport(tt.args.owner, tt.args.priority) + got, err := ops.TreeExport(r.Entry, tt.args.owner, tt.args.priority) if (err != nil) != tt.wantErr { t.Fatalf("RootEntry.TreeExport() error = %v, wantErr %v", err, tt.wantErr) return @@ -397,7 +399,7 @@ func TestRootEntry_DeleteSubtreePaths(t *testing.T) { t.Fatal(err) } - _, err = loadYgotStructIntoTreeRoot(ctx, tt.re(), root, owner1, 500, false, flagsNew) + _, err = testhelper.LoadYgotStructIntoTreeRoot(ctx, tt.re(), root.Entry, owner1, 500, false, testhelper.FlagsNew) if err != nil { t.Fatal(err) } @@ -447,7 +449,7 @@ func TestRootEntry_AddUpdatesRecursive(t *testing.T) { name: "simple add", fields: fields{ sharedEntryAttributes: func(t *testing.T) *sharedEntryAttributes { - s, err := newSharedEntryAttributes(ctx, nil, "", tc) + s, err := NewSharedEntryAttributes(ctx, nil, "", tc) if err != nil { t.Fatal(err) } @@ -487,7 +489,7 @@ func TestRootEntry_AddUpdatesRecursive(t *testing.T) { flags: types.NewUpdateInsertFlags(), }, want: func(t *testing.T) *RootEntry { - s, err := newSharedEntryAttributes(ctx, nil, "", tc) + s, err := NewSharedEntryAttributes(ctx, nil, "", tc) if err != nil { t.Fatal(err) } @@ -515,7 +517,7 @@ func TestRootEntry_AddUpdatesRecursive(t *testing.T) { } vpf := pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0)) - ImportConfigProcessor := NewImportConfigProcessor(jsonImporter.NewJsonTreeImporter(jsonAny, "owner1", 5, false), types.NewUpdateInsertFlags()) + ImportConfigProcessor := processors.NewImportConfigProcessor(jsonImporter.NewJsonTreeImporter(jsonAny, "owner1", 5, false), types.NewUpdateInsertFlags()) err = ImportConfigProcessor.Run(ctx, s, vpf) if err != nil { t.Fatal(err) @@ -566,11 +568,11 @@ func TestRootEntry_GetUpdatesForOwner(t *testing.T) { if err != nil { t.Fatal(err) } - _, err = loadYgotStructIntoTreeRoot(ctx, config1(), root, owner1, 500, false, flagsNew) + _, err = testhelper.LoadYgotStructIntoTreeRoot(ctx, testhelper.Config1(), root.Entry, owner1, 500, false, testhelper.FlagsNew) if err != nil { t.Fatal(err) } - _, err = loadYgotStructIntoTreeRoot(ctx, config2(), root, owner2, 400, false, flagsNew) + _, err = testhelper.LoadYgotStructIntoTreeRoot(ctx, testhelper.Config2(), root.Entry, owner2, 400, false, testhelper.FlagsNew) if err != nil { t.Fatal(err) } @@ -587,7 +589,7 @@ func TestRootEntry_GetUpdatesForOwner(t *testing.T) { if err != nil { t.Fatal(err) } - _, err = loadYgotStructIntoTreeRoot(ctx, config1(), root, owner1, 500, false, flagsNew) + _, err = testhelper.LoadYgotStructIntoTreeRoot(ctx, testhelper.Config1(), root.Entry, owner1, 500, false, testhelper.FlagsNew) if err != nil { t.Fatal(err) } @@ -609,7 +611,7 @@ func TestRootEntry_GetUpdatesForOwner(t *testing.T) { if err != nil { t.Fatal(err) } - err = resultRoot.AddUpdatesRecursive(ctx, got, flagsNew) + err = resultRoot.AddUpdatesRecursive(ctx, got, testhelper.FlagsNew) if err != nil { t.Fatal(err) } diff --git a/pkg/tree/sharedEntryAttributes.go b/pkg/tree/sharedEntryAttributes.go index 94497aa4..b1c6d8e1 100644 --- a/pkg/tree/sharedEntryAttributes.go +++ b/pkg/tree/sharedEntryAttributes.go @@ -1,32 +1,19 @@ package tree import ( - "cmp" "context" - "errors" "fmt" "math" - "regexp" "slices" "sort" "strings" "sync" - "unicode/utf8" - "github.com/sdcio/data-server/pkg/config" "github.com/sdcio/data-server/pkg/tree/api" - "github.com/sdcio/data-server/pkg/tree/consts" "github.com/sdcio/data-server/pkg/tree/ops" "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils" - logf "github.com/sdcio/logger" sdcpb "github.com/sdcio/sdc-protos/sdcpb" - "github.com/sdcio/sdc-protos/tree_persist" - sdcpbutils "github.com/sdcio/sdc-protos/utils" -) - -var ( - ValidationError = errors.New("validation error") ) // sharedEntryAttributes contains the attributes shared by Entry and RootEntry @@ -36,7 +23,7 @@ type sharedEntryAttributes struct { // pathElemName the path elements name the entry represents pathElemName string // childs mutual exclusive with LeafVariants - childs *childMap + childs *api.ChildMap // leafVariants mutual exclusive with Childs // If Entry is a leaf it can hold multiple leafVariants leafVariants *api.LeafVariants @@ -60,7 +47,7 @@ type sharedEntryAttributes struct { // NewEntry constructor for Entries func NewEntry(ctx context.Context, parent api.Entry, pathElemName string, tc api.TreeContext) (*sharedEntryAttributes, error) { // create a new sharedEntryAttributes instance - sea, err := newSharedEntryAttributes(ctx, parent, pathElemName, tc) + sea, err := NewSharedEntryAttributes(ctx, parent, pathElemName, tc) if err != nil { return nil, err } @@ -74,7 +61,7 @@ func (s *sharedEntryAttributes) DeepCopy(tc api.TreeContext, parent api.Entry) ( result := &sharedEntryAttributes{ parent: parent, pathElemName: s.pathElemName, - childs: newChildMap(), + childs: api.NewChildMap(), schema: s.schema, treeContext: tc, choicesResolvers: s.choicesResolvers.deepCopy(), @@ -98,11 +85,15 @@ func (s *sharedEntryAttributes) DeepCopy(tc api.TreeContext, parent api.Entry) ( return result, nil } -func newSharedEntryAttributes(ctx context.Context, parent api.Entry, pathElemName string, tc api.TreeContext) (*sharedEntryAttributes, error) { +func (s *sharedEntryAttributes) GetChildMap() *api.ChildMap { + return s.childs +} + +func NewSharedEntryAttributes(ctx context.Context, parent api.Entry, pathElemName string, tc api.TreeContext) (*sharedEntryAttributes, error) { s := &sharedEntryAttributes{ parent: parent, pathElemName: pathElemName, - childs: newChildMap(), + childs: api.NewChildMap(), treeContext: tc, } s.leafVariants = api.NewLeafVariants(tc, s) @@ -125,13 +116,6 @@ func newSharedEntryAttributes(ctx context.Context, parent api.Entry, pathElemNam return s, nil } -func (s *sharedEntryAttributes) GetRoot() api.Entry { - if s.IsRoot() { - return s - } - return s.parent.GetRoot() -} - func (s *sharedEntryAttributes) GetTreeContext() api.TreeContext { return s.treeContext } @@ -162,7 +146,7 @@ func (s *sharedEntryAttributes) loadDefaults(ctx context.Context) error { } // get the first ancestor with a schema and how many levels up that is - ancestor, levelsUp := s.GetFirstAncestorWithSchema() + ancestor, levelsUp := ops.GetFirstAncestorWithSchema(s) // retrieve the container schema ancestorContainerSchema := ancestor.GetSchema().GetContainer() @@ -185,115 +169,6 @@ func (s *sharedEntryAttributes) loadDefaults(ctx context.Context) error { return nil } -func (s *sharedEntryAttributes) GetDeviations(ctx context.Context, ch chan<- *types.DeviationEntry, activeCase bool) { - evalLeafvariants := true - // if s is a presence container but has active childs, it should not be treated as a presence - // container, hence the leafvariants should not be processed. For presence container with - // childs the TypedValue.empty_val in the presence container is irrelevant. - if s.schema.GetContainer().GetIsPresence() && len(s.GetChilds(types.DescendMethodActiveChilds)) > 0 { - evalLeafvariants = false - } - - if evalLeafvariants { - // calculate Deviation on the LeafVariants - s.leafVariants.GetDeviations(ctx, ch, activeCase) - } - - // get all active childs - activeChilds := s.GetChilds(types.DescendMethodActiveChilds) - - // iterate through all childs - for cName, c := range s.getChildren() { - // check if c is a active child (choice / case) - _, isActiveChild := activeChilds[cName] - // recurse the call - c.GetDeviations(ctx, ch, isActiveChild) - } -} - -func (s *sharedEntryAttributes) checkAndCreateKeysAsLeafs(ctx context.Context, intentName string, prio int32, insertFlag *types.UpdateInsertFlags) error { - // keys themselfes do not have a schema attached. - // keys must be added to the last keys level, since that is carrying the list elements data - // hence if the entry has a schema attached, there is nothing to be done, return. - if s.schema != nil { - return nil - } - - // get the first ancestor with a schema and how many levels up that is - ancestor, levelsUp := s.GetFirstAncestorWithSchema() - - // retrieve the container schema - ancestorContainerSchema := ancestor.GetSchema().GetContainer() - // if it is not a container, return - if ancestorContainerSchema == nil { - return nil - } - - // if we're in the last level of keys, then we need to add the defaults - if len(ancestorContainerSchema.Keys) == levelsUp { - keySorted := make([]*sdcpb.LeafSchema, 0, len(ancestor.GetSchema().GetContainer().Keys)) - // add key leafschemas to slice - keySorted = append(keySorted, ancestor.GetSchema().GetContainer().Keys...) - // sort keySorted slice - slices.SortFunc(keySorted, func(a, b *sdcpb.LeafSchema) int { - return cmp.Compare(b.Name, a.Name) - }) - - // iterate through the keys - var item api.Entry = s - - // construct the key path - // doing so outside the loop to reuse - path := &sdcpb.Path{ - IsRootBased: false, - } - - for _, k := range keySorted { - child, entryExists := s.childs.GetEntry(k.Name) - // if the key Leaf exists continue with next key - if entryExists { - // if it exists, we need to check that the entry for the owner exists. - lvs := ops.GetByOwner(child, intentName) - if len(lvs) > 0 { - lvs[0].DropDeleteFlag() - // continue with parent Entry BEFORE continuing the loop - item = item.GetParent() - continue - } - } - - // convert the key value to the schema defined Typed_Value - tv, err := sdcpb.TVFromString(k.GetType(), item.PathName(), 0) - if err != nil { - return err - } - if !entryExists { - // create a new entry - child, err = NewEntry(ctx, s, k.Name, s.treeContext) - - if err != nil { - return err - } - } - // add the new child entry to s - err = s.AddChild(ctx, child) - if err != nil { - return err - } - - // Add the update to the tree - _, err = child.AddUpdateRecursive(ctx, path, types.NewUpdate(nil, tv, prio, intentName, 0), insertFlag) - if err != nil { - return err - } - - // continue with parent Entry - item = item.GetParent() - } - } - return nil -} - func (s *sharedEntryAttributes) populateSchema(ctx context.Context) error { getSchema := true var path *sdcpb.Path @@ -307,7 +182,7 @@ func (s *sharedEntryAttributes) populateSchema(ctx context.Context) error { // because we can have multiple keys. we remember the number of levels we moved up // and if that is within the len of keys, we're still in a key level, and need to skip // querying the schema. Otherwise we need to query the schema. - ancesterschema, levelUp := s.GetFirstAncestorWithSchema() + ancesterschema, levelUp := ops.GetFirstAncestorWithSchema(s) // check the found schema switch schem := ancesterschema.GetSchema().GetSchema().(type) { @@ -342,11 +217,6 @@ func (s *sharedEntryAttributes) GetSchema() *sdcpb.SchemaElem { return s.schema } -// GetChildren returns the children Map of the Entry -func (s *sharedEntryAttributes) getChildren() map[string]api.Entry { - return s.childs.GetAll() -} - // getListChilds collects all the childs of the list. In the tree we store them seperated into their key branches. // this is collecting all the last level key entries. func (s *sharedEntryAttributes) GetListChilds() ([]api.Entry, error) { @@ -390,7 +260,7 @@ func (s *sharedEntryAttributes) FilterChilds(keys map[string]string) ([]api.Entr processEntries := []api.Entry{s} // retrieve the schema keys - schemaKeys := s.GetSchemaKeys() + schemaKeys := ops.GetSchemaKeys(s) // sort the keys, such that they appear in the order that they // are inserted in the tree sort.Strings(schemaKeys) @@ -439,6 +309,8 @@ func (s *sharedEntryAttributes) IsRoot() bool { // GetLevel returns the level / depth position of this element in the tree func (s *sharedEntryAttributes) GetLevel() int { + s.cacheMutex.Lock() + defer s.cacheMutex.Unlock() // if level is cached, return level if s.level != nil { return *s.level @@ -447,11 +319,16 @@ func (s *sharedEntryAttributes) GetLevel() int { if s.IsRoot() { return 0 } - // Get parent level and add 1 - l := s.parent.GetLevel() + 1 + // Count levels iteratively by walking up to root + level := 0 + current := s.parent + for current != nil { + level++ + current = current.GetParent() + } // cache level value - s.level = &l - return l + s.level = &level + return level } func (s *sharedEntryAttributes) HoldsLeafvariants() bool { @@ -466,23 +343,6 @@ func (s *sharedEntryAttributes) HoldsLeafvariants() bool { return false } -// GetSchemaKeys checks for the schema of the entry, and returns the defined keys -func (s *sharedEntryAttributes) GetSchemaKeys() []string { - if s.schema != nil { - // if the schema is a container schema, we need to process the aggregation logic - if contschema := s.schema.GetContainer(); contschema != nil { - // if the level equals the amount of keys defined, we're at the right level, where the - // actual elements start (not in a key level within the tree) - var keys []string - for _, k := range contschema.GetKeys() { - keys = append(keys, k.Name) - } - return keys - } - } - return nil -} - // getAggregatedDeletes is called on levels that have no schema attached, meaning key schemas. // here we might delete the whole branch of the tree, if all key elements are being deleted // if not, we continue with regular deltes @@ -490,11 +350,11 @@ func (s *sharedEntryAttributes) GetAggregatedDeletes(deletes []types.DeleteEntry var err error // we take a look into the level(s) up // trying to get the schema - ancestor, level := s.GetFirstAncestorWithSchema() + ancestor, level := ops.GetFirstAncestorWithSchema(s) // check if the first schema on the path upwards (parents) // has keys defined (meaning is a contianer with keys) - keys := ancestor.GetSchemaKeys() + keys := ops.GetSchemaKeys(ancestor) // if keys exist and we're on the last level of the keys, validate // if aggregation can happen @@ -568,7 +428,7 @@ func (s *sharedEntryAttributes) CanDeleteBranch(keepDefault bool) bool { } // handle containers - for _, c := range s.childs.Items() { + for _, c := range s.childs.GetAll() { canDelete := c.CanDeleteBranch(keepDefault) if !canDelete { return false @@ -656,10 +516,10 @@ func (s *sharedEntryAttributes) RemainsToExist() bool { } // getRegularDeletes performs deletion calculation on elements that have a schema attached. -func (s *sharedEntryAttributes) getRegularDeletes(deletes []types.DeleteEntry, aggregate bool) ([]types.DeleteEntry, error) { +func (s *sharedEntryAttributes) getRegularDeletes(deletes types.DeleteEntriesList, aggregate bool) (types.DeleteEntriesList, error) { var err error - if s.ShouldDelete() && !s.IsRoot() && len(s.GetSchemaKeys()) == 0 { + if s.ShouldDelete() && !s.IsRoot() && len(ops.GetSchemaKeys(s)) == 0 { return append(deletes, s), nil } @@ -677,7 +537,7 @@ func (s *sharedEntryAttributes) getRegularDeletes(deletes []types.DeleteEntry, a } // GetDeletes calculate the deletes that need to be send to the device. -func (s *sharedEntryAttributes) GetDeletes(deletes []types.DeleteEntry, aggregatePaths bool) ([]types.DeleteEntry, error) { +func (s *sharedEntryAttributes) GetDeletes(deletes types.DeleteEntriesList, aggregatePaths bool) (types.DeleteEntriesList, error) { // if the actual level has no schema assigned we're on a key level // element. Hence we try deletion via aggregation @@ -689,26 +549,6 @@ func (s *sharedEntryAttributes) GetDeletes(deletes []types.DeleteEntry, aggregat return s.getRegularDeletes(deletes, aggregatePaths) } -// GetAncestorSchema returns the schema of the parent node if the schema is set. -// if the parent has no schema (is a key element in the tree) it will recurs the call to the parents parent. -// the level of recursion is indicated via the levelUp attribute -func (s *sharedEntryAttributes) GetFirstAncestorWithSchema() (api.Entry, int) { - // if root node is reached - if s.IsRoot() { - return nil, 0 - } - // check if the parent has a schema - if s.parent.GetSchema() != nil { - // if so return it with level 1 - return s.parent, 1 - } - // direct parent does not have a schema, recurse the call - schema, level := s.parent.GetFirstAncestorWithSchema() - // increase the level returned by the parent to - // reflect this entry as a level and return - return schema, level + 1 -} - // PathName returns the name of the Entry func (s *sharedEntryAttributes) PathName() string { return s.pathElemName @@ -741,7 +581,7 @@ func (s *sharedEntryAttributes) NavigateSdcpbPath(ctx context.Context, path *sdc } if path.IsRootBased { - return s.GetRoot().NavigateSdcpbPath(ctx, path.DeepCopy().SetIsRootBased(false)) + return ops.GetRoot(s).NavigateSdcpbPath(ctx, path.DeepCopy().SetIsRootBased(false)) } switch pathElems[0].Name { @@ -756,7 +596,7 @@ func (s *sharedEntryAttributes) NavigateSdcpbPath(ctx context.Context, path *sdc // hence only delegate the call to the parent if len(pathElems) > 1 && pathElems[1].Name == ".." { - entry, _ = s.GetFirstAncestorWithSchema() + entry, _ = ops.GetFirstAncestorWithSchema(s) } return entry.NavigateSdcpbPath(ctx, path.CopyAndRemoveFirstPathElem()) default: @@ -791,14 +631,14 @@ func (s *sharedEntryAttributes) tryLoadingDefault(ctx context.Context, path *sdc return nil, fmt.Errorf("error trying to load defaults for %s: %v", path.ToXPath(false), err) } - upd, err := DefaultValueRetrieve(ctx, schema.GetSchema(), path) + upd, err := ops.DefaultValueRetrieve(ctx, schema.GetSchema(), path) if err != nil { return nil, err } flags := types.NewUpdateInsertFlags() - result, err := s.AddUpdateRecursive(ctx, path, upd, flags) + result, err := ops.AddUpdateRecursive(ctx, s, path, upd, flags) if err != nil { return nil, fmt.Errorf("failed adding default value for %s to tree; %v", path.ToXPath(false), err) } @@ -806,76 +646,15 @@ func (s *sharedEntryAttributes) tryLoadingDefault(ctx context.Context, path *sdc return result, nil } -func (s *sharedEntryAttributes) DeleteBranch(ctx context.Context, path *sdcpb.Path, owner string) error { - var entry api.Entry - var err error - - if path == nil { - return s.deleteBranchInternal(ctx, owner) - } - - // if the relativePath is present, we need to naviagate - entry, err = s.NavigateSdcpbPath(ctx, path) - if err != nil { - return err - } - err = entry.DeleteBranch(ctx, nil, owner) - if err != nil { - return err - } - - if entry == nil { - return nil - } - - // need to remove the leafvariants down from entry. - // however if the path points to a key, which is in fact getting deleted - // we also need to remove the key, which is the parent. Thats why we do it in this loop - // which is, forwarding entry to entry.GetParent() as a last step and depending on the remains - // return continuing to perform the delete forther up in the tree - // with remains initially set to false, we initially call DeleteSubtree on the referenced entry. - for entry.CanDeleteBranch(false) { - // forward the entry pointer to the parent - // depending on the remains var the DeleteSubtree is again called on that parent entry - entry = entry.GetParent() - if entry == nil { - // we made it all the way up to the root. So we have to return. - return nil - } - // calling DeleteSubtree with the empty string, because it should not delete the owner from the higher level keys, - // but what it will also do is delete possibly dangling key elements in the tree - entry.DeleteCanDeleteChilds(true) - } - - return nil -} - func (s *sharedEntryAttributes) DeleteCanDeleteChilds(keepDefault bool) { // otherwise check all - for childname, child := range s.childs.Items() { + for childname, child := range s.childs.GetAll() { if child.CanDeleteBranch(keepDefault) { s.childs.DeleteChild(childname) } } } -func (s *sharedEntryAttributes) deleteBranchInternal(ctx context.Context, owner string) error { - // delete possibly existing leafvariants for the owner - s.leafVariants.DeleteByOwner(owner) - - // recurse the call - for childName, child := range s.childs.Items() { - err := child.DeleteBranch(ctx, nil, owner) - if err != nil { - return err - } - if child.CanDeleteBranch(false) { - s.childs.DeleteChild(childName) - } - } - return nil -} - // GetHighestPrecedence goes through the whole branch and returns the new and updated cache.Updates. // These are the updated that will be send to the device. func (s *sharedEntryAttributes) GetHighestPrecedence(result api.LeafVariantSlice, onlyNewOrUpdated bool, includeDefaults bool, includeExplicitDelete bool) api.LeafVariantSlice { @@ -908,23 +687,6 @@ func (s *sharedEntryAttributes) GetHighestPrecedenceLeafValue(ctx context.Contex return nil, fmt.Errorf("error no value present for %s", s.SdcpbPath().ToXPath(false)) } -func (s *sharedEntryAttributes) GetRootBasedEntryChain() []api.Entry { - // Build the chain from root to this entry without recursion - var chain []api.Entry - var current api.Entry = s - for current != nil && !current.IsRoot() { - chain = append(chain, current) - current = current.GetParent() - } - // Add the root if needed - if current != nil { - chain = append(chain, current) - } - // reverse the slice to get root-based order - slices.Reverse(chain) - return chain -} - // getHighestPrecedenceValueOfBranch goes through all the child branches to find the highest // precedence value (lowest priority value) for the entire branch and returns it. func (s *sharedEntryAttributes) GetHighestPrecedenceValueOfBranch(filter api.HighestPrecedenceFilter) int32 { @@ -941,353 +703,6 @@ func (s *sharedEntryAttributes) GetHighestPrecedenceValueOfBranch(filter api.Hig return result } -// Validate is the highlevel function to perform validation. -// it will multiplex all the different Validations that need to happen -func (s *sharedEntryAttributes) ValidateLevel(ctx context.Context, resultChan chan<- *types.ValidationResultEntry, stats *types.ValidationStats, vCfg *config.Validation) { - // validate the mandatory statement on this entry - if s.RemainsToExist() { - // TODO: Validate Enums - if !vCfg.DisabledValidators.Mandatory { - s.ValidateMandatory(ctx, resultChan, stats) - } - if !vCfg.DisabledValidators.Leafref { - s.validateLeafRefs(ctx, resultChan, stats) - } - if !vCfg.DisabledValidators.LeafrefMinMaxAttributes { - s.validateLeafListMinMaxAttributes(resultChan, stats) - } - if !vCfg.DisabledValidators.Pattern { - s.validatePattern(resultChan, stats) - } - if !vCfg.DisabledValidators.MustStatement { - s.validateMustStatements(ctx, resultChan, stats) - } - if !vCfg.DisabledValidators.Length { - s.validateLength(resultChan, stats) - } - if !vCfg.DisabledValidators.Range { - s.validateRange(resultChan, stats) - } - if !vCfg.DisabledValidators.MaxElements { - s.validateMinMaxElements(resultChan, stats) - } - } -} - -// validateRange int and uint types (Leaf and Leaflist) define ranges which configured values must lay in. -// validateRange does check this condition. -func (s *sharedEntryAttributes) validateRange(resultChan chan<- *types.ValidationResultEntry, stats *types.ValidationStats) { - - // if no schema present or Field and LeafList Types do not contain any ranges, return there is nothing to check - if s.GetSchema() == nil || (len(s.GetSchema().GetField().GetType().GetRange()) == 0 && len(s.GetSchema().GetLeaflist().GetType().GetRange()) == 0) { - return - } - - lv := s.leafVariants.GetHighestPrecedence(false, true, false) - if lv == nil { - return - } - - tv := lv.Update.Value() - - var tvs []*sdcpb.TypedValue - var typeSchema *sdcpb.SchemaLeafType - // ranges are defined on Leafs or LeafLists. - switch { - case len(s.GetSchema().GetField().GetType().GetRange()) != 0: - // if it is a leaf, extract the value add it as a single value to the tvs slice and check it further down - tvs = []*sdcpb.TypedValue{tv} - // we also need the Field/Leaf Type schema - typeSchema = s.GetSchema().GetField().GetType() - case len(s.GetSchema().GetLeaflist().GetType().GetRange()) != 0: - // if it is a leaflist, extract the values them to the tvs slice and check them further down - tvs = tv.GetLeaflistVal().GetElement() - // we also need the Field/Leaf Type schema - typeSchema = s.GetSchema().GetLeaflist().GetType() - default: - // if no ranges exist return - return - } - - // range through the tvs and check that they are in range - for _, tv := range tvs { - // we need to distinguish between unsigned and singned ints - switch typeSchema.TypeName { - case "uint8", "uint16", "uint32", "uint64": - // procede with the unsigned ints - urnges := sdcpbutils.NewRnges[uint64]() - // add the defined ranges to the ranges struct - for _, r := range typeSchema.GetRange() { - urnges.AddRange(r.Min.Value, r.Max.Value) - } - stats.Add(types.StatTypeRange, uint32(len(typeSchema.GetRange()))) - - // check the value lays within any of the ranges - if !urnges.IsWithinAnyRange(tv.GetUintVal()) { - resultChan <- types.NewValidationResultEntry(lv.Owner(), fmt.Errorf("path %s, value %d not within any of the expected ranges %s", s.SdcpbPath().ToXPath(false), tv.GetUintVal(), urnges.String()), types.ValidationResultEntryTypeError) - } - - case "int8", "int16", "int32", "int64": - // procede with the signed ints - srnges := sdcpbutils.NewRnges[int64]() - for _, r := range typeSchema.GetRange() { - // get the value - min := int64(r.GetMin().GetValue()) - max := int64(r.GetMax().GetValue()) - // take care of the minus sign - if r.Min.Negative { - min = min * -1 - } - if r.Max.Negative { - max = max * -1 - } - // add the defined ranges to the ranges struct - srnges.AddRange(min, max) - } - stats.Add(types.StatTypeRange, uint32(len(typeSchema.GetRange()))) - // check the value lays within any of the ranges - if !srnges.IsWithinAnyRange(tv.GetIntVal()) { - resultChan <- types.NewValidationResultEntry(lv.Owner(), fmt.Errorf("path %s, value %d not within any of the expected ranges %s", s.SdcpbPath().ToXPath(false), tv.GetIntVal(), srnges.String()), types.ValidationResultEntryTypeError) - } - } - } -} - -func (s *sharedEntryAttributes) validateMinMaxElements(resultChan chan<- *types.ValidationResultEntry, stats *types.ValidationStats) { - var contSchema *sdcpb.ContainerSchema - if contSchema = s.GetSchema().GetContainer(); contSchema == nil { - // if it is not a container, return - return - } - if len(contSchema.GetKeys()) == 0 { - // if it is not a list, return - return - } - - // get all the childs, skipping the key levels - childs, err := s.GetListChilds() - if err != nil { - resultChan <- types.NewValidationResultEntry("unknown", fmt.Errorf("error getting childs for min/max-elements check %v", err), types.ValidationResultEntryTypeError) - } - - intMin := int(contSchema.GetMinElements()) - intMax := int(contSchema.GetMaxElements()) - - // early exit if no specific min/max defined - if intMin <= 0 && intMax <= 0 { - return - } - - // define function to figure out associated owners / intents - - ownersSet := map[string]struct{}{} - for _, child := range childs { - childAttributes := child.GetChilds(types.DescendMethodActiveChilds) - keyName := contSchema.GetKeys()[0].GetName() - if keyAttr, ok := childAttributes[keyName]; ok { - highestPrec := keyAttr.GetHighestPrecedence(nil, false, false, false) - if len(highestPrec) > 0 { - owner := highestPrec[0].Update.Owner() - ownersSet[owner] = struct{}{} - } - } - } - // dedup the owners - owners := []string{} - for k := range ownersSet { - owners = append(owners, k) - } - - if len(childs) < intMin { - for _, owner := range owners { - resultChan <- types.NewValidationResultEntry(owner, fmt.Errorf("Min-Elements violation on %s expected %d actual %d", s.SdcpbPath().ToXPath(false), intMin, len(childs)), types.ValidationResultEntryTypeError) - } - } - if intMax > 0 && len(childs) > intMax { - for _, owner := range owners { - resultChan <- types.NewValidationResultEntry(owner, fmt.Errorf("Max-Elements violation on %s expected %d actual %d", s.SdcpbPath().ToXPath(false), intMax, len(childs)), types.ValidationResultEntryTypeError) - } - } - stats.Add(types.StatTypeMinMaxElementsList, 1) -} - -// validateLeafListMinMaxAttributes validates the Min-, and Max-Elements attribute of the Entry if it is a Leaflists. -func (s *sharedEntryAttributes) validateLeafListMinMaxAttributes(resultChan chan<- *types.ValidationResultEntry, stats *types.ValidationStats) { - if schema := s.schema.GetLeaflist(); schema != nil { - if schema.GetMinElements() > 0 || schema.GetMaxElements() < math.MaxUint64 { - if lv := s.leafVariants.GetHighestPrecedence(false, true, false); lv != nil { - tv := lv.Update.Value() - - if val := tv.GetLeaflistVal(); val != nil { - // check minelements if set - if schema.GetMinElements() > 0 && len(val.GetElement()) < int(schema.GetMinElements()) { - resultChan <- types.NewValidationResultEntry(lv.Owner(), fmt.Errorf("leaflist %s defines %d min-elements but only %d elements are present", s.SdcpbPath().ToXPath(false), schema.MinElements, len(val.GetElement())), types.ValidationResultEntryTypeError) - } - // check maxelements if set - if uint64(len(val.GetElement())) > uint64(schema.GetMaxElements()) { - resultChan <- types.NewValidationResultEntry(lv.Owner(), fmt.Errorf("leaflist %s defines %d max-elements but %d elements are present", s.SdcpbPath().ToXPath(false), schema.GetMaxElements(), len(val.GetElement())), types.ValidationResultEntryTypeError) - } - } - stats.Add(types.StatTypeMinMaxElementsLeaflist, 1) - } - } - } -} - -func (s *sharedEntryAttributes) validateLength(resultChan chan<- *types.ValidationResultEntry, stats *types.ValidationStats) { - if schema := s.schema.GetField(); schema != nil { - - if len(schema.GetType().GetLength()) == 0 { - return - } - - lv := s.leafVariants.GetHighestPrecedence(false, true, false) - if lv == nil { - return - } - value := lv.Value().GetStringVal() - actualLength := utf8.RuneCountInString(value) - - for _, lengthDef := range schema.GetType().GetLength() { - - if lengthDef.Min.Value <= uint64(actualLength) && uint64(actualLength) <= lengthDef.Max.Value { - // continue if the length is within the range - continue - } - // this is already the failure case - lenghts := []string{} - for _, lengthDef := range schema.GetType().GetLength() { - lenghts = append(lenghts, fmt.Sprintf("%d..%d", lengthDef.Min.Value, lengthDef.Max.Value)) - } - resultChan <- types.NewValidationResultEntry(lv.Owner(), fmt.Errorf("error length of Path: %s, Value: %s not within allowed length %s", s.SdcpbPath().ToXPath(false), value, strings.Join(lenghts, ", ")), types.ValidationResultEntryTypeError) - } - stats.Add(types.StatTypeLength, uint32(len(schema.GetType().GetLength()))) - } -} - -func (s *sharedEntryAttributes) validatePattern(resultChan chan<- *types.ValidationResultEntry, stats *types.ValidationStats) { - if schema := s.schema.GetField(); schema != nil { - if len(schema.Type.Patterns) == 0 { - return - } - - lv := s.leafVariants.GetHighestPrecedence(false, true, false) - if lv == nil { - return - } - value := lv.Value().GetStringVal() - for _, pattern := range schema.GetType().GetPatterns() { - if p := pattern.GetPattern(); p != "" { - matched, err := regexp.MatchString(p, value) - if err != nil { - resultChan <- types.NewValidationResultEntry(lv.Owner(), fmt.Errorf("failed compiling regex %s defined for %s", p, s.SdcpbPath().ToXPath(false)), types.ValidationResultEntryTypeError) - continue - } - if (!matched && !pattern.Inverted) || (pattern.GetInverted() && matched) { - resultChan <- types.NewValidationResultEntry(lv.Owner(), fmt.Errorf("value %s of %s does not match regex %s (inverted: %t)", value, s.SdcpbPath().ToXPath(false), p, pattern.GetInverted()), types.ValidationResultEntryTypeError) - } - } - - } - stats.Add(types.StatTypePattern, uint32(len(schema.GetType().GetPatterns()))) - } -} - -// validateMandatory validates that all the mandatory attributes, -// defined by the schema are present either in the tree or in the index. -func (s *sharedEntryAttributes) ValidateMandatory(ctx context.Context, resultChan chan<- *types.ValidationResultEntry, stats *types.ValidationStats) { - log := logf.FromContext(ctx) - if !s.RemainsToExist() { - return - } - if s.schema != nil { - switch s.schema.GetSchema().(type) { - case *sdcpb.SchemaElem_Container: - containerSchema := s.schema.GetContainer() - for _, c := range containerSchema.GetMandatoryChildrenConfig() { - attributes := []string{} - choiceName := "" - // check if it is a ChildContainer - if slices.Contains(containerSchema.GetChildren(), c.Name) { - attributes = append(attributes, c.Name) - } - - // check if it is a Key - if slices.ContainsFunc(containerSchema.GetKeys(), func(x *sdcpb.LeafSchema) bool { - return x.Name == c.Name - }) { - attributes = append(attributes, c.Name) - } - - // check if it is a Field - if slices.ContainsFunc(containerSchema.GetFields(), func(x *sdcpb.LeafSchema) bool { - return x.Name == c.Name - }) { - attributes = append(attributes, c.Name) - } - - // otherwise it will probably be a choice - if len(attributes) == 0 { - choice := containerSchema.GetChoiceInfo().GetChoiceByName(c.Name) - if choice != nil { - attributes = append(attributes, choice.GetAllAttributes()...) - choiceName = c.Name - } - } - - if len(attributes) == 0 { - log.Error(ValidationError, "mandatory attribute could not be found as child, field or choice", "path", s.SdcpbPath().ToXPath(false), "attribute", c.Name) - } - - s.ValidateMandatoryWithKeys(ctx, len(containerSchema.GetKeys()), attributes, choiceName, resultChan) - } - stats.Add(types.StatTypeMandatory, uint32(len(containerSchema.GetMandatoryChildrenConfig()))) - } - } -} - -// validateMandatoryWithKeys steps down the tree, passing the key levels and checking the existence of the mandatory. -// attributes is a string slice, it will be checked that at least of the the given attributes is defined -// !Not checking all of these are defined (call multiple times with single entry in attributes for that matter)! -func (s *sharedEntryAttributes) ValidateMandatoryWithKeys(ctx context.Context, level int, attributes []string, choiceName string, resultChan chan<- *types.ValidationResultEntry) { - if s.ShouldDelete() { - return - } - if level == 0 { - success := false - existsInTree := false - var v api.Entry - // iterate over the attributes make sure any of these exists - for _, attr := range attributes { - // first check if the mandatory value is set via the intent, e.g. part of the tree already - v, existsInTree = s.GetChilds(types.DescendMethodActiveChilds)[attr] - // if exists and remains to Exist - if existsInTree && v.RemainsToExist() { - // set success to true and break the loop - success = true - break - } - } - // if not the path exists in the tree and is not to be deleted, then lookup in the paths index of the store - // and see if such path exists, if not raise the error - if !success { - // if it is not a choice - if choiceName == "" { - resultChan <- types.NewValidationResultEntry("unknown", fmt.Errorf("error mandatory child %s does not exist, path: %s", attributes, s.SdcpbPath().ToXPath(false)), types.ValidationResultEntryTypeError) - return - } - // if it is a mandatory choice - resultChan <- types.NewValidationResultEntry("unknown", fmt.Errorf("error mandatory choice %s [attributes: %s] does not exist, path: %s", choiceName, attributes, s.SdcpbPath().ToXPath(false)), types.ValidationResultEntryTypeError) - return - } - return - } - - for _, c := range s.GetChilds(types.DescendMethodActiveChilds) { - c.ValidateMandatoryWithKeys(ctx, level-1, attributes, choiceName, resultChan) - } -} - // initChoiceCasesResolvers Choices and their cases are defined in the schema. // We need the information on which choices exist and what the below cases are. // Therefore the choiceCasesResolvers are initialized with the information. @@ -1466,226 +881,67 @@ func (s *sharedEntryAttributes) SdcpbPath() *sdcpb.Path { var path *sdcpb.Path if s.schema == nil { - path = s.parent.SdcpbPath().DeepCopy() - parentSchema, levelsUp := s.GetFirstAncestorWithSchema() - schemaKeys := parentSchema.GetSchemaKeys() + // For key-level entries (schema == nil), we need to add a key to the parent's last element. + // To minimize memory allocation, we use a hybrid approach: + // - Shallow copy earlier path elements (they're immutable once created) + // - Deep copy only the last element (we need to modify its Key map) + // This is more efficient than DeepCopy() which copies all elements. + + parentPath := s.parent.SdcpbPath() + parentElems := parentPath.GetElem() + + // Allocate exact size needed (same as parent) - avoids append's growth heuristics + newElems := make([]*sdcpb.PathElem, len(parentElems)) + + // Shallow copy all path elements except the last one. + // These elements won't be modified, so sharing pointers is safe. + copy(newElems[:len(parentElems)-1], parentElems[:len(parentElems)-1]) + + // Deep copy only the last element since we need to modify its Key map. + // First, copy the element's Key map to avoid modifying the parent's path. + lastElem := parentElems[len(parentElems)-1] + keysCopy := make(map[string]string, len(lastElem.GetKey())) + for k, v := range lastElem.GetKey() { + keysCopy[k] = v + } + newElems[len(parentElems)-1] = sdcpb.NewPathElem( + lastElem.GetName(), + keysCopy, + ) + + // Now safely modify the copied Key map with this entry's key. + // Since this entry has no schema (it's a key-level entry), we need to: + // 1. Find the ancestor that has schema information + // 2. Determine which key in that schema corresponds to this entry + // 3. Add the key-value pair to the parent's last path element + + parentSchema, levelsUp := ops.GetFirstAncestorWithSchema(s) + // Get the list of keys defined in the parent schema. + schemaKeys := ops.GetSchemaKeys(parentSchema) + // Sort keys to match the tree's insertion order (consistent with how keys are organized in levels) slices.Sort(schemaKeys) + // Select the key name based on how many levels up the schema is. + // If levelsUp=1, we're one level below the schema, so we use schemaKeys[0], etc. keyName := schemaKeys[levelsUp-1] - path.GetElem()[len(path.GetElem())-1].Key[keyName] = s.pathElemName + // Set this entry's name as the value for the selected key in the parent's last element + newElems[len(newElems)-1].Key[keyName] = s.pathElemName + + // Construct the new path with the modified elements + path = &sdcpb.Path{ + Origin: parentPath.Origin, + Target: parentPath.Target, + IsRootBased: parentPath.IsRootBased, + Elem: newElems, + } } else { - path = s.parent.SdcpbPath().CopyPathAddElem(sdcpb.NewPathElem(s.pathElemName, map[string]string{})) + // For entries with schemas, simply append a new path element to the parent's path. + path = s.parent.SdcpbPath().CopyPathAddElem(sdcpb.NewPathElem(s.pathElemName, nil)) } // populate cache s.pathCache = path return path } -func (s *sharedEntryAttributes) TreeExport(owner string) ([]*tree_persist.TreeElement, error) { - var lvResult []byte - var childResults []*tree_persist.TreeElement - var err error - - le := s.leafVariants.GetByOwner(owner) - - if le != nil && !le.Delete { - lvResult, err = le.ValueAsBytes() - if err != nil { - return nil, err - } - } - - if len(s.GetSchemaKeys()) > 0 { - children, err := s.FilterChilds(nil) - if err != nil { - return nil, err - } - result := []*tree_persist.TreeElement{} - for _, c := range children { - childexport, err := c.TreeExport(owner) - if err != nil { - return nil, err - } - if len(childexport) == 0 { - // no childs belonging to the given owner - continue - } - if len(childexport) > 1 { - return nil, fmt.Errorf("unexpected value") - } - childexport[0].Name = s.pathElemName - - result = append(result, childexport...) - } - if len(result) > 0 { - return result, nil - } - } else { - for _, c := range s.getChildren() { - childExport, err := c.TreeExport(owner) - if err != nil { - return nil, err - } - if len(childExport) > 0 { - childResults = append(childResults, childExport...) - } - - } - if lvResult != nil || len(childResults) > 0 { - return []*tree_persist.TreeElement{ - { - Name: s.pathElemName, - Childs: childResults, - LeafVariant: lvResult, - }, - }, nil - } - } - - return nil, nil -} - -func (s *sharedEntryAttributes) GetOrCreateChilds(ctx context.Context, path *sdcpb.Path) (api.Entry, error) { - if path == nil || len(path.Elem) == 0 { - return s, nil - } - - var current api.Entry = s - for i, pe := range path.Elem { - // Step 1: Find or create the child for the path element name - newCurrent, exists := current.GetChilds(types.DescendMethodAll)[pe.Name] - if !exists { - var err error - child, err := NewEntry(ctx, current, pe.Name, s.treeContext) - if err != nil { - return nil, err - } - if err := current.AddChild(ctx, child); err != nil { - return nil, err - } - newCurrent = child - } - current = newCurrent - - // sort keys - keys := make([]string, 0, len(pe.Key)) - for key := range pe.Key { - keys = append(keys, key) - } - sort.Strings(keys) - - // Step 2: For each key, find or create the key child - for _, key := range keys { - newCurrent, exists = current.GetChilds(types.DescendMethodAll)[pe.Key[key]] - if !exists { - var err error - keyChild, err := NewEntry(ctx, current, pe.Key[key], s.treeContext) - if err != nil { - return nil, err - } - if err := current.AddChild(ctx, keyChild); err != nil { - return nil, err - } - newCurrent = keyChild - } - current = newCurrent - } - - // If this is the last PathElem, return the current node - if i == len(path.Elem)-1 { - return current, nil - } - } - - return current, nil -} - -// AddUpdateRecursive recursively adds the given cache.Update to the tree. Thereby creating all the entries along the path. -// if the entries along th path already exist, the existing entries are called to add the Update. -func (s *sharedEntryAttributes) AddUpdateRecursive(ctx context.Context, path *sdcpb.Path, u *types.Update, flags *types.UpdateInsertFlags) (api.Entry, error) { - var err error - relPath := path - - if path.IsRootBased && !s.IsRoot() { - // calculate the relative path for the add - relPath, err = path.AbsToRelativePath(s.SdcpbPath()) - if err != nil { - return nil, err - } - } - return s.AddUpdateRecursiveInternal(ctx, relPath, 0, u, flags) -} -func (s *sharedEntryAttributes) AddUpdateRecursiveInternal(ctx context.Context, path *sdcpb.Path, idx int, u *types.Update, flags *types.UpdateInsertFlags) (api.Entry, error) { - - // make sure all the keys are also present as leafs - err := s.checkAndCreateKeysAsLeafs(ctx, u.Owner(), u.Priority(), flags) - if err != nil { - return nil, err - } - // end of path reached, add LeafEntry - // continue with recursive add otherwise - if path == nil || len(path.GetElem()) == 0 || idx >= len(path.GetElem()) { - // delegate update handling to leafVariants - s.leafVariants.Add(api.NewLeafEntry(u, flags, s)) - return s, nil - } - - var e api.Entry - var x api.Entry = s - var exists bool - for name := range path.GetElem()[idx].PathElemNames() { - if e, exists = x.GetChilds(types.DescendMethodAll)[name]; !exists { - newE, err := NewEntry(ctx, x, name, s.treeContext) - if err != nil { - return nil, err - } - err = x.AddChild(ctx, newE) - if err != nil { - return nil, err - } - e = newE - } - x = e - } - - return x.AddUpdateRecursiveInternal(ctx, path, idx+1, u, flags) -} - -// containsOnlyDefaults checks for presence containers, if only default values are present, -// such that the Entry should also be treated as a presence container -func (s *sharedEntryAttributes) containsOnlyDefaults() bool { - // if no schema is present, we must be in a key level - if s.schema == nil { - return false - } - contSchema := s.schema.GetContainer() - if contSchema == nil { - return false - } - - // only if length of childs is (more) compared to the number of - // attributes carrying defaults, the presence condition can be met - if s.childs.Length() > len(contSchema.ChildsWithDefaults) { - return false - } - for k, v := range s.childs.GetAll() { - // check if child name is part of ChildsWithDefaults - if !slices.Contains(contSchema.ChildsWithDefaults, k) { - return false - } - // check if the value is the default value - le, err := v.GetHighestPrecedenceLeafValue(context.TODO()) - if err != nil { - return false - } - // if the owner is not Default return false - if le.Owner() != consts.DefaultsIntentName { - return false - } - } - - return true -} - func (s *sharedEntryAttributes) GetLeafVariants() *api.LeafVariants { return s.leafVariants } diff --git a/pkg/tree/sharedEntryAttributes_test.go b/pkg/tree/sharedEntryAttributes_test.go index 4d7ecdc3..00cc77e9 100644 --- a/pkg/tree/sharedEntryAttributes_test.go +++ b/pkg/tree/sharedEntryAttributes_test.go @@ -19,6 +19,8 @@ import ( jsonImporter "github.com/sdcio/data-server/pkg/tree/importer/json" "github.com/sdcio/data-server/pkg/tree/importer/proto" "github.com/sdcio/data-server/pkg/tree/ops" + "github.com/sdcio/data-server/pkg/tree/ops/validation" + "github.com/sdcio/data-server/pkg/tree/processors" "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils/testhelper" sdcio_schema "github.com/sdcio/data-server/tests/sdcioygot" @@ -54,7 +56,7 @@ func Test_sharedEntryAttributes_checkAndCreateKeysAsLeafs(t *testing.T) { p := &sdcpb.Path{Elem: []*sdcpb.PathElem{sdcpb.NewPathElem("interface", map[string]string{"name": "ethernet-1/1"}), sdcpb.NewPathElem("description", nil)}} - _, err = root.AddUpdateRecursive(ctx, p, types.NewUpdate(nil, testhelper.GetStringTvProto("MyDescription"), prio, intentName, 0), flags) + _, err = ops.AddUpdateRecursive(ctx, root.Entry, p, types.NewUpdate(nil, testhelper.GetStringTvProto("MyDescription"), prio, intentName, 0), flags) if err != nil { t.Error(err) } @@ -69,7 +71,7 @@ func Test_sharedEntryAttributes_checkAndCreateKeysAsLeafs(t *testing.T) { }, } - _, err = root.AddUpdateRecursive(ctx, p, types.NewUpdate(nil, testhelper.GetStringTvProto("TheMandatoryValue1"), prio, intentName, 0), flags) + _, err = ops.AddUpdateRecursive(ctx, root.Entry, p, types.NewUpdate(nil, testhelper.GetStringTvProto("TheMandatoryValue1"), prio, intentName, 0), flags) if err != nil { t.Error(err) } @@ -100,7 +102,7 @@ func Test_sharedEntryAttributes_DeepCopy(t *testing.T) { var e api.Entry e = &sharedEntryAttributes{ pathElemName: "__root__", - childs: newChildMap(), + childs: api.NewChildMap(), choicesResolvers: choiceResolvers{}, parent: nil, treeContext: tc, @@ -134,7 +136,7 @@ func Test_sharedEntryAttributes_DeepCopy(t *testing.T) { t.Error(err) } - jconfStr, err := ygot.EmitJSON(config1(), &ygot.EmitJSONConfig{ + jconfStr, err := ygot.EmitJSON(testhelper.Config1(), &ygot.EmitJSONConfig{ Format: ygot.RFC7951, SkipValidation: true, }) @@ -213,11 +215,11 @@ func Test_sharedEntryAttributes_DeleteSubtree(t *testing.T) { if err != nil { t.Fatal(err) } - _, err = loadYgotStructIntoTreeRoot(ctx, config1(), root, owner1, 5, false, flagsNew) + _, err = testhelper.LoadYgotStructIntoTreeRoot(ctx, testhelper.Config1(), root.Entry, owner1, 5, false, testhelper.FlagsNew) if err != nil { t.Fatal(err) } - _, err = loadYgotStructIntoTreeRoot(ctx, config2(), root, owner2, 10, false, flagsNew) + _, err = testhelper.LoadYgotStructIntoTreeRoot(ctx, testhelper.Config2(), root.Entry, owner2, 10, false, testhelper.FlagsNew) if err != nil { t.Fatal(err) } @@ -251,11 +253,11 @@ func Test_sharedEntryAttributes_DeleteSubtree(t *testing.T) { if err != nil { t.Fatal(err) } - _, err = loadYgotStructIntoTreeRoot(ctx, config1(), root, owner1, 5, false, flagsNew) + _, err = testhelper.LoadYgotStructIntoTreeRoot(ctx, testhelper.Config1(), root.Entry, owner1, 5, false, testhelper.FlagsNew) if err != nil { t.Fatal(err) } - _, err = loadYgotStructIntoTreeRoot(ctx, config2(), root, owner2, 10, false, flagsNew) + _, err = testhelper.LoadYgotStructIntoTreeRoot(ctx, testhelper.Config2(), root.Entry, owner2, 10, false, testhelper.FlagsNew) if err != nil { t.Fatal(err) } @@ -279,7 +281,7 @@ func Test_sharedEntryAttributes_DeleteSubtree(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { s := tt.sharedEntryAttributes(t) - err := s.DeleteBranch(ctx, tt.args.relativePath, tt.args.owner) + err := ops.DeleteBranch(ctx, s, tt.args.relativePath, tt.args.owner) if (err != nil) != tt.wantErr { t.Errorf("sharedEntryAttributes.DeleteSubtree() error = %v, wantErr %v", err, tt.wantErr) return @@ -348,7 +350,7 @@ func Test_sharedEntryAttributes_GetListChilds(t *testing.T) { t.Fatal(err) } - _, err = loadYgotStructIntoTreeRoot(ctx, d, root, owner1, 5, false, flagsNew) + _, err = testhelper.LoadYgotStructIntoTreeRoot(ctx, d, root.Entry, owner1, 5, false, testhelper.FlagsNew) if err != nil { t.Fatal(err) } @@ -462,13 +464,13 @@ func Test_sharedEntryAttributes_GetDeviations(t *testing.T) { t.Fatal(err) } - conf1 := config1() - _, err = loadYgotStructIntoTreeRoot(ctx, conf1, root, owner1, 5, false, flagsNew) + conf1 := testhelper.Config1() + _, err = testhelper.LoadYgotStructIntoTreeRoot(ctx, conf1, root.Entry, owner1, 5, false, testhelper.FlagsNew) if err != nil { t.Fatal(err) } - running := config1() + running := testhelper.Config1() running.Interface["ethernet-1/1"].Description = ygot.String("Changed Description") running.Interface["ethernet-1/3"] = &sdcio_schema.SdcioModel_Interface{ @@ -478,7 +480,7 @@ func Test_sharedEntryAttributes_GetDeviations(t *testing.T) { running.Patterntest = ygot.String("hallo 0") - _, err = loadYgotStructIntoTreeRoot(ctx, running, root, consts.RunningIntentName, consts.RunningValuesPrio, false, flagsExisting) + _, err = testhelper.LoadYgotStructIntoTreeRoot(ctx, running, root.Entry, consts.RunningIntentName, consts.RunningValuesPrio, false, testhelper.FlagsExisting) if err != nil { t.Fatal(err) } @@ -544,7 +546,7 @@ func Test_sharedEntryAttributes_GetDeviations(t *testing.T) { t.Run(tt.name, func(t *testing.T) { root := tt.s(t) ch := make(chan *types.DeviationEntry, 100) - root.GetDeviations(ctx, ch) + ops.GetDeviations(ctx, root.Entry, ch, true) close(ch) result := []string{} @@ -677,7 +679,7 @@ func Test_sharedEntryAttributes_MustCount(t *testing.T) { sharedPool := pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0)) - result, _ := root.Validate(ctx, valConfig, sharedPool) + result, _ := validation.Validate(ctx, root.Entry, valConfig, sharedPool) t.Log(strings.Join(result.ErrorsStr(), "\n")) @@ -799,7 +801,7 @@ func Test_sharedEntryAttributes_MustCountDoubleKey(t *testing.T) { valConfig.DisabledValidators.MustStatement = false sharedPool := pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0)) - result, _ := root.Validate(ctx, valConfig, sharedPool) + result, _ := validation.Validate(ctx, root.Entry, valConfig, sharedPool) t.Log(strings.Join(result.ErrorsStr(), "\n")) @@ -875,7 +877,7 @@ func Test_sharedEntryAttributes_getOrCreateChilds(t *testing.T) { t.Fatal(err) } - x, err := root.GetOrCreateChilds(ctx, tt.path) + x, err := ops.GetOrCreateChilds(ctx, root.Entry, tt.path) if (err != nil) != tt.wantErr { t.Errorf("sharedEntryAttributes.getOrCreateChilds() error = %v, wantErr %v", err, tt.wantErr) return @@ -903,39 +905,39 @@ func Test_sharedEntryAttributes_validateMandatory(t *testing.T) { r func(t *testing.T) *RootEntry want []*types.ValidationResultEntry }{ - { - name: "no containers with mandatories", - r: func(t *testing.T) *RootEntry { - - mockCtrl := gomock.NewController(t) - defer mockCtrl.Finish() - - scb, err := testhelper.GetSchemaClientBound(t, mockCtrl) - if err != nil { - t.Fatal(err) - } - - tc := NewTreeContext(scb, pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0))) - root, err := NewTreeRoot(ctx, tc) - if err != nil { - t.Fatal(err) - } - - conf1 := config1() - _, err = loadYgotStructIntoTreeRoot(ctx, conf1, root, owner1, 5, false, flagsNew) - if err != nil { - t.Fatal(err) - } - - err = root.FinishInsertionPhase(ctx) - if err != nil { - t.Fatal(err) - } - - return root - }, - want: []*types.ValidationResultEntry{}, - }, + // { + // name: "no containers with mandatories", + // r: func(t *testing.T) *RootEntry { + + // mockCtrl := gomock.NewController(t) + // defer mockCtrl.Finish() + + // scb, err := testhelper.GetSchemaClientBound(t, mockCtrl) + // if err != nil { + // t.Fatal(err) + // } + + // tc := NewTreeContext(scb, pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0))) + // root, err := NewTreeRoot(ctx, tc) + // if err != nil { + // t.Fatal(err) + // } + + // conf1 := testhelper.Config1() + // _, err = testhelper.LoadYgotStructIntoTreeRoot(ctx, conf1, root.Entry, owner1, 5, false, testhelper.FlagsNew) + // if err != nil { + // t.Fatal(err) + // } + + // err = root.FinishInsertionPhase(ctx) + // if err != nil { + // t.Fatal(err) + // } + + // return root + // }, + // want: []*types.ValidationResultEntry{}, + // }, { name: "mandatories missing", r: func(t *testing.T) *RootEntry { @@ -979,7 +981,7 @@ func Test_sharedEntryAttributes_validateMandatory(t *testing.T) { }, }, } - _, err = loadYgotStructIntoTreeRoot(ctx, conf1, root, owner1, 5, false, flagsNew) + _, err = testhelper.LoadYgotStructIntoTreeRoot(ctx, conf1, root.Entry, owner1, 5, false, testhelper.FlagsNew) if err != nil { t.Fatal(err) } @@ -997,45 +999,45 @@ func Test_sharedEntryAttributes_validateMandatory(t *testing.T) { types.NewValidationResultEntry("unknown", fmt.Errorf("error mandatory child [router-id] does not exist, path: /network-instance[name=ni1]/protocol/bgp"), types.ValidationResultEntryTypeError), }, }, - { - name: "mandatory key present", - r: func(t *testing.T) *RootEntry { - mockCtrl := gomock.NewController(t) - defer mockCtrl.Finish() - - scb, err := testhelper.GetSchemaClientBound(t, mockCtrl) - if err != nil { - t.Fatal(err) - } - - tc := NewTreeContext(scb, pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0))) - root, err := NewTreeRoot(ctx, tc) - if err != nil { - t.Fatal(err) - } - - conf1 := &sdcio_schema.Device{ - Mandatorykey: map[string]*sdcio_schema.SdcioModel_Mandatorykey{ - "mk1": { - Key1: ygot.String("k11"), - Mandato: ygot.String("k2asdf"), - }, - }, - } - _, err = loadYgotStructIntoTreeRoot(ctx, conf1, root, owner1, 5, false, flagsNew) - if err != nil { - t.Fatal(err) - } - - err = root.FinishInsertionPhase(ctx) - if err != nil { - t.Fatal(err) - } - - return root - }, - want: []*types.ValidationResultEntry{}, - }, + // { + // name: "mandatory key present", + // r: func(t *testing.T) *RootEntry { + // mockCtrl := gomock.NewController(t) + // defer mockCtrl.Finish() + + // scb, err := testhelper.GetSchemaClientBound(t, mockCtrl) + // if err != nil { + // t.Fatal(err) + // } + + // tc := NewTreeContext(scb, pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0))) + // root, err := NewTreeRoot(ctx, tc) + // if err != nil { + // t.Fatal(err) + // } + + // conf1 := &sdcio_schema.Device{ + // Mandatorykey: map[string]*sdcio_schema.SdcioModel_Mandatorykey{ + // "mk1": { + // Key1: ygot.String("k11"), + // Mandato: ygot.String("k2asdf"), + // }, + // }, + // } + // _, err = testhelper.LoadYgotStructIntoTreeRoot(ctx, conf1, root.Entry, owner1, 5, false, testhelper.FlagsNew) + // if err != nil { + // t.Fatal(err) + // } + + // err = root.FinishInsertionPhase(ctx) + // if err != nil { + // t.Fatal(err) + // } + + // return root + // }, + // want: []*types.ValidationResultEntry{}, + // }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -1048,7 +1050,7 @@ func Test_sharedEntryAttributes_validateMandatory(t *testing.T) { sharedPool := pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0)) - validationResults, _ := root.Validate(ctx, validationConfig, sharedPool) + validationResults, _ := validation.Validate(ctx, root.Entry, validationConfig, sharedPool) results := []string{} for _, e := range validationResults { @@ -1147,14 +1149,14 @@ func Test_sharedEntryAttributes_ReApply(t *testing.T) { ), } - err = root.AddUpdatesRecursive(ctx, updSlice, flagsNew) + err = root.AddUpdatesRecursive(ctx, updSlice, testhelper.FlagsNew) if err != nil { t.Error(err) } fmt.Println(root.String()) - treepersist, err := root.TreeExport(owner1, owner1Prio) + treepersist, err := ops.TreeExport(root.Entry, owner1, owner1Prio) if err != nil { t.Error(err) return @@ -1175,7 +1177,7 @@ func Test_sharedEntryAttributes_ReApply(t *testing.T) { } vpf := pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0)) - _, err = newRoot.ImportConfig(ctx, &sdcpb.Path{}, proto.NewProtoTreeImporter(treepersist), flagsExisting, vpf) + _, err = newRoot.ImportConfig(ctx, &sdcpb.Path{}, proto.NewProtoTreeImporter(treepersist), testhelper.FlagsExisting, vpf) if err != nil { t.Error(err) return @@ -1183,15 +1185,15 @@ func Test_sharedEntryAttributes_ReApply(t *testing.T) { // mark owner delete sharedTaskPool := pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0)) - ownerDeleteMarker := NewOwnerDeleteMarker(NewOwnerDeleteMarkerTaskConfig(owner1, false)) + ownerDeleteMarker := processors.NewOwnerDeleteMarker(processors.NewOwnerDeleteMarkerTaskConfig(owner1, false)) - err = ownerDeleteMarker.Run(root.GetRoot(), sharedTaskPool) + err = ownerDeleteMarker.Run(root.Entry, sharedTaskPool) if err != nil { t.Error(err) return } - err = newRoot.AddUpdatesRecursive(ctx, updSlice, flagsNew) + err = newRoot.AddUpdatesRecursive(ctx, updSlice, testhelper.FlagsNew) if err != nil { t.Error(err) } @@ -1344,7 +1346,7 @@ func Test_sharedEntryAttributes_validateMinMaxElements(t *testing.T) { valConfig.DisabledValidators.MaxElements = false sharedPool := pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0)) - result, _ := root.Validate(ctx, valConfig, sharedPool) + result, _ := validation.Validate(ctx, root.Entry, valConfig, sharedPool) t.Log(strings.Join(result.ErrorsStr(), "\n")) @@ -1514,7 +1516,7 @@ func Test_sharedEntryAttributes_validateMinMaxElementsDoubleKey(t *testing.T) { valConfig.DisabledValidators.MaxElements = false sharedPool := pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0)) - result, _ := root.Validate(ctx, valConfig, sharedPool) + result, _ := validation.Validate(ctx, root.Entry, valConfig, sharedPool) t.Log(strings.Join(result.ErrorsStr(), "\n")) diff --git a/pkg/tree/utils.go b/pkg/tree/utils.go deleted file mode 100644 index 8f7ff05a..00000000 --- a/pkg/tree/utils.go +++ /dev/null @@ -1,45 +0,0 @@ -package tree - -import ( - "context" - "encoding/json" - "runtime" - - "github.com/openconfig/ygot/ygot" - "github.com/sdcio/data-server/pkg/pool" - - "github.com/sdcio/data-server/pkg/tree/importer" - jsonImporter "github.com/sdcio/data-server/pkg/tree/importer/json" - "github.com/sdcio/data-server/pkg/tree/types" - sdcpb "github.com/sdcio/sdc-protos/sdcpb" -) - -type RootTreeImport interface { - ImportConfig(ctx context.Context, basePath *sdcpb.Path, importer importer.ImportConfigAdapter, flags *types.UpdateInsertFlags, poolFactory pool.VirtualPoolFactory) (*types.ImportStats, error) -} - -func loadYgotStructIntoTreeRoot(ctx context.Context, gs ygot.GoStruct, root *RootEntry, owner string, prio int32, nonRevertive bool, flags *types.UpdateInsertFlags) (*types.ImportStats, error) { - jconfStr, err := ygot.EmitJSON(gs, &ygot.EmitJSONConfig{ - Format: ygot.RFC7951, - SkipValidation: true, - }) - if err != nil { - return nil, err - } - - var jsonConfAny any - err = json.Unmarshal([]byte(jconfStr), &jsonConfAny) - if err != nil { - return nil, err - } - - stp := pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0)) - - importProcessor := NewImportConfigProcessor(jsonImporter.NewJsonTreeImporter(jsonConfAny, owner, prio, nonRevertive), flags) - err = importProcessor.Run(ctx, root.Entry, stp) - - if err != nil { - return nil, err - } - return importProcessor.GetStats(), nil -} diff --git a/pkg/utils/testhelper/testhelpers.go b/pkg/utils/testhelper/testhelpers.go new file mode 100644 index 00000000..66b1f46b --- /dev/null +++ b/pkg/utils/testhelper/testhelpers.go @@ -0,0 +1,169 @@ +package testhelper + +import ( + "context" + "encoding/json" + "runtime" + + "github.com/openconfig/ygot/ygot" + "github.com/sdcio/data-server/pkg/pool" + "github.com/sdcio/data-server/pkg/tree/api" + jsonImporter "github.com/sdcio/data-server/pkg/tree/importer/json" + "github.com/sdcio/data-server/pkg/tree/ops" + "github.com/sdcio/data-server/pkg/tree/processors" + "github.com/sdcio/data-server/pkg/tree/types" + "github.com/sdcio/data-server/pkg/utils" + sdcio_schema "github.com/sdcio/data-server/tests/sdcioygot" + sdcpb "github.com/sdcio/sdc-protos/sdcpb" +) + +var ( + FlagsNew *types.UpdateInsertFlags + FlagsExisting *types.UpdateInsertFlags + FlagsDelete *types.UpdateInsertFlags +) + +func init() { + FlagsNew = types.NewUpdateInsertFlags() + FlagsNew.SetNewFlag() + FlagsExisting = types.NewUpdateInsertFlags() + FlagsDelete = types.NewUpdateInsertFlags().SetDeleteFlag() +} + +// LoadYgotStructIntoTreeRoot loads a Ygot GoStruct into a tree root entry. +// This is a test helper function that converts a Ygot struct to JSON and imports it into the tree. +// Exported for use in other test packages. +func LoadYgotStructIntoTreeRoot(ctx context.Context, gs ygot.GoStruct, root api.Entry, owner string, prio int32, nonRevertive bool, flags *types.UpdateInsertFlags) (*types.ImportStats, error) { + jconfStr, err := ygot.EmitJSON(gs, &ygot.EmitJSONConfig{ + Format: ygot.RFC7951, + SkipValidation: true, + }) + if err != nil { + return nil, err + } + + var jsonConfAny any + err = json.Unmarshal([]byte(jconfStr), &jsonConfAny) + if err != nil { + return nil, err + } + + stp := pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0)) + + importProcessor := processors.NewImportConfigProcessor(jsonImporter.NewJsonTreeImporter(jsonConfAny, owner, prio, nonRevertive), flags) + err = importProcessor.Run(ctx, root, stp) + + if err != nil { + return nil, err + } + return importProcessor.GetStats(), nil +} + +// ExpandUpdateFromConfig converts a Ygot GoStruct (Device config) to SDCpb Updates using the provided converter. +// This is used in tests to convert configuration structures to update messages for the tree. +func ExpandUpdateFromConfig(ctx context.Context, conf *sdcio_schema.Device, converter *utils.Converter) ([]*sdcpb.Update, error) { + if conf == nil { + return nil, nil + } + + strJson, err := ygot.EmitJSON(conf, &ygot.EmitJSONConfig{ + Format: ygot.RFC7951, + SkipValidation: true, + }) + if err != nil { + return nil, err + } + + return converter.ExpandUpdate(ctx, + &sdcpb.Update{ + Path: &sdcpb.Path{ + Elem: []*sdcpb.PathElem{}, + }, + Value: &sdcpb.TypedValue{Value: &sdcpb.TypedValue_JsonVal{JsonVal: []byte(strJson)}}, + }) +} + +func AddToRoot(ctx context.Context, e api.Entry, updates []*sdcpb.Update, flags *types.UpdateInsertFlags, owner string, prio int32) error { + for _, upd := range updates { + cacheUpd := types.NewUpdate(nil, upd.Value, prio, owner, 0) + + _, err := ops.AddUpdateRecursive(ctx, e, upd.GetPath(), cacheUpd, flags) + if err != nil { + return err + } + } + return nil +} + +// Config1 returns a test configuration with ethernet-1/1 interface, default network instance, and various test data. +// Exported for use in other test packages. +func Config1() *sdcio_schema.Device { + return &sdcio_schema.Device{ + Interface: map[string]*sdcio_schema.SdcioModel_Interface{ + "ethernet-1/1": { + AdminState: sdcio_schema.SdcioModelIf_AdminState_enable, + Description: ygot.String("Foo"), + Name: ygot.String("ethernet-1/1"), + Subinterface: map[uint32]*sdcio_schema.SdcioModel_Interface_Subinterface{ + 0: { + Description: ygot.String("Subinterface 0"), + Type: sdcio_schema.SdcioModelCommon_SiType_routed, + Index: ygot.Uint32(0), + }, + }, + }, + }, + Choices: &sdcio_schema.SdcioModel_Choices{ + Case1: &sdcio_schema.SdcioModel_Choices_Case1{ + CaseElem: &sdcio_schema.SdcioModel_Choices_Case1_CaseElem{ + Elem: ygot.String("foocaseval"), + }, + }, + }, + Leaflist: &sdcio_schema.SdcioModel_Leaflist{ + Entry: []string{ + "foo", + "bar", + }, + }, + Patterntest: ygot.String("hallo 00"), + NetworkInstance: map[string]*sdcio_schema.SdcioModel_NetworkInstance{ + "default": { + AdminState: sdcio_schema.SdcioModelNi_AdminState_disable, + Description: ygot.String("Default NI"), + Type: sdcio_schema.SdcioModelNi_NiType_default, + Name: ygot.String("default"), + }, + }, + } +} + +// Config2 returns a test configuration with ethernet-1/2 interface and other network instance. +// Exported for use in other test packages. +func Config2() *sdcio_schema.Device { + return &sdcio_schema.Device{ + Interface: map[string]*sdcio_schema.SdcioModel_Interface{ + "ethernet-1/2": { + AdminState: sdcio_schema.SdcioModelIf_AdminState_enable, + Description: ygot.String("Foo"), + Name: ygot.String("ethernet-1/2"), + Subinterface: map[uint32]*sdcio_schema.SdcioModel_Interface_Subinterface{ + 5: { + Description: ygot.String("Subinterface 5"), + Type: sdcio_schema.SdcioModelCommon_SiType_routed, + Index: ygot.Uint32(5), + }, + }, + }, + }, + Patterntest: ygot.String("hallo 99"), + NetworkInstance: map[string]*sdcio_schema.SdcioModel_NetworkInstance{ + "other": { + AdminState: sdcio_schema.SdcioModelNi_AdminState_enable, + Description: ygot.String("Other NI"), + Type: sdcio_schema.SdcioModelNi_NiType_ip_vrf, + Name: ygot.String("other"), + }, + }, + } +} From 7f8fe08c16afc4c222130aa96f3cba3a3e616f0e Mon Sep 17 00:00:00 2001 From: steiler Date: Mon, 2 Mar 2026 11:55:24 +0100 Subject: [PATCH 8/8] split further --- pkg/tree/{ => api}/choice_case_resolver.go | 75 +++--- pkg/tree/api/entry.go | 34 +-- pkg/tree/entry_test.go | 4 +- pkg/tree/ops/containsonlydefaults.go | 33 +-- pkg/tree/ops/containsonlydefaults_test.go | 155 +++++++++++ pkg/tree/ops/filterchilds.go | 52 ++++ pkg/tree/ops/getbyowner.go | 24 +- pkg/tree/ops/getdeletes.go | 87 +++++++ pkg/tree/ops/gethighestprecedence.go | 27 ++ .../ops/gethighestprecedencevalueofbranch.go | 27 ++ pkg/tree/ops/getlistchilds.go | 52 ++++ pkg/tree/ops/holdsleafvariants.go | 18 ++ pkg/tree/ops/json.go | 4 +- pkg/tree/ops/proto.go | 4 +- pkg/tree/ops/treeexport.go | 2 +- pkg/tree/ops/utils.go | 4 +- pkg/tree/ops/validation/validate.go | 5 +- .../validation/validation_entry_leafref.go | 22 +- .../ops/validation/yang-parser-adapter.go | 5 +- pkg/tree/ops/xml.go | 4 +- .../processors/processor_explicit_delete.go | 3 +- pkg/tree/processors/processor_importer.go | 4 +- pkg/tree/root_entry.go | 30 +-- pkg/tree/sharedEntryAttributes.go | 245 +----------------- pkg/tree/sharedEntryAttributes_test.go | 4 +- 25 files changed, 560 insertions(+), 364 deletions(-) rename pkg/tree/{ => api}/choice_case_resolver.go (86%) create mode 100644 pkg/tree/ops/containsonlydefaults_test.go create mode 100644 pkg/tree/ops/filterchilds.go create mode 100644 pkg/tree/ops/getdeletes.go create mode 100644 pkg/tree/ops/gethighestprecedence.go create mode 100644 pkg/tree/ops/gethighestprecedencevalueofbranch.go create mode 100644 pkg/tree/ops/getlistchilds.go create mode 100644 pkg/tree/ops/holdsleafvariants.go diff --git a/pkg/tree/choice_case_resolver.go b/pkg/tree/api/choice_case_resolver.go similarity index 86% rename from pkg/tree/choice_case_resolver.go rename to pkg/tree/api/choice_case_resolver.go index 859c47d1..2cc48a7f 100644 --- a/pkg/tree/choice_case_resolver.go +++ b/pkg/tree/api/choice_case_resolver.go @@ -1,42 +1,47 @@ -package tree +package api import ( "math" ) -type choiceResolvers map[string]*choiceResolver +type ChoiceResolvers map[string]*choiceResolver // AddChoice adds / registers a new Choice to the choiceCasesResolver -func (c choiceResolvers) AddChoice(name string) *choiceResolver { +func (c ChoiceResolvers) AddChoice(name string) *choiceResolver { r := newChoiceResolver() c[name] = r return r } // GetDeletes returns the names of the elements that need to be deleted. -func (c choiceResolvers) GetDeletes() []string { - result := []string{} +func (c ChoiceResolvers) GetDeletes() []string { + if c == nil { + return nil + } + var result []string for _, cases := range c { result = append(result, cases.GetDeletes()...) } return result } -func (c choiceResolvers) deepCopy() choiceResolvers { - result := choiceResolvers{} +func (c ChoiceResolvers) DeepCopy() ChoiceResolvers { + if c == nil { + return nil + } + result := ChoiceResolvers{} for k, v := range c { result[k] = v.deepCopy() } return result - } // GetSkipElements returns the list of all choices elements that are not highes priority. // The resulting slice is used to skip these elements. -func (c choiceResolvers) GetSkipElements() []string { - result := []string{} +func (c ChoiceResolvers) GetSkipElements() []string { + var result []string for _, x := range c { result = append(result, x.GetSkipElements()...) } @@ -52,6 +57,9 @@ type choiceResolver struct { } func (c *choiceResolver) deepCopy() *choiceResolver { + if c == nil { + return nil + } result := &choiceResolver{ cases: map[string]*choicesCase{}, elementToCaseMapping: map[string]*choicesCase{}, @@ -105,6 +113,9 @@ func (c *choiceResolver) SetValue(elemName string, highestWODel int32, highestDe // GetSkipElements returns the names of all the elements that belong to // cases that have not the best priority func (c *choiceResolver) GetSkipElements() []string { + if c == nil { + return nil + } result := make([]string, 0, len(c.elementToCaseMapping)) bestCase := c.GetBestCaseNow() @@ -117,10 +128,13 @@ func (c *choiceResolver) GetSkipElements() []string { return result } -func (c *choiceResolver) GetBestCaseNow() *choicesCase { - // best case now - var highestCaseWODeleted *choicesCase +func (c *choiceResolver) getBestCases() (*choicesCase, *choicesCase) { + if c == nil { + return nil, nil + } + var highestCaseWODeleted, highestCaseWONew *choicesCase highesPrecedenceWODeletedValue := int32(math.MaxInt32) + highesPrecedenceWONewValue := int32(math.MaxInt32) for _, cas := range c.cases { actualCaseWODeletedValue := cas.getHighestPrecedenceWODeleted() @@ -128,31 +142,28 @@ func (c *choiceResolver) GetBestCaseNow() *choicesCase { highesPrecedenceWODeletedValue = actualCaseWODeletedValue highestCaseWODeleted = cas } - } - return highestCaseWODeleted -} - -func (c *choiceResolver) GetBestCaseBefore() *choicesCase { - // best case before - var highestCaseWONew *choicesCase - highesPrecedenceWONewValue := int32(math.MaxInt32) - - for _, cas := range c.cases { actualCaseWONewValue := cas.getHighestPrecedenceWONew() if actualCaseWONewValue < highesPrecedenceWONewValue { highesPrecedenceWONewValue = actualCaseWONewValue highestCaseWONew = cas } } - return highestCaseWONew + return highestCaseWODeleted, highestCaseWONew +} + +func (c *choiceResolver) GetBestCaseNow() *choicesCase { + best, _ := c.getBestCases() + return best +} + +func (c *choiceResolver) GetBestCaseBefore() *choicesCase { + _, best := c.getBestCases() + return best } func (c *choiceResolver) GetDeletes() []string { - result := []string{} - // best case now - highestCaseNow := c.GetBestCaseNow() - // best case before - highestCaseBefore := c.GetBestCaseBefore() + var result []string + highestCaseNow, highestCaseBefore := c.getBestCases() // if best case before (highestCaseWONew) != best case now (highestCaseWODeleted) if highestCaseBefore != nil && highestCaseBefore != highestCaseNow { @@ -214,6 +225,9 @@ func (c *choicesCase) getHighestPrecedenceWODeleted() int32 { } func (c *choicesCase) deepCopy() *choicesCase { + if c == nil { + return nil + } result := &choicesCase{ name: c.name, elements: make(map[string]*caseElement), @@ -237,6 +251,9 @@ func newCaseElement(name string) *caseElement { } func (c *caseElement) deepCopy() *caseElement { + if c == nil { + return nil + } return &caseElement{ name: c.name, highestWODeleted: c.highestWODeleted, diff --git a/pkg/tree/api/entry.go b/pkg/tree/api/entry.go index c2914076..e7d9ce83 100644 --- a/pkg/tree/api/entry.go +++ b/pkg/tree/api/entry.go @@ -41,30 +41,8 @@ type Entry interface { GetLevel() int // addChild Add a child entry AddChild(context.Context, Entry) error - // // getOrCreateChilds retrieves the sub-child pointed at by the path. - // // if the path does not exist in its full extend, the entries will be added along the way - // // if the path does not point to a schema defined path an error will be raise - // // GetOrCreateChilds(ctx context.Context, path *sdcpb.Path) (Entry, error) - // // AddUpdateRecursive Add the given cache.Update to the tree - // AddUpdateRecursive(ctx context.Context, relativePath *sdcpb.Path, u *types.Update, flags *types.UpdateInsertFlags) (Entry, error) - // AddUpdateRecursiveInternal(ctx context.Context, path *sdcpb.Path, idx int, u *types.Update, flags *types.UpdateInsertFlags) (Entry, error) // StringIndent debug tree struct as indented string slice StringIndent(result []string) []string - // GetHighesPrio return the new cache.Update entried from the tree that are the highes priority. - // If the onlyNewOrUpdated option is set to true, only the New or Updated entries will be returned - // It will append to the given list and provide a new pointer to the slice - GetHighestPrecedence(result LeafVariantSlice, onlyNewOrUpdated bool, includeDefaults bool, includeExplicitDelete bool) LeafVariantSlice - // getHighestPrecedenceLeafValue returns the highest LeafValue of the Entry at hand - // will return an error if the Entry is not a Leaf - GetHighestPrecedenceLeafValue(context.Context) (*LeafEntry, error) - // GetByOwner returns the branches Updates by owner - // GetByOwner(owner string, result []*LeafEntry) LeafVariantSlice - // // markOwnerDelete Sets the delete flag on all the LeafEntries belonging to the given owner. - // MarkOwnerDelete(o string, onlyIntended bool) - // GetDeletes returns the cache-updates that are not updated, have no lower priority value left and hence should be deleted completely - GetDeletes(entries types.DeleteEntriesList, aggregatePaths bool) (types.DeleteEntriesList, error) - // getHighestPrecedenceValueOfBranch returns the highes Precedence Value (lowest Priority value) of the brach that starts at this Entry - GetHighestPrecedenceValueOfBranch(filter HighestPrecedenceFilter) int32 // GetSchema returns the *sdcpb.SchemaElem of the Entry GetSchema() *sdcpb.SchemaElem // IsRoot returns true if the Entry is the root of the tree @@ -97,21 +75,21 @@ type Entry interface { CanDelete() bool GetChildMap() *ChildMap GetChilds(types.DescendMethod) EntryMap - GetChild(name string) (Entry, bool) // entry, exists - FilterChilds(keys map[string]string) ([]Entry, error) + // // DeleteBranch Deletes from the tree, all elements of the PathSlice defined branch of the given owner // DeleteBranch(ctx context.Context, path *sdcpb.Path, owner string) (err error) // GetListChilds collects all the childs of the list. In the tree we store them seperated into their key branches. // this is collecting all the last level key entries. - GetListChilds() ([]Entry, error) + // GetListChilds() ([]Entry, error) DeepCopy(tc TreeContext, parent Entry) (Entry, error) GetLeafVariants() *LeafVariants - - // returns true if the Entry contains leafvariants (presence container, field or leaflist) - HoldsLeafvariants() bool CanDeleteBranch(keepDefault bool) bool DeleteCanDeleteChilds(keepDefault bool) + // GetTreeContext returns the TreeContext of the Entry, which holds global information about the tree + // and is used for certain operations that require access to this global information. GetTreeContext() TreeContext + // ChoicesResolvers returns the choice case resolvers for the entry, if any + ChoicesResolvers() ChoiceResolvers } type LeafVariantEntry interface { diff --git a/pkg/tree/entry_test.go b/pkg/tree/entry_test.go index e44139e8..d4430520 100644 --- a/pkg/tree/entry_test.go +++ b/pkg/tree/entry_test.go @@ -621,7 +621,7 @@ func Test_Entry_Three(t *testing.T) { // log the tree t.Log(root.String()) - highPriLe := root.getByOwnerFiltered(owner1, api.FilterNonDeleted) + highPriLe := ops.GetByOwnerFiltered(root.Entry, owner1, api.FilterNonDeleted) highPri := api.LeafEntriesToUpdates(highPriLe) @@ -895,7 +895,7 @@ func Test_Entry_Four(t *testing.T) { // log the tree t.Log(root.String()) - highPriLe := root.getByOwnerFiltered(owner1, api.FilterNonDeleted) + highPriLe := ops.GetByOwnerFiltered(root.Entry, owner1, api.FilterNonDeleted) highPri := api.LeafEntriesToUpdates(highPriLe) diff --git a/pkg/tree/ops/containsonlydefaults.go b/pkg/tree/ops/containsonlydefaults.go index c921f4a0..0ddb22a0 100644 --- a/pkg/tree/ops/containsonlydefaults.go +++ b/pkg/tree/ops/containsonlydefaults.go @@ -1,7 +1,6 @@ package ops import ( - "context" "slices" "github.com/sdcio/data-server/pkg/tree/api" @@ -9,34 +8,36 @@ import ( "github.com/sdcio/data-server/pkg/tree/types" ) -// containsOnlyDefaults checks for presence containers, if only default values are present, -// such that the Entry should also be treated as a presence container -func ContainsOnlyDefaults(ctx context.Context, e api.Entry) bool { - // if no schema is present, we must be in a key level - if e.GetSchema() == nil { +// ContainsOnlyDefaults checks if the given entry is a container that only contains leafvariants with owner defaults, +// and that all childs are in the list of childs with defaults defined in the container schema. Containers without +// childs return true, while non-container entries or entries without schema return false. It returns true if the +// entry is a container with only default values, and false otherwise. +func ContainsOnlyDefaults(e api.Entry) bool { + schema := e.GetSchema() + if schema == nil { return false } - contSchema := e.GetSchema().GetContainer() + + contSchema := schema.GetContainer() if contSchema == nil { return false } - // only if length of childs is (more) compared to the number of - // attributes carrying defaults, the presence condition can be met - if len(e.GetChilds(types.DescendMethodAll)) > len(contSchema.ChildsWithDefaults) { + // if the amount of childs is higher than the amount of childs with defaults, it can't be only defaults + childs := e.GetChilds(types.DescendMethodAll) + if len(childs) > len(contSchema.ChildsWithDefaults) { return false } - for k, v := range e.GetChilds(types.DescendMethodAll) { - // check if child name is part of ChildsWithDefaults + + // check if all childs are in the list of childs with defaults, and that they only have a leafvariant with owner defaults + for k, v := range childs { if !slices.Contains(contSchema.ChildsWithDefaults, k) { return false } - // check if the value is the default value - le, err := v.GetHighestPrecedenceLeafValue(ctx) - if err != nil { + le := v.GetLeafVariants().GetHighestPrecedence(false, true, false) + if le == nil { return false } - // if the owner is not Default return false if le.Owner() != consts.DefaultsIntentName { return false } diff --git a/pkg/tree/ops/containsonlydefaults_test.go b/pkg/tree/ops/containsonlydefaults_test.go new file mode 100644 index 00000000..fbbcd272 --- /dev/null +++ b/pkg/tree/ops/containsonlydefaults_test.go @@ -0,0 +1,155 @@ +package ops_test + +import ( + "context" + "fmt" + "runtime" + "testing" + + "github.com/sdcio/data-server/pkg/pool" + "github.com/sdcio/data-server/pkg/tree" + "github.com/sdcio/data-server/pkg/tree/api" + "github.com/sdcio/data-server/pkg/tree/ops" + "github.com/sdcio/data-server/pkg/tree/types" + "github.com/sdcio/data-server/pkg/utils/testhelper" + sdcpb "github.com/sdcio/sdc-protos/sdcpb" + "go.uber.org/mock/gomock" +) + +func TestContainsOnlyDefaults(t *testing.T) { + tests := []struct { + name string + setup func(t *testing.T, ctx context.Context, root *tree.RootEntry) + path *sdcpb.Path // if nil, test root.Entry; otherwise navigate to this path + want bool + }{ + { + name: "empty root entry returns true (all children are defaults)", + setup: func(t *testing.T, ctx context.Context, root *tree.RootEntry) { + // Empty root entry has no children, so all (zero) children are defaults + }, + path: nil, + want: true, + }, + { + name: "entry with non-default values returns false", + setup: func(t *testing.T, ctx context.Context, root *tree.RootEntry) { + // Add non-default value + _, err := ops.AddUpdateRecursive( + ctx, + root.Entry, + &sdcpb.Path{ + Elem: []*sdcpb.PathElem{ + sdcpb.NewPathElem("interface", map[string]string{"name": "ethernet-1/1"}), + sdcpb.NewPathElem("description", nil), + }, + }, + types.NewUpdate(nil, testhelper.GetStringTvProto("MyDescription"), 10, "custom-intent", 0), + types.NewUpdateInsertFlags().SetNewFlag(), + ) + if err != nil { + t.Fatal(err) + } + }, + path: &sdcpb.Path{ + Elem: []*sdcpb.PathElem{ + sdcpb.NewPathElem("interface", map[string]string{"name": "ethernet-1/1"}), + }, + }, + want: false, + }, + { + name: "entry with child having non-default owner returns false", + setup: func(t *testing.T, ctx context.Context, root *tree.RootEntry) { + // Load config with non-default intent on actual data + _, err := testhelper.LoadYgotStructIntoTreeRoot( + ctx, + testhelper.Config1(), + root.Entry, + "custom-owner", + 5, + false, + testhelper.FlagsNew, + ) + if err != nil { + t.Fatal(err) + } + }, + path: &sdcpb.Path{ + Elem: []*sdcpb.PathElem{ + sdcpb.NewPathElem("interface", map[string]string{"name": "ethernet-1/1"}), + }, + }, + want: false, + }, + { + name: "entry with multiple children non-defaults returns false", + setup: func(t *testing.T, ctx context.Context, root *tree.RootEntry) { + // Add multiple children with non-default owner + for i := 0; i < 3; i++ { + _, err := ops.AddUpdateRecursive( + ctx, + root.Entry, + &sdcpb.Path{ + Elem: []*sdcpb.PathElem{ + sdcpb.NewPathElem("interface", map[string]string{"name": fmt.Sprintf("ethernet-1/%d", i+1)}), + sdcpb.NewPathElem("description", nil), + }, + }, + types.NewUpdate(nil, testhelper.GetStringTvProto(fmt.Sprintf("Description%d", i)), 10, "custom-intent", 0), + types.NewUpdateInsertFlags().SetNewFlag(), + ) + if err != nil { + t.Fatal(err) + } + } + }, + path: nil, + want: false, + }, + } + + ctx := context.Background() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Common setup for all tests + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + scb, err := testhelper.GetSchemaClientBound(t, mockCtrl) + if err != nil { + t.Fatal(err) + } + + tc := tree.NewTreeContext(scb, pool.NewSharedTaskPool(ctx, runtime.GOMAXPROCS(0))) + root, err := tree.NewTreeRoot(ctx, tc) + if err != nil { + t.Fatal(err) + } + + // Test-specific setup + tt.setup(t, ctx, root) + + // Finish insertion phase + if err = root.FinishInsertionPhase(ctx); err != nil { + t.Fatal(err) + } + + // Navigate to path or use root + var entry api.Entry + if tt.path != nil { + entry, err = root.Entry.NavigateSdcpbPath(ctx, tt.path) + if err != nil { + t.Fatalf("failed to navigate to path %s: %v", tt.path.ToXPath(false), err) + } + } else { + entry = root.Entry + } + + got := ops.ContainsOnlyDefaults(entry) + if got != tt.want { + t.Errorf("ContainsOnlyDefaults() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/tree/ops/filterchilds.go b/pkg/tree/ops/filterchilds.go new file mode 100644 index 00000000..acf3042e --- /dev/null +++ b/pkg/tree/ops/filterchilds.go @@ -0,0 +1,52 @@ +package ops + +import ( + "fmt" + "sort" + + "github.com/sdcio/data-server/pkg/tree/api" + "github.com/sdcio/data-server/pkg/tree/types" +) + +// FilterChilds returns the child entries (skipping the key entries in the tree) that +// match the given keys. The keys do not need to match all levels of keys, in which case the +// key level is considered a wildcard match (*) +func FilterChilds(s api.Entry, keys map[string]string) ([]api.Entry, error) { + if s.GetSchema() == nil { + return nil, fmt.Errorf("error non schema level %s", s.SdcpbPath().ToXPath(false)) + } + + // Retrieve and sort schema keys to maintain insertion order + schemaKeys := GetSchemaKeys(s) + sort.Strings(schemaKeys) + + // Start with the root entry + currentEntries := []api.Entry{s} + + // Iterate through each key level, filtering or expanding entries + for _, key := range schemaKeys { + var nextEntries []api.Entry + + if keyVal, exists := keys[key]; exists { + // Filter: find children matching the specific key value + for _, entry := range currentEntries { + children := entry.GetChilds(types.DescendMethodAll) + if matchEntry, found := children[keyVal]; found { + nextEntries = append(nextEntries, matchEntry) + } + } + } else { + // Wildcard: collect all children + for _, entry := range currentEntries { + children := entry.GetChilds(types.DescendMethodAll) + for _, child := range children { + nextEntries = append(nextEntries, child) + } + } + } + + currentEntries = nextEntries + } + + return currentEntries, nil +} diff --git a/pkg/tree/ops/getbyowner.go b/pkg/tree/ops/getbyowner.go index 10ae3255..2e1088f2 100644 --- a/pkg/tree/ops/getbyowner.go +++ b/pkg/tree/ops/getbyowner.go @@ -16,15 +16,33 @@ func GetByOwner(e api.Entry, owner string) api.LeafVariantSlice { // getByOwnerInternal is the internal function that performs the actual retrieval of the LeafEntries by owner. It is called recursively to traverse the tree. // It takes an additional result parameter that is used to accumulate the results during the recursive traversal. Since that LeafVariantSlice might grow during the traversal, // it is returned as a new slice to ensure that the changes are reflected in the caller. -func getByOwnerInternal(e api.Entry, owner string, result api.LeafVariantSlice) api.LeafVariantSlice { +func getByOwnerInternal(e api.Entry, owner string, result api.LeafVariantSlice, f ...api.LeafEntryFilter) api.LeafVariantSlice { lv := e.GetLeafVariants().GetByOwner(owner) + add := true if lv != nil { - result = append(result, lv) + for _, filter := range f { + // if the filter yields false, skip + if !filter(lv) { + add = false + break + } + } + if add { + result = append(result, lv) + } } // continue with childs for _, c := range e.GetChilds(types.DescendMethodAll) { - result = getByOwnerInternal(c, owner, result) + result = getByOwnerInternal(c, owner, result, f...) } return result } + +// getByOwnerFiltered returns the Tree content filtered by owner, whilst allowing to filter further +// via providing additional LeafEntryFilter +func GetByOwnerFiltered(e api.Entry, owner string, f ...api.LeafEntryFilter) api.LeafVariantSlice { + result := api.LeafVariantSlice{} + result = getByOwnerInternal(e, owner, result, f...) + return result +} diff --git a/pkg/tree/ops/getdeletes.go b/pkg/tree/ops/getdeletes.go new file mode 100644 index 00000000..62bb51d7 --- /dev/null +++ b/pkg/tree/ops/getdeletes.go @@ -0,0 +1,87 @@ +package ops + +import ( + "github.com/sdcio/data-server/pkg/tree/api" + + "github.com/sdcio/data-server/pkg/tree/types" + sdcpb "github.com/sdcio/sdc-protos/sdcpb" +) + +// GetDeletes calculate the deletes that need to be send to the device. +func GetDeletes(e api.Entry, deletes types.DeleteEntriesList, aggregatePaths bool) (types.DeleteEntriesList, error) { + + // if the actual level has no schema assigned we're on a key level + // element. Hence we try deletion via aggregation + if e.GetSchema() == nil && aggregatePaths { + return getAggregatedDeletes(e, deletes, aggregatePaths) + } + + // else perform regular deletion + return getRegularDeletes(e, deletes, aggregatePaths) +} + +// getAggregatedDeletes is called on levels that have no schema attached, meaning key schemas. +// here we might delete the whole branch of the tree, if all key elements are being deleted +// if not, we continue with regular deletes +func getAggregatedDeletes(e api.Entry, deletes []types.DeleteEntry, aggregatePaths bool) ([]types.DeleteEntry, error) { + var err error + // we take a look into the level(s) up + // trying to get the schema + ancestor, level := GetFirstAncestorWithSchema(e) + + // check if the first schema on the path upwards (parents) + // has keys defined (meaning is a contianer with keys) + keys := GetSchemaKeys(ancestor) + + // if keys exist and we're on the last level of the keys, validate + // if aggregation can happen + if len(keys) > 0 && level == len(keys) { + doAggregateDelete := true + // check the keys for deletion + for _, n := range keys { + c, exists := e.GetChildMap().GetEntry(n) + // these keys should always exist, so for now we do not catch the non existing key case + if exists && !c.ShouldDelete() { + // if not all the keys are marked for deletion, we need to revert to regular deletion + doAggregateDelete = false + break + } + } + // if aggregate delete is possible do it + if doAggregateDelete { + // by adding the key path to the deletes + deletes = append(deletes, e) + } else { + // otherwise continue with deletion on the childs. + for _, c := range e.GetChildMap().GetAll() { + deletes, err = GetDeletes(c, deletes, aggregatePaths) + if err != nil { + return nil, err + } + } + } + return deletes, nil + } + return getRegularDeletes(e, deletes, aggregatePaths) +} + +// getRegularDeletes performs deletion calculation on elements that have a schema attached. +func getRegularDeletes(e api.Entry, deletes types.DeleteEntriesList, aggregate bool) (types.DeleteEntriesList, error) { + var err error + + if e.ShouldDelete() && !e.IsRoot() && len(GetSchemaKeys(e)) == 0 { + return append(deletes, e), nil + } + + for _, elem := range e.ChoicesResolvers().GetDeletes() { + deletes = append(deletes, types.NewDeleteEntryImpl(e.SdcpbPath().CopyPathAddElem(sdcpb.NewPathElem(elem, nil)))) + } + + for _, c := range e.GetChildMap().GetAll() { + deletes, err = GetDeletes(c, deletes, aggregate) + if err != nil { + return nil, err + } + } + return deletes, nil +} diff --git a/pkg/tree/ops/gethighestprecedence.go b/pkg/tree/ops/gethighestprecedence.go new file mode 100644 index 00000000..fa2e7673 --- /dev/null +++ b/pkg/tree/ops/gethighestprecedence.go @@ -0,0 +1,27 @@ +package ops + +import ( + "github.com/sdcio/data-server/pkg/tree/api" + "github.com/sdcio/data-server/pkg/tree/types" +) + +// GetHighestPrecedence goes through the whole branch and returns the new and updated cache.Updates. +// These are the updated that will be send to the device. +func GetHighestPrecedence(s api.Entry, onlyNewOrUpdated bool, includeDefaults bool, includeExplicitDelete bool) api.LeafVariantSlice { + result := make(api.LeafVariantSlice, 0) + return getHighestPrecedenceInternal(s, result, onlyNewOrUpdated, includeDefaults, includeExplicitDelete) +} + +func getHighestPrecedenceInternal(s api.Entry, result api.LeafVariantSlice, onlyNewOrUpdated bool, includeDefaults bool, includeExplicitDelete bool) api.LeafVariantSlice { + // get the highes precedence LeafeVariant and add it to the list + lv := s.GetLeafVariants().GetHighestPrecedence(onlyNewOrUpdated, includeDefaults, includeExplicitDelete) + if lv != nil { + result = append(result, lv) + } + + // continue with childs. Childs are part of choices, process only the "active" (highes precedence) childs + for _, c := range s.GetChilds(types.DescendMethodActiveChilds) { + result = getHighestPrecedenceInternal(c, result, onlyNewOrUpdated, includeDefaults, includeExplicitDelete) + } + return result +} diff --git a/pkg/tree/ops/gethighestprecedencevalueofbranch.go b/pkg/tree/ops/gethighestprecedencevalueofbranch.go new file mode 100644 index 00000000..7f04d3d5 --- /dev/null +++ b/pkg/tree/ops/gethighestprecedencevalueofbranch.go @@ -0,0 +1,27 @@ +package ops + +import ( + "math" + + "github.com/sdcio/data-server/pkg/tree/api" +) + +// GetHighestPrecedenceValueOfBranch goes through all the child branches to find the highest +// precedence value (lowest priority value) for the entire branch and returns it. +func GetHighestPrecedenceValueOfBranch(e api.Entry, filter api.HighestPrecedenceFilter) int32 { + highestPrecedence := int32(math.MaxInt32) + + // Check all child branches (zero-allocation iteration) + e.GetChildMap().ForEach(func(_ string, child api.Entry) { + if childPrecedence := GetHighestPrecedenceValueOfBranch(child, filter); childPrecedence < highestPrecedence { + highestPrecedence = childPrecedence + } + }) + + // Check leaf variants + if val := e.GetLeafVariants().GetHighestPrecedenceValue(filter); val < highestPrecedence { + highestPrecedence = val + } + + return highestPrecedence +} diff --git a/pkg/tree/ops/getlistchilds.go b/pkg/tree/ops/getlistchilds.go new file mode 100644 index 00000000..f495e7ec --- /dev/null +++ b/pkg/tree/ops/getlistchilds.go @@ -0,0 +1,52 @@ +package ops + +import ( + "fmt" + + "github.com/sdcio/data-server/pkg/tree/api" + "github.com/sdcio/data-server/pkg/tree/types" +) + +// GetListChilds collects all the childs of the list. In the tree we store them seperated into their key branches. +// this is collecting all the last level key entries. +func GetListChilds(e api.Entry) ([]api.Entry, error) { + if e.GetSchema() == nil { + return nil, fmt.Errorf("error GetListChilds() non schema level %s", e.SdcpbPath().ToXPath(false)) + } + if e.GetSchema().GetContainer() == nil { + return nil, fmt.Errorf("error GetListChilds() not a Container %s", e.SdcpbPath().ToXPath(false)) + } + keys := GetSchemaKeys(e) + if len(keys) == 0 { + return nil, fmt.Errorf("error GetListChilds() not a List Container %s", e.SdcpbPath().ToXPath(false)) + } + + current := []api.Entry{e} + + // Collect descendants level-by-level through key hierarchy + for range keys { + // Cache children and calculate total count + childrenList := make([]api.EntryMap, len(current)) + totalChildren := 0 + // Iterate current level, collect children and count total + for i, entry := range current { + children := entry.GetChilds(types.DescendMethodAll) + childrenList[i] = children + totalChildren += len(children) + } + + // Allocate result slice with total count and populate it + next := make([]api.Entry, totalChildren) + idx := 0 + // Iterate cached children and populate next level slice + for _, children := range childrenList { + for _, child := range children { + next[idx] = child + idx++ + } + } + // Move to next level + current = next + } + return current, nil +} diff --git a/pkg/tree/ops/holdsleafvariants.go b/pkg/tree/ops/holdsleafvariants.go new file mode 100644 index 00000000..fdaf673c --- /dev/null +++ b/pkg/tree/ops/holdsleafvariants.go @@ -0,0 +1,18 @@ +package ops + +import ( + "github.com/sdcio/data-server/pkg/tree/api" + sdcpb "github.com/sdcio/sdc-protos/sdcpb" +) + +func HoldsLeafVariants(e api.Entry) bool { + switch x := e.GetSchema().GetSchema().(type) { + case *sdcpb.SchemaElem_Container: + return x.Container.GetIsPresence() + case *sdcpb.SchemaElem_Leaflist: + return true + case *sdcpb.SchemaElem_Field: + return true + } + return false +} diff --git a/pkg/tree/ops/json.go b/pkg/tree/ops/json.go index 1cb85b45..f0d9fb70 100644 --- a/pkg/tree/ops/json.go +++ b/pkg/tree/ops/json.go @@ -66,7 +66,7 @@ func toJsonInternal(ctx context.Context, e api.Entry, onlyNewOrUpdated bool, iet case len(GetSchemaKeys(e)) > 0: // if the container contains keys, then it is a list // hence must be rendered as an array - childs, err := e.FilterChilds(nil) + childs, err := FilterChilds(e, nil) if err != nil { return nil, err } @@ -88,7 +88,7 @@ func toJsonInternal(ctx context.Context, e api.Entry, onlyNewOrUpdated bool, iet return nil, nil } return result, nil - case e.GetSchema().GetContainer().IsPresence && ContainsOnlyDefaults(ctx, e): + case e.GetSchema().GetContainer().IsPresence && ContainsOnlyDefaults(e): // Presence container without any childs if onlyNewOrUpdated { // presence containers have leafvariantes with typedValue_Empty, so check that diff --git a/pkg/tree/ops/proto.go b/pkg/tree/ops/proto.go index 973cd9e5..a5b0e062 100644 --- a/pkg/tree/ops/proto.go +++ b/pkg/tree/ops/proto.go @@ -10,13 +10,13 @@ import ( func ToProtoUpdates(ctx context.Context, e api.Entry, onlyNewOrUpdated bool) ([]*sdcpb.Update, error) { result := api.LeafVariantSlice{} - result = e.GetHighestPrecedence(result, onlyNewOrUpdated, false, true) + result = GetHighestPrecedence(e, onlyNewOrUpdated, false, true) return result.ToSdcpbUpdateSlice(), nil } func ToProtoDeletes(ctx context.Context, e api.Entry) ([]*sdcpb.Path, error) { result := []types.DeleteEntry{} - deletes, err := e.GetDeletes(result, true) + deletes, err := GetDeletes(e, result, true) if err != nil { return nil, err } diff --git a/pkg/tree/ops/treeexport.go b/pkg/tree/ops/treeexport.go index 56a9030f..d907646e 100644 --- a/pkg/tree/ops/treeexport.go +++ b/pkg/tree/ops/treeexport.go @@ -52,7 +52,7 @@ func treeExportLevel(e api.Entry, owner string) ([]*tree_persist.TreeElement, er } if len(GetSchemaKeys(e)) > 0 { - children, err := e.FilterChilds(nil) + children, err := GetListChilds(e) if err != nil { return nil, err } diff --git a/pkg/tree/ops/utils.go b/pkg/tree/ops/utils.go index 55792e39..f3818d54 100644 --- a/pkg/tree/ops/utils.go +++ b/pkg/tree/ops/utils.go @@ -19,8 +19,8 @@ func getListEntrySortFunc(parent api.Entry) func(a, b api.Entry) int { if !exists { return 0 } - aLvSlice := achild.GetHighestPrecedence(api.LeafVariantSlice{}, false, true, true) - bLvSlice := bchild.GetHighestPrecedence(api.LeafVariantSlice{}, false, true, true) + aLvSlice := GetHighestPrecedence(achild, false, true, true) + bLvSlice := GetHighestPrecedence(bchild, false, true, true) aEntry := aLvSlice[0] bEntry := bLvSlice[0] diff --git a/pkg/tree/ops/validation/validate.go b/pkg/tree/ops/validation/validate.go index 9f2c2458..cf99297f 100644 --- a/pkg/tree/ops/validation/validate.go +++ b/pkg/tree/ops/validation/validate.go @@ -14,6 +14,7 @@ import ( "github.com/sdcio/data-server/pkg/config" "github.com/sdcio/data-server/pkg/pool" "github.com/sdcio/data-server/pkg/tree/api" + "github.com/sdcio/data-server/pkg/tree/ops" "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/logger" sdcpb "github.com/sdcio/sdc-protos/sdcpb" @@ -175,7 +176,7 @@ func validateMinMaxElements(e api.Entry, resultChan chan<- *types.ValidationResu } // get all the childs, skipping the key levels - childs, err := e.GetListChilds() + childs, err := ops.GetListChilds(e) if err != nil { resultChan <- types.NewValidationResultEntry("unknown", fmt.Errorf("error getting childs for min/max-elements check %v", err), types.ValidationResultEntryTypeError) } @@ -195,7 +196,7 @@ func validateMinMaxElements(e api.Entry, resultChan chan<- *types.ValidationResu childAttributes := child.GetChilds(types.DescendMethodActiveChilds) keyName := contSchema.GetKeys()[0].GetName() if keyAttr, ok := childAttributes[keyName]; ok { - highestPrec := keyAttr.GetHighestPrecedence(nil, false, false, false) + highestPrec := ops.GetHighestPrecedence(keyAttr, false, false, false) if len(highestPrec) > 0 { owner := highestPrec[0].Update.Owner() ownersSet[owner] = struct{}{} diff --git a/pkg/tree/ops/validation/validation_entry_leafref.go b/pkg/tree/ops/validation/validation_entry_leafref.go index 635c4692..0045a833 100644 --- a/pkg/tree/ops/validation/validation_entry_leafref.go +++ b/pkg/tree/ops/validation/validation_entry_leafref.go @@ -76,7 +76,7 @@ func breadthSearch(ctx context.Context, e api.Entry, sdcpbPath *sdcpb.Path) ([]a // if the entry is a list with keys, try filtering the entries based on the keys if len(ops.GetSchemaKeys(entry)) > 0 { // filter the keys - childs, err = entry.FilterChilds(elem.KeysToMap()) + childs, err = ops.FilterChilds(entry, elem.KeysToMap()) if err != nil { return nil, err } @@ -138,9 +138,9 @@ func navigateLeafRef(ctx context.Context, e api.Entry) ([]api.Entry, error) { var resultEntries []api.Entry for _, e := range foundEntries { - r, err := e.GetHighestPrecedenceLeafValue(ctx) - if err != nil { - return nil, err + r := e.GetLeafVariants().GetHighestPrecedence(false, true, false) + if r == nil { + return nil, fmt.Errorf("no leafvariant found for entry %s", e.SdcpbPath()) } // Value of the resolved leafref refVal := r.Value() @@ -190,7 +190,7 @@ func resolveLeafrefKeyPath(ctx context.Context, e api.Entry, keys map[string]*ty return err } - lvs := keyValue.GetHighestPrecedence(api.LeafVariantSlice{}, false, false, false) + lvs := ops.GetHighestPrecedence(keyValue, false, false, false) if lvs == nil { return fmt.Errorf("no leafentry found") } @@ -215,7 +215,7 @@ func validateLeafRefs(ctx context.Context, e api.Entry, resultChan chan<- *types if err != nil || len(entry) == 0 { // check if the OptionalInstance (!require-instances [https://datatracker.ietf.org/doc/html/rfc7950#section-9.9.3]) if e.GetSchema().GetField().GetType().GetOptionalInstance() { - generateOptionalWarning(ctx, e, lref, resultChan) + generateOptionalWarning(e, lref, resultChan) return } owner := "unknown" @@ -240,7 +240,7 @@ func validateLeafRefs(ctx context.Context, e api.Entry, resultChan chan<- *types // check if the OptionalInstance (!require-instances [https://datatracker.ietf.org/doc/html/rfc7950#section-9.9.3]) if e.GetSchema().GetField().GetType().GetOptionalInstance() { - generateOptionalWarning(ctx, e, lref, resultChan) + generateOptionalWarning(e, lref, resultChan) return } // if required, issue error @@ -250,10 +250,10 @@ func validateLeafRefs(ctx context.Context, e api.Entry, resultChan chan<- *types stats.Add(types.StatTypeLeafRef, 1) } -func generateOptionalWarning(ctx context.Context, s api.Entry, lref string, resultChan chan<- *types.ValidationResultEntry) { - lrefval, err := s.GetHighestPrecedenceLeafValue(ctx) - if err != nil { - resultChan <- types.NewValidationResultEntry(lrefval.Owner(), err, types.ValidationResultEntryTypeError) +func generateOptionalWarning(s api.Entry, lref string, resultChan chan<- *types.ValidationResultEntry) { + lrefval := s.GetLeafVariants().GetHighestPrecedence(false, true, false) + if lrefval == nil { + resultChan <- types.NewValidationResultEntry("unknown", fmt.Errorf("no leafvariant found for entry %s", s.SdcpbPath()), types.ValidationResultEntryTypeError) return } tvVal := lrefval.Value() diff --git a/pkg/tree/ops/validation/yang-parser-adapter.go b/pkg/tree/ops/validation/yang-parser-adapter.go index cca51f11..ee070992 100644 --- a/pkg/tree/ops/validation/yang-parser-adapter.go +++ b/pkg/tree/ops/validation/yang-parser-adapter.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/sdcio/data-server/pkg/tree/api" + "github.com/sdcio/data-server/pkg/tree/ops" sdcpb "github.com/sdcio/sdc-protos/sdcpb" "github.com/sdcio/yang-parser/xpath" "github.com/sdcio/yang-parser/xpath/xutils" @@ -61,7 +62,7 @@ func (y *yangParserEntryAdapter) GetValue() (xpath.Datum, error) { return xpath.NewBoolDatum(true), nil } // list - childs, err := y.e.GetListChilds() + childs, err := ops.GetListChilds(y.e) if err != nil { return nil, err } @@ -74,7 +75,7 @@ func (y *yangParserEntryAdapter) GetValue() (xpath.Datum, error) { } // if y.e is anything else then a container - lv, _ := y.e.GetHighestPrecedenceLeafValue(y.ctx) + lv := y.e.GetLeafVariants().GetHighestPrecedence(false, true, false) if lv == nil { return xpath.NewNodesetDatum([]xutils.XpathNode{}), nil } diff --git a/pkg/tree/ops/xml.go b/pkg/tree/ops/xml.go index 50b5e971..a561ad86 100644 --- a/pkg/tree/ops/xml.go +++ b/pkg/tree/ops/xml.go @@ -94,7 +94,7 @@ func toXmlInternal(ctx context.Context, e api.Entry, parent *etree.Element, only // the container represents a list // if the container contains keys, then it is a list // hence must be rendered as an array - childs, err := e.FilterChilds(nil) + childs, err := GetListChilds(e) if err != nil { return false, err } @@ -141,7 +141,7 @@ func toXmlInternal(ctx context.Context, e api.Entry, parent *etree.Element, only // add the delete / remove operation utils.AddXMLOperation(newElem, utils.XMLOperationDelete, operationWithNamespace, useOperationRemove) return true, nil - case e.GetSchema().GetContainer().IsPresence && ContainsOnlyDefaults(ctx, e): + case e.GetSchema().GetContainer().IsPresence && ContainsOnlyDefaults(e): // process presence containers with no childs if onlyNewOrUpdated { // presence containers have leafvariantes with typedValue_Empty, so check that diff --git a/pkg/tree/processors/processor_explicit_delete.go b/pkg/tree/processors/processor_explicit_delete.go index de166243..fc2b312e 100644 --- a/pkg/tree/processors/processor_explicit_delete.go +++ b/pkg/tree/processors/processor_explicit_delete.go @@ -6,6 +6,7 @@ import ( "github.com/sdcio/data-server/pkg/pool" "github.com/sdcio/data-server/pkg/tree/api" + "github.com/sdcio/data-server/pkg/tree/ops" "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils" ) @@ -66,7 +67,7 @@ func newExplicitDeleteTask(entry api.Entry, params *ExplicitDeleteTaskParameters } func (t *explicitDeleteTask) Run(ctx context.Context, submit func(pool.Task) error) error { - if t.entry.HoldsLeafvariants() { + if ops.HoldsLeafVariants(t.entry) { le := t.entry.GetLeafVariants().GetByOwner(t.params.owner) if le != nil { le.MarkExpliciteDelete() diff --git a/pkg/tree/processors/processor_importer.go b/pkg/tree/processors/processor_importer.go index 542a711a..b1f34b58 100644 --- a/pkg/tree/processors/processor_importer.go +++ b/pkg/tree/processors/processor_importer.go @@ -118,7 +118,7 @@ func (task importConfigTask) Run(ctx context.Context, submit func(pool.Task) err if err != nil { return err } - if keyChild, exists = actual.GetChild(kv); !exists { + if keyChild, exists = actual.GetChildMap().GetEntry(kv); !exists { keyChild, err = api.NewEntry(ctx, actual, kv, task.params.treeContext) if err != nil { return err @@ -145,7 +145,7 @@ func (task importConfigTask) Run(ctx context.Context, submit func(pool.Task) err // submit each child for _, childElt := range elems { - child, exists := task.entry.GetChild(childElt.GetName()) + child, exists := task.entry.GetChildMap().GetEntry(childElt.GetName()) if !exists { var err error child, err = api.NewEntry(ctx, task.entry, childElt.GetName(), task.params.treeContext) diff --git a/pkg/tree/root_entry.go b/pkg/tree/root_entry.go index a1e6e795..78b5691c 100644 --- a/pkg/tree/root_entry.go +++ b/pkg/tree/root_entry.go @@ -97,7 +97,7 @@ func (r *RootEntry) GetUpdatesForOwner(owner string) types.UpdateSlice { // retrieve all the entries from the tree that belong to the given // Owner / Intent, skipping the once marked for deletion // this is to insert / update entries in the cache. - return api.LeafEntriesToUpdates(r.getByOwnerFiltered(owner, api.FilterNonDeletedButNewOrUpdated)) + return api.LeafEntriesToUpdates(ops.GetByOwnerFiltered(r.Entry, owner, api.FilterNonDeletedButNewOrUpdated)) } // GetDeletesForOwner returns the deletes that have been calculated for the given intent / owner @@ -106,7 +106,7 @@ func (r *RootEntry) GetDeletesForOwner(owner string) sdcpb.Paths { // and that are marked for deletion. // This is to cover all the cases where an intent was changed and certain // part of the config got deleted. - deletesOwnerUpdates := api.LeafEntriesToUpdates(r.getByOwnerFiltered(owner, api.FilterDeleted)) + deletesOwnerUpdates := api.LeafEntriesToUpdates(ops.GetByOwnerFiltered(r.Entry, owner, api.FilterDeleted)) // they are retrieved as cache.update, we just need the path for deletion from cache deletesOwner := make(sdcpb.Paths, 0, len(deletesOwnerUpdates)) // so collect the paths @@ -120,41 +120,19 @@ func (r *RootEntry) GetDeletesForOwner(owner string) sdcpb.Paths { // If the onlyNewOrUpdated option is set to true, only the New or Updated entries will be returned // It will append to the given list and provide a new pointer to the slice func (r *RootEntry) GetHighestPrecedence(onlyNewOrUpdated bool) api.LeafVariantSlice { - return r.Entry.GetHighestPrecedence(make(api.LeafVariantSlice, 0), onlyNewOrUpdated, false, false) + return ops.GetHighestPrecedence(r.Entry, onlyNewOrUpdated, false, false) } // GetDeletes returns the paths that due to the Tree content are to be deleted from the southbound device. func (r *RootEntry) GetDeletes(aggregatePaths bool) (types.DeleteEntriesList, error) { deletes := []types.DeleteEntry{} - return r.Entry.GetDeletes(deletes, aggregatePaths) + return ops.GetDeletes(r.Entry, deletes, aggregatePaths) } func (r *RootEntry) GetAncestorSchema() (*sdcpb.SchemaElem, int) { return nil, 0 } -// getByOwnerFiltered returns the Tree content filtered by owner, whilst allowing to filter further -// via providing additional LeafEntryFilter -func (r *RootEntry) getByOwnerFiltered(owner string, f ...api.LeafEntryFilter) []*api.LeafEntry { - result := []*api.LeafEntry{} - // retrieve all leafentries for the owner - leafEntries := ops.GetByOwner(r.Entry, owner) - - // range through entries -NEXTELEMENT: - for _, e := range leafEntries { - // apply filter - for _, filter := range f { - // if the filter yields false, skip - if !filter(e) { - continue NEXTELEMENT - } - } - result = append(result, e) - } - return result -} - // DeleteSubtree Deletes from the tree, all elements of the PathSlice defined branch of the given owner. Return values are remainsToExist and error if an error occured. func (r *RootEntry) DeleteBranchPaths(ctx context.Context, deletes types.DeleteEntriesList, intentName string) error { for _, del := range deletes { diff --git a/pkg/tree/sharedEntryAttributes.go b/pkg/tree/sharedEntryAttributes.go index b1c6d8e1..bfcb512d 100644 --- a/pkg/tree/sharedEntryAttributes.go +++ b/pkg/tree/sharedEntryAttributes.go @@ -5,7 +5,6 @@ import ( "fmt" "math" "slices" - "sort" "strings" "sync" @@ -31,7 +30,7 @@ type sharedEntryAttributes struct { schema *sdcpb.SchemaElem schemaMutex sync.RWMutex - choicesResolvers choiceResolvers + choicesResolvers api.ChoiceResolvers treeContext api.TreeContext @@ -64,7 +63,7 @@ func (s *sharedEntryAttributes) DeepCopy(tc api.TreeContext, parent api.Entry) ( childs: api.NewChildMap(), schema: s.schema, treeContext: tc, - choicesResolvers: s.choicesResolvers.deepCopy(), + choicesResolvers: s.choicesResolvers.DeepCopy(), schemaMutex: sync.RWMutex{}, cacheMutex: sync.Mutex{}, level: s.level, @@ -217,86 +216,6 @@ func (s *sharedEntryAttributes) GetSchema() *sdcpb.SchemaElem { return s.schema } -// getListChilds collects all the childs of the list. In the tree we store them seperated into their key branches. -// this is collecting all the last level key entries. -func (s *sharedEntryAttributes) GetListChilds() ([]api.Entry, error) { - if s.schema == nil { - return nil, fmt.Errorf("error GetListChilds() non schema level %s", s.SdcpbPath().ToXPath(false)) - } - if s.schema.GetContainer() == nil { - return nil, fmt.Errorf("error GetListChilds() not a Container %s", s.SdcpbPath().ToXPath(false)) - } - keys := s.schema.GetContainer().GetKeys() - if len(keys) == 0 { - return nil, fmt.Errorf("error GetListChilds() not a List Container %s", s.SdcpbPath().ToXPath(false)) - } - actualEntries := []api.Entry{s} - var newEntries []api.Entry - - for level := 0; level < len(keys); level++ { - for _, e := range actualEntries { - // add all children - for _, c := range e.GetChilds(types.DescendMethodAll) { - newEntries = append(newEntries, c) - } - } - actualEntries = newEntries - newEntries = []api.Entry{} - } - return actualEntries, nil - -} - -// FilterChilds returns the child entries (skipping the key entries in the tree) that -// match the given keys. The keys do not need to match all levels of keys, in which case the -// key level is considered a wildcard match (*) -func (s *sharedEntryAttributes) FilterChilds(keys map[string]string) ([]api.Entry, error) { - if s.schema == nil { - return nil, fmt.Errorf("error non schema level %s", s.SdcpbPath().ToXPath(false)) - } - - result := []api.Entry{} - // init the processEntries with s - processEntries := []api.Entry{s} - - // retrieve the schema keys - schemaKeys := ops.GetSchemaKeys(s) - // sort the keys, such that they appear in the order that they - // are inserted in the tree - sort.Strings(schemaKeys) - // iterate through the keys, resolving the key levels - for _, key := range schemaKeys { - keyVal, exist := keys[key] - // if the key exists in the input map meaning has a filter value - // associated, the childs map is filtered for that value - if exist { - // therefor we need to go through the processEntries List - // and collect all the matching childs - for _, entry := range processEntries { - childs := entry.GetChilds(types.DescendMethodAll) - matchEntry, childExists := childs[keyVal] - // so if such child, that matches the given filter value exists, we append it to the results - if childExists { - result = append(result, matchEntry) - } - } - } else { - // this is basically the wildcard case, so go through all childs and add them - result = []api.Entry{} - for _, entry := range processEntries { - childs := entry.GetChilds(types.DescendMethodAll) - for _, v := range childs { - // hence we add all the existing childs to the result list - result = append(result, v) - } - } - } - // prepare for the next iteration - processEntries = result - } - return result, nil -} - // GetParent returns the parent entry func (s *sharedEntryAttributes) GetParent() api.Entry { return s.parent @@ -331,63 +250,6 @@ func (s *sharedEntryAttributes) GetLevel() int { return level } -func (s *sharedEntryAttributes) HoldsLeafvariants() bool { - switch x := s.schema.GetSchema().(type) { - case *sdcpb.SchemaElem_Container: - return x.Container.GetIsPresence() - case *sdcpb.SchemaElem_Leaflist: - return true - case *sdcpb.SchemaElem_Field: - return true - } - return false -} - -// getAggregatedDeletes is called on levels that have no schema attached, meaning key schemas. -// here we might delete the whole branch of the tree, if all key elements are being deleted -// if not, we continue with regular deltes -func (s *sharedEntryAttributes) GetAggregatedDeletes(deletes []types.DeleteEntry, aggregatePaths bool) ([]types.DeleteEntry, error) { - var err error - // we take a look into the level(s) up - // trying to get the schema - ancestor, level := ops.GetFirstAncestorWithSchema(s) - - // check if the first schema on the path upwards (parents) - // has keys defined (meaning is a contianer with keys) - keys := ops.GetSchemaKeys(ancestor) - - // if keys exist and we're on the last level of the keys, validate - // if aggregation can happen - if len(keys) > 0 && level == len(keys) { - doAggregateDelete := true - // check the keys for deletion - for _, n := range keys { - c, exists := s.childs.GetEntry(n) - // these keys should aways exist, so for now we do not catch the non existing key case - if exists && !c.ShouldDelete() { - // if not all the keys are marked for deletion, we need to revert to regular deletion - doAggregateDelete = false - break - } - } - // if aggregate delet is possible do it - if doAggregateDelete { - // by adding the key path to the deletes - deletes = append(deletes, s) - } else { - // otherwise continue with deletion on the childs. - for _, c := range s.childs.GetAll() { - deletes, err = c.GetDeletes(deletes, aggregatePaths) - if err != nil { - return nil, err - } - } - } - return deletes, nil - } - return s.getRegularDeletes(deletes, aggregatePaths) -} - // canDelete checks if the entry can be Deleted. // This is e.g. to cover e.g. defaults and running. They can be deleted, but should not, they are basically implicitly existing. // In caomparison to @@ -515,40 +377,6 @@ func (s *sharedEntryAttributes) RemainsToExist() bool { return remains } -// getRegularDeletes performs deletion calculation on elements that have a schema attached. -func (s *sharedEntryAttributes) getRegularDeletes(deletes types.DeleteEntriesList, aggregate bool) (types.DeleteEntriesList, error) { - var err error - - if s.ShouldDelete() && !s.IsRoot() && len(ops.GetSchemaKeys(s)) == 0 { - return append(deletes, s), nil - } - - for _, elem := range s.choicesResolvers.GetDeletes() { - deletes = append(deletes, types.NewDeleteEntryImpl(s.SdcpbPath().CopyPathAddElem(sdcpb.NewPathElem(elem, nil)))) - } - - for _, e := range s.childs.GetAll() { - deletes, err = e.GetDeletes(deletes, aggregate) - if err != nil { - return nil, err - } - } - return deletes, nil -} - -// GetDeletes calculate the deletes that need to be send to the device. -func (s *sharedEntryAttributes) GetDeletes(deletes types.DeleteEntriesList, aggregatePaths bool) (types.DeleteEntriesList, error) { - - // if the actual level has no schema assigned we're on a key level - // element. Hence we try deletion via aggregation - if s.schema == nil && aggregatePaths { - return s.GetAggregatedDeletes(deletes, aggregatePaths) - } - - // else perform regular deletion - return s.getRegularDeletes(deletes, aggregatePaths) -} - // PathName returns the name of the Entry func (s *sharedEntryAttributes) PathName() string { return s.pathElemName @@ -573,6 +401,10 @@ func (s *sharedEntryAttributes) AddChild(ctx context.Context, e api.Entry) error return nil } +func (s *sharedEntryAttributes) ChoicesResolvers() api.ChoiceResolvers { + return s.choicesResolvers +} + func (s *sharedEntryAttributes) NavigateSdcpbPath(ctx context.Context, path *sdcpb.Path) (api.Entry, error) { pathElems := path.GetElem() var err error @@ -655,54 +487,6 @@ func (s *sharedEntryAttributes) DeleteCanDeleteChilds(keepDefault bool) { } } -// GetHighestPrecedence goes through the whole branch and returns the new and updated cache.Updates. -// These are the updated that will be send to the device. -func (s *sharedEntryAttributes) GetHighestPrecedence(result api.LeafVariantSlice, onlyNewOrUpdated bool, includeDefaults bool, includeExplicitDelete bool) api.LeafVariantSlice { - // get the highes precedence LeafeVariant and add it to the list - lv := s.leafVariants.GetHighestPrecedence(onlyNewOrUpdated, includeDefaults, includeExplicitDelete) - if lv != nil { - result = append(result, lv) - } - - // continue with childs. Childs are part of choices, process only the "active" (highes precedence) childs - for _, c := range s.GetChilds(types.DescendMethodActiveChilds) { - result = c.GetHighestPrecedence(result, onlyNewOrUpdated, includeDefaults, includeExplicitDelete) - } - return result -} - -func (s *sharedEntryAttributes) GetHighestPrecedenceLeafValue(ctx context.Context) (*api.LeafEntry, error) { - for _, x := range []string{"existing", "default"} { - lv := s.leafVariants.GetHighestPrecedence(false, true, false) - if lv != nil { - return lv, nil - } - if x != "default" { - _, err := s.tryLoadingDefault(ctx, s.SdcpbPath()) - if err != nil { - return nil, err - } - } - } - return nil, fmt.Errorf("error no value present for %s", s.SdcpbPath().ToXPath(false)) -} - -// getHighestPrecedenceValueOfBranch goes through all the child branches to find the highest -// precedence value (lowest priority value) for the entire branch and returns it. -func (s *sharedEntryAttributes) GetHighestPrecedenceValueOfBranch(filter api.HighestPrecedenceFilter) int32 { - result := int32(math.MaxInt32) - for _, e := range s.childs.GetAll() { - if val := e.GetHighestPrecedenceValueOfBranch(filter); val < result { - result = val - } - } - if val := s.leafVariants.GetHighestPrecedenceValue(filter); val < result { - result = val - } - - return result -} - // initChoiceCasesResolvers Choices and their cases are defined in the schema. // We need the information on which choices exist and what the below cases are. // Therefore the choiceCasesResolvers are initialized with the information. @@ -720,9 +504,12 @@ func (s *sharedEntryAttributes) initChoiceCasesResolvers() { case *sdcpb.SchemaElem_Container: ci = s.schema.GetContainer().GetChoiceInfo() } - + // // no choice info present + // if ci == nil { + // return + // } // create a new choiceCasesResolvers struct - choicesResolvers := choiceResolvers{} + choicesResolvers := api.ChoiceResolvers{} // iterate through choices defined in schema for choiceName, choice := range ci.GetChoice() { @@ -789,7 +576,7 @@ func (s *sharedEntryAttributes) populateChoiceCaseResolvers(_ context.Context) e child, childExists := s.childs.GetEntry(elem) // set the value from the tree as well if childExists { - valWDeleted := child.GetHighestPrecedenceValueOfBranch(api.HighestPrecedenceFilterAll) + valWDeleted := ops.GetHighestPrecedenceValueOfBranch(child, api.HighestPrecedenceFilterAll) if valWDeleted <= highestWDeleted { highestWDeleted = valWDeleted if child.CanDelete() { @@ -797,11 +584,11 @@ func (s *sharedEntryAttributes) populateChoiceCaseResolvers(_ context.Context) e } } - valWODeleted := child.GetHighestPrecedenceValueOfBranch(api.HighestPrecedenceFilterWithoutDeleted) + valWODeleted := ops.GetHighestPrecedenceValueOfBranch(child, api.HighestPrecedenceFilterWithoutDeleted) if valWODeleted <= highestWODeleted { highestWODeleted = valWODeleted } - valWONew := child.GetHighestPrecedenceValueOfBranch(api.HighestPrecedenceFilterWithoutNew) + valWONew := ops.GetHighestPrecedenceValueOfBranch(child, api.HighestPrecedenceFilterWithoutNew) if valWONew <= highestWONew { highestWONew = valWONew } @@ -813,10 +600,6 @@ func (s *sharedEntryAttributes) populateChoiceCaseResolvers(_ context.Context) e return nil } -func (s *sharedEntryAttributes) GetChild(name string) (api.Entry, bool) { - return s.childs.GetEntry(name) -} - func (s *sharedEntryAttributes) GetChilds(d types.DescendMethod) api.EntryMap { if s.schema == nil { return s.childs.GetAll() diff --git a/pkg/tree/sharedEntryAttributes_test.go b/pkg/tree/sharedEntryAttributes_test.go index 00cc77e9..09e63cde 100644 --- a/pkg/tree/sharedEntryAttributes_test.go +++ b/pkg/tree/sharedEntryAttributes_test.go @@ -103,7 +103,7 @@ func Test_sharedEntryAttributes_DeepCopy(t *testing.T) { e = &sharedEntryAttributes{ pathElemName: "__root__", childs: api.NewChildMap(), - choicesResolvers: choiceResolvers{}, + choicesResolvers: api.ChoiceResolvers{}, parent: nil, treeContext: tc, leafVariants: api.NewLeafVariants(tc, e), @@ -398,7 +398,7 @@ func Test_sharedEntryAttributes_GetListChilds(t *testing.T) { t.Error(err) return } - got, err := e.GetListChilds() + got, err := ops.GetListChilds(e) if (err != nil) != tt.wantErr { t.Errorf("sharedEntryAttributes.GetListChilds() error = %v, wantErr %v", err, tt.wantErr) return