From 009ff8d766929fc5c58d23b067e0bbf5b4b71eb9 Mon Sep 17 00:00:00 2001 From: steiler Date: Mon, 20 Apr 2026 11:48:22 +0200 Subject: [PATCH 1/2] get intent in xml format --- go.mod | 4 +- go.sum | 2 - pkg/datastore/intent_rpc.go | 1 + pkg/server/intent.go | 16 +++ pkg/tree/api/adapter/intentresponse.go | 4 + pkg/tree/ops/xpath.go | 23 ++++ pkg/tree/ops/xpath_test.go | 140 +++++++++++++++++++++++++ 7 files changed, 187 insertions(+), 3 deletions(-) create mode 100644 pkg/tree/ops/xpath.go create mode 100644 pkg/tree/ops/xpath_test.go diff --git a/go.mod b/go.mod index 9691778c..afc2593c 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,8 @@ go 1.24.0 replace github.com/openconfig/goyang v1.6.0 => github.com/sdcio/goyang v1.6.2-2 +replace github.com/sdcio/sdc-protos => /home/mava/projects/sdc-protos + require ( github.com/AlekSi/pointer v1.2.0 github.com/beevik/etree v1.6.0 @@ -24,7 +26,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.51 + github.com/sdcio/sdc-protos v0.0.52-0.20260420093658-100270c40f0c 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 ab43f6d6..d33436b2 100644 --- a/go.sum +++ b/go.sum @@ -193,8 +193,6 @@ 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.51 h1:sFc2ct8v4D7rBgFdg/fmXgJffcFhupfx4QJKsSAoolA= -github.com/sdcio/sdc-protos v0.0.51/go.mod h1:FkJMZWtp7Rcc/EedbX2mt1tET/j8KdavNl2BsHf03+o= 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/datastore/intent_rpc.go b/pkg/datastore/intent_rpc.go index 04377fae..974384d5 100644 --- a/pkg/datastore/intent_rpc.go +++ b/pkg/datastore/intent_rpc.go @@ -127,4 +127,5 @@ type GetIntentResponse interface { ToJsonIETF(ctx context.Context) (any, error) ToXML(ctx context.Context) (*etree.Document, error) ToProtoUpdates(ctx context.Context) ([]*sdcpb.Update, error) + ToXPath(ctx context.Context) (*sdcpb.PathValues, error) } diff --git a/pkg/server/intent.go b/pkg/server/intent.go index ffd25e5e..45f7c56c 100644 --- a/pkg/server/intent.go +++ b/pkg/server/intent.go @@ -10,6 +10,7 @@ import ( sdcpb "github.com/sdcio/sdc-protos/sdcpb" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/encoding/protojson" ) func (s *Server) ListIntent(ctx context.Context, req *sdcpb.ListIntentRequest) (*sdcpb.ListIntentResponse, error) { @@ -82,6 +83,20 @@ func (s *Server) GetIntent(ctx context.Context, req *sdcpb.GetIntentRequest) (*s } switch req.GetFormat() { + case sdcpb.Format_Intent_Format_XPATH: + j, err := rsp.ToXPath(ctx) + if err != nil { + return nil, err + } + + b, err := protojson.Marshal(j) + if err != nil { + return nil, err + } + getIntentResponse.Intent = &sdcpb.GetIntentResponse_Blob{ + Blob: b, + } + return getIntentResponse, nil case sdcpb.Format_Intent_Format_JSON, sdcpb.Format_Intent_Format_JSON_IETF: var j any switch req.GetFormat() { @@ -95,6 +110,7 @@ func (s *Server) GetIntent(ctx context.Context, req *sdcpb.GetIntentRequest) (*s if err != nil { return nil, err } + } b, err := json.Marshal(j) if err != nil { diff --git a/pkg/tree/api/adapter/intentresponse.go b/pkg/tree/api/adapter/intentresponse.go index dcf26e67..e221c752 100644 --- a/pkg/tree/api/adapter/intentresponse.go +++ b/pkg/tree/api/adapter/intentresponse.go @@ -57,3 +57,7 @@ func (t *IntentResponseAdapter) ToProtoUpdates(ctx context.Context) ([]*sdcpb.Up func (t *IntentResponseAdapter) ToProtoDeletes(ctx context.Context) ([]*sdcpb.Path, error) { return ops.ToProtoDeletes(ctx, t.Entry) } + +func (t *IntentResponseAdapter) ToXPath(ctx context.Context) (*sdcpb.PathValues, error) { + return ops.ToXPath(ctx, t.Entry, false, false) +} diff --git a/pkg/tree/ops/xpath.go b/pkg/tree/ops/xpath.go new file mode 100644 index 00000000..344e61d4 --- /dev/null +++ b/pkg/tree/ops/xpath.go @@ -0,0 +1,23 @@ +package ops + +import ( + "context" + + "github.com/sdcio/data-server/pkg/tree/api" + "github.com/sdcio/sdc-protos/sdcpb" +) + +// ToXPath converts the branch under e to a list of XPath entries. It returns only the highest precedence value for each path. +func ToXPath(_ context.Context, e api.Entry, onlyNewOrUpdated, includeDefaults bool) (*sdcpb.PathValues, error) { + lvs := GetHighestPrecedence(e, onlyNewOrUpdated, includeDefaults, false) + + xpaths := make([]*sdcpb.PathValue, 0, len(lvs)) + + for _, lv := range lvs { + xpaths = append(xpaths, &sdcpb.PathValue{ + Path: lv.SdcpbPath(), + Value: lv.Value(), + }) + } + return &sdcpb.PathValues{PathValues: xpaths}, nil +} diff --git a/pkg/tree/ops/xpath_test.go b/pkg/tree/ops/xpath_test.go new file mode 100644 index 00000000..7599f1e4 --- /dev/null +++ b/pkg/tree/ops/xpath_test.go @@ -0,0 +1,140 @@ +package ops_test + +import ( + "context" + "runtime" + "testing" + + "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/utils/testhelper" + "go.uber.org/mock/gomock" +) + +func TestToXPath_FromConfig1(t *testing.T) { + ctx := context.Background() + + 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) + } + + _, err = testhelper.LoadYgotStructIntoTreeRoot(ctx, testhelper.Config1(), root.Entry, "owner1", 5, false, testhelper.FlagsNew) + if err != nil { + t.Fatal(err) + } + + err = root.FinishInsertionPhase(ctx) + if err != nil { + t.Fatal(err) + } + + pvs, err := ops.ToXPath(ctx, root.Entry, false, false) + if err != nil { + t.Fatal(err) + } + + if len(pvs.GetPathValues()) == 0 { + t.Fatal("ToXPath() returned no path values") + } + + got := make(map[string]string, len(pvs.GetPathValues())) + for _, pv := range pvs.GetPathValues() { + if pv.GetPath() == nil { + t.Fatal("ToXPath() returned a path value with nil path") + } + if pv.GetValue() == nil { + t.Fatalf("ToXPath() returned nil value for path %s", pv.GetPath().ToXPath(false)) + } + + xPath := pv.GetPath().ToXPath(false) + if strVal := pv.GetValue().GetStringVal(); strVal != "" { + got[xPath] = strVal + continue + } + got[xPath] = pv.GetValue().String() + } + + if got["/interface[name=ethernet-1/1]/description"] != "Foo" { + t.Fatalf("missing or mismatching interface description, got %q", got["/interface[name=ethernet-1/1]/description"]) + } + + if got["/network-instance[name=default]/description"] != "Default NI" { + t.Fatalf("missing or mismatching network-instance description, got %q", got["/network-instance[name=default]/description"]) + } + + if got["/patterntest"] != "hallo 00" { + t.Fatalf("missing or mismatching patterntest, got %q", got["/patterntest"]) + } +} + +func TestToXPath_OnlyNewOrUpdated_WithSameNewAndExistingConfig1(t *testing.T) { + ctx := context.Background() + + 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) + } + + owner := "owner1" + prio := int32(5) + + _, err = testhelper.LoadYgotStructIntoTreeRoot(ctx, testhelper.Config1(), root.Entry, owner, prio, false, testhelper.FlagsExisting) + if err != nil { + t.Fatal(err) + } + + _, err = testhelper.LoadYgotStructIntoTreeRoot(ctx, testhelper.Config1(), root.Entry, owner, prio, false, testhelper.FlagsNew) + if err != nil { + t.Fatal(err) + } + + err = root.FinishInsertionPhase(ctx) + if err != nil { + t.Fatal(err) + } + + onlyNewOrUpdated, err := ops.ToXPath(ctx, root.Entry, true, false) + if err != nil { + t.Fatal(err) + } + + allValues, err := ops.ToXPath(ctx, root.Entry, false, false) + if err != nil { + t.Fatal(err) + } + + if got, want := len(onlyNewOrUpdated.GetPathValues()), len(allValues.GetPathValues()); got != want { + t.Fatalf("ToXPath() onlyNewOrUpdated count mismatch: got %d, want %d", got, want) + } + + containsPatternTest := false + for _, pv := range onlyNewOrUpdated.GetPathValues() { + if pv.GetPath() != nil && pv.GetPath().ToXPath(false) == "/patterntest" { + containsPatternTest = true + break + } + } + if !containsPatternTest { + t.Fatal("ToXPath() did not include expected /patterntest path") + } +} \ No newline at end of file From 2a8fb9897c2c32389a0a470351e6a749380ffb4a Mon Sep 17 00:00:00 2001 From: steiler Date: Mon, 20 Apr 2026 11:51:22 +0200 Subject: [PATCH 2/2] remove replace --- go.mod | 2 -- go.sum | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index afc2593c..fff4cbf1 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,6 @@ go 1.24.0 replace github.com/openconfig/goyang v1.6.0 => github.com/sdcio/goyang v1.6.2-2 -replace github.com/sdcio/sdc-protos => /home/mava/projects/sdc-protos - require ( github.com/AlekSi/pointer v1.2.0 github.com/beevik/etree v1.6.0 diff --git a/go.sum b/go.sum index d33436b2..725f99a3 100644 --- a/go.sum +++ b/go.sum @@ -193,6 +193,8 @@ 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.52-0.20260420093658-100270c40f0c h1:N6iwnjWWfv00tOC02XkpTH9G4AMBX83UxVe+Z0taGA4= +github.com/sdcio/sdc-protos v0.0.52-0.20260420093658-100270c40f0c/go.mod h1:FkJMZWtp7Rcc/EedbX2mt1tET/j8KdavNl2BsHf03+o= 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=