diff --git a/Makefile b/Makefile index 619cbafd..0ca24070 100644 --- a/Makefile +++ b/Makefile @@ -62,7 +62,6 @@ mocks-gen: mocks-rm ## Generate mocks for all the defined interfaces. mockgen -package=mockschemaclientbound -source=pkg/datastore/clients/schema/schemaClientBound.go -destination=$(MOCKDIR)/mockschemaclientbound/client.go mockgen -package=mockcacheclient -source=pkg/cache/cache.go -destination=$(MOCKDIR)/mockcacheclient/client.go mockgen -package=mocktarget -source=pkg/datastore/target/target.go -destination=$(MOCKDIR)/mocktarget/target.go - mockgen -package=mockvalidationclient -source=pkg/datastore/clients/validationClient.go -destination=$(MOCKDIR)/mockvalidationclient/client.go mockgen -package=mockTreeEntry -source=pkg/tree/entry.go -destination=$(MOCKDIR)/mocktreeentry/entry.go .PHONY: mocks-rm @@ -88,4 +87,13 @@ format_yang: .PHONY: goreleaser-nightly goreleaser-nightly: go install github.com/goreleaser/goreleaser/v2@latest - goreleaser release --clean -f .goreleaser.nightlies.yml --skip=validate \ No newline at end of file + goreleaser release --clean -f .goreleaser.nightlies.yml --skip=validate + +.PHONY: proto +CACHE_OUT_DIR := $(CURDIR)/pkg/tree/tree_persist + +proto: + clang-format -i -style=file:$(CURDIR)/proto/clang-format.style $(CURDIR)/proto/tree_persist.proto + + mkdir -p $(CACHE_OUT_DIR) + protoc --go_out=$(CACHE_OUT_DIR) --go-grpc_out=$(CACHE_OUT_DIR) -I $(CURDIR)/proto $(CURDIR)/proto/tree_persist.proto \ No newline at end of file diff --git a/client/cmd/data.go b/client/cmd/data.go index 39c477e3..6aeabc27 100644 --- a/client/cmd/data.go +++ b/client/cmd/data.go @@ -26,7 +26,5 @@ var dataCmd = &cobra.Command{ func init() { rootCmd.AddCommand(dataCmd) - - dataCmd.PersistentFlags().StringVarP(&candidate, "candidate", "", "", "datastore (candidate) name") dataCmd.PersistentFlags().StringVarP(&datastoreName, "ds", "", "", "datastore target name") } diff --git a/client/cmd/data_diff.go b/client/cmd/data_diff.go deleted file mode 100644 index 9fd010b4..00000000 --- a/client/cmd/data_diff.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2024 Nokia -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cmd - -import ( - "context" - "fmt" - - sdcpb "github.com/sdcio/sdc-protos/sdcpb" - "github.com/spf13/cobra" - "google.golang.org/protobuf/encoding/prototext" - - "github.com/sdcio/data-server/pkg/utils" -) - -// dataDiffCmd represents the diff command -var dataDiffCmd = &cobra.Command{ - Use: "diff", - Short: "diff candidate and its baseline", - SilenceUsage: true, - RunE: func(cmd *cobra.Command, _ []string) error { - ctx, cancel := context.WithCancel(cmd.Context()) - defer cancel() - dataClient, err := createDataClient(ctx, addr) - if err != nil { - return err - } - req := &sdcpb.DiffRequest{ - Name: datastoreName, - Datastore: &sdcpb.DataStore{ - Type: sdcpb.Type_CANDIDATE, - Name: candidate, - }, - } - fmt.Println("request:") - fmt.Println(prototext.Format(req)) - rsp, err := dataClient.Diff(ctx, req) - if err != nil { - return err - } - fmt.Println("response:") - fmt.Println(prototext.Format(rsp)) - printDiffResponse(rsp) - return nil - }, -} - -func init() { - dataCmd.AddCommand(dataDiffCmd) -} - -func printDiffResponse(rsp *sdcpb.DiffResponse) { - if rsp == nil { - return - } - prefix := fmt.Sprintf("%s: %s/%s/%d:", rsp.GetName(), rsp.GetDatastore().GetName(), rsp.GetDatastore().GetOwner(), rsp.GetDatastore().GetPriority()) - for _, diff := range rsp.GetDiff() { - p := utils.ToXPath(diff.GetPath(), false) - fmt.Printf("%s\n\tpath=%s:\n\tmain->candidate: %s -> %s\n", - prefix, p, - diff.GetMainValue(), diff.GetCandidateValue()) - } -} diff --git a/client/cmd/data_get.go b/client/cmd/data_get.go deleted file mode 100644 index 887dc4bb..00000000 --- a/client/cmd/data_get.go +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright 2024 Nokia -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cmd - -import ( - "context" - "encoding/json" - "fmt" - "strings" - - "github.com/sdcio/schema-server/pkg/utils" - sdcpb "github.com/sdcio/sdc-protos/sdcpb" - "github.com/spf13/cobra" - "google.golang.org/protobuf/encoding/prototext" -) - -var paths []string -var dataType string -var format string -var intended bool -var encoding string - -// dataGetCmd represents the get command -var dataGetCmd = &cobra.Command{ - Use: "get", - Short: "get data", - SilenceUsage: true, - RunE: func(cmd *cobra.Command, _ []string) error { - if candidate != "" && intended { - return fmt.Errorf("cannot set a candidate name and intended store at the same time") - } - var dt sdcpb.DataType - switch dataType { - case "ALL": - case "CONFIG": - dt = sdcpb.DataType_CONFIG - case "STATE": - dt = sdcpb.DataType_STATE - default: - return fmt.Errorf("invalid flag value --type %s", dataType) - } - - var enc sdcpb.Encoding - switch encoding { - case "STRING": - enc = sdcpb.Encoding_STRING - case "JSON": - enc = sdcpb.Encoding_JSON - case "JSON_IETF": - enc = sdcpb.Encoding_JSON_IETF - case "PROTO": - enc = sdcpb.Encoding_PROTO - } - - req := &sdcpb.GetDataRequest{ - Name: datastoreName, - DataType: dt, - Encoding: enc, - } - for _, p := range paths { - xp, err := utils.ParsePath(p) - if err != nil { - return err - } - req.Path = append(req.Path, xp) - } - if candidate != "" { - req.Datastore = &sdcpb.DataStore{ - Type: sdcpb.Type_CANDIDATE, - Name: candidate, - } - } - if intended { - req.Datastore = &sdcpb.DataStore{ - Type: sdcpb.Type_INTENDED, - Owner: owner, - Priority: priority, - } - } - ctx, cancel := context.WithCancel(cmd.Context()) - defer cancel() - dataClient, err := createDataClient(ctx, addr) - if err != nil { - return err - } - fmt.Println("request:") - fmt.Println(prototext.Format(req)) - stream, err := dataClient.GetData(ctx, req) - if err != nil { - return err - } - count := 0 - for { - rsp, err := stream.Recv() - if err != nil { - if strings.Contains(err.Error(), "EOF") { - break - } - return err - } - count++ - switch format { - case "json": - b, err := json.MarshalIndent(rsp, "", " ") - if err != nil { - return err - } - fmt.Println(string(b)) - case "flat": - for _, n := range rsp.GetNotification() { - for _, upd := range n.GetUpdate() { - p := utils.ToXPath(upd.GetPath(), false) - // upd.GetValue() - fmt.Printf("%s: %s\n", p, upd.GetValue()) - } - } - - default: - fmt.Println(prototext.Format(rsp)) - } - - } - - fmt.Println("num notifications:", count) - return nil - }, -} - -func init() { - dataCmd.AddCommand(dataGetCmd) - dataGetCmd.Flags().StringArrayVarP(&paths, "path", "", []string{}, "get path(s)") - dataGetCmd.Flags().StringVarP(&dataType, "type", "", "ALL", "data type, one of: ALL, CONFIG, STATE") - dataGetCmd.Flags().StringVarP(&encoding, "encoding", "", "STRING", "encoding of the returned data: STRING, JSON, JSON_IETF or PROTO") - - // intended store - dataGetCmd.Flags().BoolVarP(&intended, "intended", "", false, "get data from intended store") - dataGetCmd.Flags().StringVarP(&owner, "owner", "", "", "intended store owner to query") - dataGetCmd.Flags().Int32VarP(&priority, "priority", "", 0, "intended store priority") -} diff --git a/client/cmd/data_getIntent.go b/client/cmd/data_getIntent.go index 329a7ad7..f88636b0 100644 --- a/client/cmd/data_getIntent.go +++ b/client/cmd/data_getIntent.go @@ -32,9 +32,9 @@ var dataGetIntentCmd = &cobra.Command{ SilenceUsage: true, RunE: func(cmd *cobra.Command, _ []string) error { req := &sdcpb.GetIntentRequest{ - Name: datastoreName, - Intent: intentName, - Priority: priority, + DatastoreName: datastoreName, + Intent: intentName, + Format: sdcpb.Format_Intent_Format_JSON, } ctx, cancel := context.WithCancel(cmd.Context()) defer cancel() diff --git a/client/cmd/data_listIntent.go b/client/cmd/data_listIntent.go index c1bfc346..01ca1bb9 100644 --- a/client/cmd/data_listIntent.go +++ b/client/cmd/data_listIntent.go @@ -30,7 +30,7 @@ var dataListIntentCmd = &cobra.Command{ SilenceUsage: true, RunE: func(cmd *cobra.Command, _ []string) error { req := &sdcpb.ListIntentRequest{ - Name: datastoreName, + DatastoreName: datastoreName, } ctx, cancel := context.WithCancel(cmd.Context()) defer cancel() diff --git a/client/cmd/data_setIntent.go b/client/cmd/data_setIntent.go deleted file mode 100644 index ee1d540f..00000000 --- a/client/cmd/data_setIntent.go +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2024 Nokia -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cmd - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "os" - - sdcpb "github.com/sdcio/sdc-protos/sdcpb" - "github.com/spf13/cobra" - "google.golang.org/protobuf/encoding/prototext" - - "github.com/sdcio/data-server/pkg/utils" -) - -var deleteFlag bool -var intentDefinition string - -// dataSetIntentCmd represents the set-intent command -var dataSetIntentCmd = &cobra.Command{ - Use: "set-intent", - Short: "set intent data", - SilenceUsage: true, - RunE: func(cmd *cobra.Command, _ []string) error { - if deleteFlag && intentDefinition != "" { - return errors.New("cannot set an intent body and the delete flag at the same time") - } - req := &sdcpb.SetIntentRequest{ - Name: datastoreName, - Intent: intentName, - Priority: priority, - Update: make([]*sdcpb.Update, 0), - } - if deleteFlag { - req.Delete = true - } - if intentDefinition != "" { - b, err := os.ReadFile(intentDefinition) - if err != nil { - return err - } - intentDefs := make([]*intentDef, 0) - err = json.Unmarshal(b, &intentDefs) - if err != nil { - return err - } - for _, idef := range intentDefs { - p, err := utils.ParsePath(idef.Path) - if err != nil { - return err - } - bb, err := json.Marshal(idef.Value) - if err != nil { - return err - } - req.Update = append(req.Update, &sdcpb.Update{ - Path: p, - Value: &sdcpb.TypedValue{ - Value: &sdcpb.TypedValue_JsonVal{JsonVal: bb}, - }, - }) - } - } - ctx, cancel := context.WithCancel(cmd.Context()) - defer cancel() - dataClient, err := createDataClient(ctx, addr) - if err != nil { - return err - } - fmt.Println("request:") - fmt.Println(prototext.Format(req)) - rsp, err := dataClient.SetIntent(ctx, req) - if err != nil { - return err - } - fmt.Println("response:") - fmt.Println(prototext.Format(rsp)) - return nil - }, -} - -func init() { - dataCmd.AddCommand(dataSetIntentCmd) - dataSetIntentCmd.Flags().StringVarP(&intentName, "intent", "", "", "intent name") - dataSetIntentCmd.Flags().StringVarP(&intentDefinition, "body", "", "", "intent body") - dataSetIntentCmd.Flags().Int32VarP(&priority, "priority", "", 0, "intent priority") - dataSetIntentCmd.Flags().BoolVarP(&deleteFlag, "delete", "", false, "delete intent") -} - -type intentDef struct { - Path string `json:"path,omitempty"` - Value any `json:"value,omitempty"` -} diff --git a/client/cmd/data_subscribe.go b/client/cmd/data_subscribe.go deleted file mode 100644 index 988d246f..00000000 --- a/client/cmd/data_subscribe.go +++ /dev/null @@ -1,192 +0,0 @@ -// Copyright 2024 Nokia -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cmd - -import ( - "context" - "encoding/json" - "fmt" - "os" - "strings" - "time" - - "github.com/sdcio/schema-server/pkg/utils" - sdcpb "github.com/sdcio/sdc-protos/sdcpb" - "github.com/spf13/cobra" - "google.golang.org/protobuf/encoding/prototext" - "gopkg.in/yaml.v2" -) - -var sampleInterval time.Duration -var subscriptionFile string - -// dataSubscribeCmd represents the subscribe command -var dataSubscribeCmd = &cobra.Command{ - Use: "subscribe", - Aliases: []string{"sub"}, - Short: "subscribe to data", - SilenceUsage: true, - RunE: func(cmd *cobra.Command, _ []string) error { - var err error - var req *sdcpb.SubscribeRequest - if subscriptionFile != "" { - req, err = subscribeRequestFromFile(subscriptionFile) - if err != nil { - return err - } - } else { - var dt sdcpb.DataType - switch strings.ToUpper(dataType) { - case "ALL": - case "CONFIG": - dt = sdcpb.DataType_CONFIG - case "STATE": - dt = sdcpb.DataType_STATE - default: - return fmt.Errorf("invalid flag value --type %s", dataType) - } - - xps := make([]*sdcpb.Path, 0, len(paths)) - for _, p := range paths { - xp, err := utils.ParsePath(p) - if err != nil { - return err - } - xps = append(xps, xp) - } - - req = &sdcpb.SubscribeRequest{ - Name: datastoreName, - Subscription: []*sdcpb.Subscription{ - { - Path: xps, - SampleInterval: uint64(sampleInterval), - DataType: dt, - }, - }, - } - } - fmt.Println("request:") - fmt.Println(prototext.Format(req)) - - ctx, cancel := context.WithCancel(cmd.Context()) - defer cancel() - dataClient, err := createDataClient(ctx, addr) - if err != nil { - return err - } - stream, err := dataClient.Subscribe(ctx, req) - if err != nil { - return err - } - fmt.Println("responses:") - count := 0 - for { - rsp, err := stream.Recv() - if err != nil { - if strings.Contains(err.Error(), "EOF") { - break - } - return err - } - count++ - switch format { - case "json": - b, err := json.MarshalIndent(rsp, "", " ") - if err != nil { - return err - } - fmt.Println(string(b)) - case "flat": - for _, upd := range rsp.GetUpdate().GetUpdate() { - p := utils.ToXPath(upd.GetPath(), false) - fmt.Printf("%s: %s\n", p, upd.GetValue()) - } - - default: - fmt.Println(prototext.Format(rsp)) - } - } - - fmt.Println("num notifications:", count) - return nil - }, -} - -func init() { - dataCmd.AddCommand(dataSubscribeCmd) - dataSubscribeCmd.Flags().StringArrayVarP(&paths, "path", "", []string{}, "get path(s)") - dataSubscribeCmd.Flags().DurationVarP(&sampleInterval, "sample-interval", "", 10*time.Second, "sample interval") - dataSubscribeCmd.Flags().StringVarP(&dataType, "type", "", "ALL", "data type, one of: ALL, CONFIG, STATE") - dataSubscribeCmd.Flags().StringVarP(&subscriptionFile, "file", "", "", "file with a subscription definition") - dataSubscribeCmd.Flags().StringVarP(&format, "format", "", "", "print format, '', 'flat' or 'json'") -} - -type subscribeDefinition struct { - Subscription []*subscription `yaml:"subscription,omitempty"` -} - -type subscription struct { - Paths []string `yaml:"paths,omitempty"` - SampleInterval time.Duration `yaml:"sample-interval,omitempty"` - DataType string `yaml:"data-type,omitempty"` - SuppressRedundant bool `yaml:"suppress-redundant,omitempty"` -} - -func subscribeRequestFromFile(file string) (*sdcpb.SubscribeRequest, error) { - b, err := os.ReadFile(subscriptionFile) - if err != nil { - return nil, err - } - subDef := new(subscribeDefinition) - err = yaml.Unmarshal(b, subDef) - if err != nil { - return nil, err - } - req := &sdcpb.SubscribeRequest{ - Name: datastoreName, - Subscription: make([]*sdcpb.Subscription, 0, len(subDef.Subscription)), - } - for _, subcd := range subDef.Subscription { - if subcd.DataType == "" { - subcd.DataType = "ALL" - } - var dt sdcpb.DataType - switch strings.ToUpper(subcd.DataType) { - case "ALL": - case "CONFIG": - dt = sdcpb.DataType_CONFIG - case "STATE": - dt = sdcpb.DataType_STATE - default: - return nil, fmt.Errorf("invalid data type %s", subcd.DataType) - } - subsc := &sdcpb.Subscription{ - Path: make([]*sdcpb.Path, 0, len(subcd.Paths)), - DataType: dt, - SampleInterval: uint64(subcd.SampleInterval), - SuppressRedundant: subcd.SuppressRedundant, - } - for _, p := range subcd.Paths { - xp, err := utils.ParsePath(p) - if err != nil { - return nil, err - } - subsc.Path = append(subsc.Path, xp) - } - req.Subscription = append(req.Subscription, subsc) - } - return req, nil -} diff --git a/client/cmd/datastore.go b/client/cmd/datastore.go index ec189b8c..dcd34936 100644 --- a/client/cmd/datastore.go +++ b/client/cmd/datastore.go @@ -29,5 +29,4 @@ var datastoreCmd = &cobra.Command{ func init() { rootCmd.AddCommand(datastoreCmd) datastoreCmd.PersistentFlags().StringVarP(&datastoreName, "ds", "", "", "datastore (main) name") - datastoreCmd.PersistentFlags().StringVarP(&candidate, "candidate", "", "", "datastore candidate name") } diff --git a/client/cmd/datastore_commit.go b/client/cmd/datastore_commit.go deleted file mode 100644 index 47b15d06..00000000 --- a/client/cmd/datastore_commit.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2024 Nokia -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cmd - -import ( - "context" - "fmt" - - sdcpb "github.com/sdcio/sdc-protos/sdcpb" - "github.com/spf13/cobra" - "google.golang.org/protobuf/encoding/prototext" -) - -// datastoreCommitCmd represents the commit command -var datastoreCommitCmd = &cobra.Command{ - Use: "commit", - Short: "commit candidate datastore to target", - SilenceUsage: true, - RunE: func(cmd *cobra.Command, _ []string) error { - ctx, cancel := context.WithCancel(cmd.Context()) - defer cancel() - dataClient, err := createDataClient(ctx, addr) - if err != nil { - return err - } - req := &sdcpb.CommitRequest{ - Name: datastoreName, - Datastore: &sdcpb.DataStore{ - Type: sdcpb.Type_CANDIDATE, - Name: candidate, - }, - Rebase: rebase, - Stay: stay, - } - fmt.Println("request:") - fmt.Println(prototext.Format(req)) - rsp, err := dataClient.Commit(ctx, req) - if err != nil { - return err - } - fmt.Println("response:") - fmt.Println(prototext.Format(rsp)) - return nil - }, -} - -func init() { - datastoreCmd.AddCommand(datastoreCommitCmd) - - datastoreCommitCmd.Flags().BoolVarP(&rebase, "rebase", "", false, "rebase before commit") - datastoreCommitCmd.Flags().BoolVarP(&stay, "stay", "", false, "do not delete candidate after commit") -} - -var rebase, stay bool diff --git a/client/cmd/datastore_create.go b/client/cmd/datastore_create.go index fd07de23..c62fa104 100644 --- a/client/cmd/datastore_create.go +++ b/client/cmd/datastore_create.go @@ -20,16 +20,13 @@ import ( "fmt" "os" - "github.com/sdcio/data-server/pkg/datastore" sdcpb "github.com/sdcio/sdc-protos/sdcpb" "github.com/spf13/cobra" "google.golang.org/protobuf/encoding/prototext" ) -var candidate string var target string var syncFile string -var owner string var priority int32 // datastoreCreateCmd represents the create command @@ -57,7 +54,7 @@ var datastoreCreateCmd = &cobra.Command{ } } req := &sdcpb.CreateDataStoreRequest{ - Name: datastoreName, + DatastoreName: datastoreName, } if syncFile != "" { b, err := os.ReadFile(syncFile) @@ -72,25 +69,14 @@ var datastoreCreateCmd = &cobra.Command{ req.Sync = sync } - switch { - // create a candidate datastore - case candidate != "": - req.Datastore = &sdcpb.DataStore{ - Type: sdcpb.Type_CANDIDATE, - Name: candidate, - Owner: owner, - Priority: priority, - } - req.Target = tg - //create a main datastore - default: - req.Schema = &sdcpb.Schema{ - Name: schemaName, - Vendor: schemaVendor, - Version: schemaVersion, - } - req.Target = tg + //create a datastore + req.Schema = &sdcpb.Schema{ + Name: schemaName, + Vendor: schemaVendor, + Version: schemaVersion, } + req.Target = tg + fmt.Println("request:") fmt.Println(prototext.Format(req)) rsp, err := dataClient.CreateDataStore(ctx, req) @@ -108,7 +94,5 @@ func init() { datastoreCreateCmd.Flags().StringVarP(&target, "target", "", "", "target definition file") datastoreCreateCmd.Flags().StringVarP(&syncFile, "sync", "", "", "target sync definition file") - datastoreCreateCmd.Flags().StringVarP(&owner, "owner", "", datastore.DefaultOwner, "candidate owner") datastoreCreateCmd.Flags().Int32VarP(&priority, "priority", "", 1, "candidate priority") - } diff --git a/client/cmd/datastore_delete.go b/client/cmd/datastore_delete.go index 6419efa7..9914ab85 100644 --- a/client/cmd/datastore_delete.go +++ b/client/cmd/datastore_delete.go @@ -32,12 +32,6 @@ var datastoreDeleteCmd = &cobra.Command{ req := &sdcpb.DeleteDataStoreRequest{ Name: datastoreName, } - if candidate != "" { - req.Datastore = &sdcpb.DataStore{ - Type: sdcpb.Type_CANDIDATE, - Name: candidate, - } - } ctx, cancel := context.WithCancel(cmd.Context()) defer cancel() dataClient, err := createDataClient(ctx, addr) diff --git a/client/cmd/datastore_discard.go b/client/cmd/datastore_discard.go deleted file mode 100644 index f622d9f0..00000000 --- a/client/cmd/datastore_discard.go +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2024 Nokia -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cmd - -import ( - "context" - "fmt" - - schema_server "github.com/sdcio/sdc-protos/sdcpb" - "github.com/spf13/cobra" - "google.golang.org/protobuf/encoding/prototext" -) - -// datastoreDiscardCmd represents the discard command -var datastoreDiscardCmd = &cobra.Command{ - Use: "discard", - Short: "discard changes made to a candidate datastore", - SilenceUsage: true, - RunE: func(cmd *cobra.Command, _ []string) error { - ctx, cancel := context.WithCancel(cmd.Context()) - defer cancel() - dataClient, err := createDataClient(ctx, addr) - if err != nil { - return err - } - req := &schema_server.DiscardRequest{ - Name: datastoreName, - Datastore: &schema_server.DataStore{ - Type: schema_server.Type_CANDIDATE, - Name: candidate, - }, - Stay: stay, - } - fmt.Println("request:") - fmt.Println(prototext.Format(req)) - rsp, err := dataClient.Discard(ctx, req) - if err != nil { - return err - } - fmt.Println("response:") - fmt.Println(prototext.Format(rsp)) - return nil - }, -} - -func init() { - datastoreCmd.AddCommand(datastoreDiscardCmd) -} diff --git a/client/cmd/datastore_get.go b/client/cmd/datastore_get.go index 5904e3f4..01bcc4d5 100644 --- a/client/cmd/datastore_get.go +++ b/client/cmd/datastore_get.go @@ -19,8 +19,6 @@ import ( "encoding/json" "fmt" "os" - "strconv" - "strings" "github.com/olekukonko/tablewriter" sdcpb "github.com/sdcio/sdc-protos/sdcpb" @@ -41,7 +39,7 @@ var datastoreGetCmd = &cobra.Command{ return err } req := &sdcpb.GetDataStoreRequest{ - Name: datastoreName, + DatastoreName: datastoreName, } // fmt.Println("request:") // fmt.Println(prototext.Format(req)) @@ -72,7 +70,7 @@ func init() { func printDataStoreTable(rsp *sdcpb.GetDataStoreResponse) { table := tablewriter.NewWriter(os.Stdout) - table.SetHeader([]string{"Name", "Schema", "Protocol", "Address", "State", "Candidate (C/O/P)"}) + table.SetHeader([]string{"Name", "Schema", "Protocol", "Address", "State"}) table.SetAlignment(tablewriter.ALIGN_LEFT) table.SetAutoFormatHeaders(false) table.SetAutoWrapText(false) @@ -81,28 +79,13 @@ func printDataStoreTable(rsp *sdcpb.GetDataStoreResponse) { } func toTableData(rsp *sdcpb.GetDataStoreResponse) [][]string { - candidates := make([]string, 0, len(rsp.GetDatastore())) - for _, ds := range rsp.GetDatastore() { - if ds.GetType() == sdcpb.Type_MAIN { - continue - } - candidateName := "- " + ds.GetName() - if ds.Owner != "" { - candidateName += "/" + ds.Owner - } - if ds.Priority != 0 { - candidateName += "/" + strconv.Itoa(int(ds.Priority)) - } - candidates = append(candidates, candidateName) - } return [][]string{ { - rsp.GetName(), + rsp.GetDatastoreName(), fmt.Sprintf("%s/%s/%s", rsp.GetSchema().GetName(), rsp.GetSchema().GetVendor(), rsp.GetSchema().GetVersion()), rsp.GetTarget().GetType(), rsp.GetTarget().GetAddress(), rsp.GetTarget().GetStatus().String(), - strings.Join(candidates, "\n"), }, } } diff --git a/client/cmd/root.go b/client/cmd/root.go index 4f0ee773..69aa2b2e 100644 --- a/client/cmd/root.go +++ b/client/cmd/root.go @@ -28,6 +28,7 @@ import ( var schemaName string var schemaVendor string var schemaVersion string +var format string // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ diff --git a/go.mod b/go.mod index 54a408c0..1fc9047e 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,11 @@ go 1.23.4 toolchain go1.23.5 -replace github.com/openconfig/goyang v1.6.0 => github.com/sdcio/goyang v1.6.0-2 +replace github.com/openconfig/goyang v1.6.0 => /home/mava/projects/goyang + +replace github.com/sdcio/cache => /home/mava/projects/cachev2 + +replace github.com/sdcio/sdc-protos => /home/mava/projects/sdc-protos require ( github.com/AlekSi/pointer v1.2.0 @@ -15,7 +19,7 @@ require ( github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 github.com/jellydator/ttlcache/v3 v3.3.0 github.com/olekukonko/tablewriter v0.0.5 - github.com/openconfig/gnmi v0.13.0 + github.com/openconfig/gnmi v0.14.0 github.com/openconfig/gnmic/pkg/api v0.1.8 github.com/openconfig/gnmic/pkg/target v0.1.4 github.com/openconfig/gnmic/pkg/types v0.1.2 @@ -31,7 +35,6 @@ require ( github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.6 go.uber.org/mock v0.5.0 - golang.org/x/sync v0.10.0 google.golang.org/grpc v1.70.0 google.golang.org/protobuf v1.36.5 gopkg.in/yaml.v2 v2.4.0 @@ -82,7 +85,6 @@ require ( github.com/prometheus/common v0.60.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/rs/xid v1.6.0 // indirect github.com/sirikothe/gotextfsm v1.0.1-0.20200816110946-6aa2cfd355e4 // indirect github.com/x448/float16 v0.8.4 // indirect go.opencensus.io v0.24.0 // indirect @@ -90,6 +92,7 @@ require ( golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect golang.org/x/net v0.34.0 // indirect golang.org/x/oauth2 v0.24.0 // indirect + golang.org/x/sync v0.10.0 // indirect golang.org/x/sys v0.29.0 // indirect golang.org/x/term v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect diff --git a/go.sum b/go.sum index 83c2725d..f84325b2 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,9 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo= cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w= github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/beevik/etree v1.5.0 h1:iaQZFSDS+3kYZiGoc9uKeOkUY3nYMXOKLl6KIJxiJWs= github.com/beevik/etree v1.5.0/go.mod h1:gPNJNaBGVZ9AwsidazFZyygnd+0pAU38N4D+WemwKNs= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -14,15 +11,11 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= -github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= @@ -43,8 +36,6 @@ github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRr github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= @@ -52,7 +43,6 @@ github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/ github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= @@ -88,10 +78,7 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= @@ -106,8 +93,6 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -125,7 +110,6 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDa github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jellydator/ttlcache/v3 v3.3.0 h1:BdoC9cE81qXfrxeb9eoJi9dWrdhSuwXMAnHTbnBm4Wc= @@ -157,7 +141,6 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -171,9 +154,8 @@ github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= -github.com/openconfig/gnmi v0.10.0/go.mod h1:Y9os75GmSkhHw2wX8sMsxfI7qRGAEcDh8NTa5a8vj6E= -github.com/openconfig/gnmi v0.13.0 h1:4aVopzMZVYtfrRqlpDqM0liutE+3/AiDMzNtI2r7em4= -github.com/openconfig/gnmi v0.13.0/go.mod h1:YJwAQ6qkU06TU/g4ZqjxSkajOm0adoBK86/s6w0ow3w= +github.com/openconfig/gnmi v0.14.0 h1:lXAd3HgjtNBnLypevp0pxzpEXmsUD0TbKzaNJ/FboPM= +github.com/openconfig/gnmi v0.14.0/go.mod h1:whr6zVq9PCU8mV1D0K9v7Ajd3+swoN6Yam9n8OH3eT0= github.com/openconfig/gnmic/pkg/api v0.1.8 h1:3N9oFduU204Ta3g7r3dC4pcYdT/Dr7PrSC13Lyc7Ymc= github.com/openconfig/gnmic/pkg/api v0.1.8/go.mod h1:WbME6stT7zcYzQnCwQO42a23WgZRqFQVGVsp0C4FHtc= github.com/openconfig/gnmic/pkg/target v0.1.4 h1:zO7gOke9RVnkockM2lZv5MqgNdfysUSqxyp91OnEjOQ= @@ -182,15 +164,11 @@ github.com/openconfig/gnmic/pkg/types v0.1.2 h1:FRjERZrbcuqhwRKHZ7ux4UXr4fc1DlgX github.com/openconfig/gnmic/pkg/types v0.1.2/go.mod h1:Gwc9suBy/s17bP8BaCrp3dE5E+RkcHqVIkFqdXopog4= github.com/openconfig/gnmic/pkg/utils v0.1.1 h1:LgfI/c/O8VpuZg1RS9G6XEeRhAQSlOalE52/8tsdf00= github.com/openconfig/gnmic/pkg/utils v0.1.1/go.mod h1:DQm/e8cdRwdmUORjODWteDU0HG0CWNYBAhLWqnPQegE= -github.com/openconfig/goyang v0.0.0-20200115183954-d0a48929f0ea/go.mod h1:dhXaV0JgHJzdrHi2l+w0fZrwArtXL7jEFoiqLEdmkvU= -github.com/openconfig/grpctunnel v0.0.0-20220819142823-6f5422b8ca70/go.mod h1:OmTWe7RyZj2CIzIgy4ovEBzCLBJzRvWSZmn7u02U9gU= github.com/openconfig/grpctunnel v0.1.0 h1:EN99qtlExZczgQgp5ANnHRC/Rs62cAG+Tz2BQ5m/maM= github.com/openconfig/grpctunnel v0.1.0/go.mod h1:G04Pdu0pml98tdvXrvLaU+EBo3PxYfI9MYqpvdaEHLo= -github.com/openconfig/ygot v0.6.0/go.mod h1:o30svNf7O0xK+R35tlx95odkDmZWS9JyWWQSmIhqwAs= github.com/openconfig/ygot v0.29.20 h1:XHLpwCN91QuKc2LAvnEqtCmH8OuxgLlErDhrdl2mJw8= github.com/openconfig/ygot v0.29.20/go.mod h1:K8HbrPm/v8/emtGQ9+RsJXx6UPKC5JzS/FqK7pN+tMo= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/pborman/getopt v1.1.0/go.mod h1:FxXoW1Re00sQG/+KIkuSqRL/LwQgSkv7uyac+STFsbk= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -206,26 +184,16 @@ github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPA github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/protocolbuffers/txtpbfmt v0.0.0-20220608084003-fc78c767cd6a/go.mod h1:KjY0wibdYKc4DYkerHSbguaf3JeIPGhNJBp2BNiFH78= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= -github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/scrapli/scrapligo v1.3.3 h1:D9zj1QrOYNYAQ30YT7wfQBINvPGxvs5L5Lz+2LnL7V4= github.com/scrapli/scrapligo v1.3.3/go.mod h1:pOWxVyPsQRrWTrkoSSDg05tjOqtWfLffAZtAsCc0w3M= -github.com/sdcio/cache v0.0.35 h1:E0PZl+37qr/MwdQecvZPaiAhgVlUvN/KNiLE9r/XYgA= -github.com/sdcio/cache v0.0.35/go.mod h1:ZQCmt2KtpsuwXZtBCoIksKGBxdjPVHoCDnjo6s49oC0= -github.com/sdcio/goyang v1.6.0-2 h1:jUphRX+NWTliDR7fTthlcJVr3MAZENYqkje2rkMECL8= -github.com/sdcio/goyang v1.6.0-2/go.mod h1:sdNZi/wdTZyLNBNfgLzmmbi7kISm7FskMDKKzMY+x1M= github.com/sdcio/schema-server v0.0.30 h1:71/tb5T9qCC5qskbxfd3RYE/rQAyNUr2gOrU0NcsLSM= github.com/sdcio/schema-server v0.0.30/go.mod h1:q/S7DAIGTI5b7PZFlpzZ34cVXAtawVoGZtfju6uIaCo= -github.com/sdcio/sdc-protos v0.0.39 h1:GDpiUcMmOnHDSCD92t3brZ315QfmkN4yBiFdaZJv5B0= -github.com/sdcio/sdc-protos v0.0.39/go.mod h1:uG2jk1oV3iQ6WRypxJA+vssPqtfZchQzGwAXueV9z6A= github.com/sdcio/yang-parser v0.0.10 h1:N+DTuOM40kkx7y2eXhxL2BAokFntwkfsp6xQxP3VQ+I= github.com/sdcio/yang-parser v0.0.10/go.mod h1:y/d8lg/mqSnZwaO2bkxyVuFBtuDQm9ys9hpsBs/WizU= github.com/sirikothe/gotextfsm v1.0.1-0.20200816110946-6aa2cfd355e4 h1:FHUL2HofYJuslFOQdy/JjjP36zxqIpd/dcoiwLMIs7k= @@ -233,7 +201,6 @@ github.com/sirikothe/gotextfsm v1.0.1-0.20200816110946-6aa2cfd355e4/go.mod h1:CJ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -246,7 +213,6 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -257,7 +223,6 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= @@ -270,7 +235,6 @@ go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiy go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -295,62 +259,42 @@ golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTk golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= @@ -362,10 +306,8 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -379,9 +321,7 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20210811021853-ddbe55d93216/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422 h1:3UsHvIr4Wc2aW4brOaSCmcxh9ksica6fHEr8P1XhkYw= google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422/go.mod h1:3ENsm/5D1mzDyhpzeRi1NR784I0BcofWBoSc5QqqMK4= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -389,14 +329,9 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -406,10 +341,6 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -421,7 +352,6 @@ gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWM gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/mocks/mockcacheclient/client.go b/mocks/mockcacheclient/client.go index b66fe171..bfd26f6a 100644 --- a/mocks/mockcacheclient/client.go +++ b/mocks/mockcacheclient/client.go @@ -12,12 +12,8 @@ package mockcacheclient import ( context "context" reflect "reflect" - time "time" - cache "github.com/sdcio/cache/pkg/cache" - cachepb "github.com/sdcio/cache/proto/cachepb" - cache0 "github.com/sdcio/data-server/pkg/cache" - schema_server "github.com/sdcio/sdc-protos/sdcpb" + tree_persist "github.com/sdcio/data-server/pkg/tree/tree_persist" gomock "go.uber.org/mock/gomock" ) @@ -45,290 +41,157 @@ func (m *MockClient) EXPECT() *MockClientMockRecorder { return m.recorder } -// ApplyPrune mocks base method. -func (m *MockClient) ApplyPrune(ctx context.Context, name, id string) error { +// InstanceClose mocks base method. +func (m *MockClient) InstanceClose(ctx context.Context, cacheInstanceName string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ApplyPrune", ctx, name, id) + ret := m.ctrl.Call(m, "InstanceClose", ctx, cacheInstanceName) ret0, _ := ret[0].(error) return ret0 } -// ApplyPrune indicates an expected call of ApplyPrune. -func (mr *MockClientMockRecorder) ApplyPrune(ctx, name, id any) *gomock.Call { +// InstanceClose indicates an expected call of InstanceClose. +func (mr *MockClientMockRecorder) InstanceClose(ctx, cacheInstanceName any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ApplyPrune", reflect.TypeOf((*MockClient)(nil).ApplyPrune), ctx, name, id) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstanceClose", reflect.TypeOf((*MockClient)(nil).InstanceClose), ctx, cacheInstanceName) } -// Clone mocks base method. -func (m *MockClient) Clone(ctx context.Context, name, clone string) error { +// InstanceCreate mocks base method. +func (m *MockClient) InstanceCreate(ctx context.Context, cacheInstanceName string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Clone", ctx, name, clone) + ret := m.ctrl.Call(m, "InstanceCreate", ctx, cacheInstanceName) ret0, _ := ret[0].(error) return ret0 } -// Clone indicates an expected call of Clone. -func (mr *MockClientMockRecorder) Clone(ctx, name, clone any) *gomock.Call { +// InstanceCreate indicates an expected call of InstanceCreate. +func (mr *MockClientMockRecorder) InstanceCreate(ctx, cacheInstanceName any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clone", reflect.TypeOf((*MockClient)(nil).Clone), ctx, name, clone) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstanceCreate", reflect.TypeOf((*MockClient)(nil).InstanceCreate), ctx, cacheInstanceName) } -// Close mocks base method. -func (m *MockClient) Close() error { +// InstanceDelete mocks base method. +func (m *MockClient) InstanceDelete(ctx context.Context, cacheInstanceName string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Close") + ret := m.ctrl.Call(m, "InstanceDelete", ctx, cacheInstanceName) ret0, _ := ret[0].(error) return ret0 } -// Close indicates an expected call of Close. -func (mr *MockClientMockRecorder) Close() *gomock.Call { +// InstanceDelete indicates an expected call of InstanceDelete. +func (mr *MockClientMockRecorder) InstanceDelete(ctx, cacheInstanceName any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockClient)(nil).Close)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstanceDelete", reflect.TypeOf((*MockClient)(nil).InstanceDelete), ctx, cacheInstanceName) } -// Commit mocks base method. -func (m *MockClient) Commit(ctx context.Context, name, candidate string) error { +// InstanceExists mocks base method. +func (m *MockClient) InstanceExists(ctx context.Context, cacheInstanceName string) bool { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Commit", ctx, name, candidate) - ret0, _ := ret[0].(error) - return ret0 -} - -// Commit indicates an expected call of Commit. -func (mr *MockClientMockRecorder) Commit(ctx, name, candidate any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Commit", reflect.TypeOf((*MockClient)(nil).Commit), ctx, name, candidate) -} - -// Create mocks base method. -func (m *MockClient) Create(ctx context.Context, name string, ephemeral, cached bool) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Create", ctx, name, ephemeral, cached) - ret0, _ := ret[0].(error) - return ret0 -} - -// Create indicates an expected call of Create. -func (mr *MockClientMockRecorder) Create(ctx, name, ephemeral, cached any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockClient)(nil).Create), ctx, name, ephemeral, cached) -} - -// CreateCandidate mocks base method. -func (m *MockClient) CreateCandidate(ctx context.Context, name, candidate, owner string, priority int32) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateCandidate", ctx, name, candidate, owner, priority) - ret0, _ := ret[0].(error) - return ret0 -} - -// CreateCandidate indicates an expected call of CreateCandidate. -func (mr *MockClientMockRecorder) CreateCandidate(ctx, name, candidate, owner, priority any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateCandidate", reflect.TypeOf((*MockClient)(nil).CreateCandidate), ctx, name, candidate, owner, priority) -} - -// CreatePruneID mocks base method. -func (m *MockClient) CreatePruneID(ctx context.Context, name string, force bool) (string, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreatePruneID", ctx, name, force) - ret0, _ := ret[0].(string) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// CreatePruneID indicates an expected call of CreatePruneID. -func (mr *MockClientMockRecorder) CreatePruneID(ctx, name, force any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreatePruneID", reflect.TypeOf((*MockClient)(nil).CreatePruneID), ctx, name, force) -} - -// Delete mocks base method. -func (m *MockClient) Delete(ctx context.Context, name string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Delete", ctx, name) - ret0, _ := ret[0].(error) - return ret0 -} - -// Delete indicates an expected call of Delete. -func (mr *MockClientMockRecorder) Delete(ctx, name any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockClient)(nil).Delete), ctx, name) -} - -// DeleteCandidate mocks base method. -func (m *MockClient) DeleteCandidate(ctx context.Context, name, candidate string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DeleteCandidate", ctx, name, candidate) - ret0, _ := ret[0].(error) + ret := m.ctrl.Call(m, "InstanceExists", ctx, cacheInstanceName) + ret0, _ := ret[0].(bool) return ret0 } -// DeleteCandidate indicates an expected call of DeleteCandidate. -func (mr *MockClientMockRecorder) DeleteCandidate(ctx, name, candidate any) *gomock.Call { +// InstanceExists indicates an expected call of InstanceExists. +func (mr *MockClientMockRecorder) InstanceExists(ctx, cacheInstanceName any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteCandidate", reflect.TypeOf((*MockClient)(nil).DeleteCandidate), ctx, name, candidate) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstanceExists", reflect.TypeOf((*MockClient)(nil).InstanceExists), ctx, cacheInstanceName) } -// Discard mocks base method. -func (m *MockClient) Discard(ctx context.Context, name, candidate string) error { +// InstanceIntentDelete mocks base method. +func (m *MockClient) InstanceIntentDelete(ctx context.Context, cacheName, intentName string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Discard", ctx, name, candidate) + ret := m.ctrl.Call(m, "InstanceIntentDelete", ctx, cacheName, intentName) ret0, _ := ret[0].(error) return ret0 } -// Discard indicates an expected call of Discard. -func (mr *MockClientMockRecorder) Discard(ctx, name, candidate any) *gomock.Call { +// InstanceIntentDelete indicates an expected call of InstanceIntentDelete. +func (mr *MockClientMockRecorder) InstanceIntentDelete(ctx, cacheName, intentName any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Discard", reflect.TypeOf((*MockClient)(nil).Discard), ctx, name, candidate) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstanceIntentDelete", reflect.TypeOf((*MockClient)(nil).InstanceIntentDelete), ctx, cacheName, intentName) } -// Exists mocks base method. -func (m *MockClient) Exists(ctx context.Context, name string) (bool, error) { +// InstanceIntentExists mocks base method. +func (m *MockClient) InstanceIntentExists(ctx context.Context, cacheName, intentName string) (bool, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Exists", ctx, name) + ret := m.ctrl.Call(m, "InstanceIntentExists", ctx, cacheName, intentName) ret0, _ := ret[0].(bool) ret1, _ := ret[1].(error) return ret0, ret1 } -// Exists indicates an expected call of Exists. -func (mr *MockClientMockRecorder) Exists(ctx, name any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Exists", reflect.TypeOf((*MockClient)(nil).Exists), ctx, name) -} - -// GetCandidates mocks base method. -func (m *MockClient) GetCandidates(ctx context.Context, name string) ([]*cache.CandidateDetails, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetCandidates", ctx, name) - ret0, _ := ret[0].([]*cache.CandidateDetails) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetCandidates indicates an expected call of GetCandidates. -func (mr *MockClientMockRecorder) GetCandidates(ctx, name any) *gomock.Call { +// InstanceIntentExists indicates an expected call of InstanceIntentExists. +func (mr *MockClientMockRecorder) InstanceIntentExists(ctx, cacheName, intentName any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCandidates", reflect.TypeOf((*MockClient)(nil).GetCandidates), ctx, name) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstanceIntentExists", reflect.TypeOf((*MockClient)(nil).InstanceIntentExists), ctx, cacheName, intentName) } -// GetChanges mocks base method. -func (m *MockClient) GetChanges(ctx context.Context, name, candidate string) ([]*cache0.Change, error) { +// InstanceIntentGet mocks base method. +func (m *MockClient) InstanceIntentGet(ctx context.Context, cacheName, intentName string) (*tree_persist.Intent, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetChanges", ctx, name, candidate) - ret0, _ := ret[0].([]*cache0.Change) + ret := m.ctrl.Call(m, "InstanceIntentGet", ctx, cacheName, intentName) + ret0, _ := ret[0].(*tree_persist.Intent) ret1, _ := ret[1].(error) return ret0, ret1 } -// GetChanges indicates an expected call of GetChanges. -func (mr *MockClientMockRecorder) GetChanges(ctx, name, candidate any) *gomock.Call { +// InstanceIntentGet indicates an expected call of InstanceIntentGet. +func (mr *MockClientMockRecorder) InstanceIntentGet(ctx, cacheName, intentName any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChanges", reflect.TypeOf((*MockClient)(nil).GetChanges), ctx, name, candidate) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstanceIntentGet", reflect.TypeOf((*MockClient)(nil).InstanceIntentGet), ctx, cacheName, intentName) } -// GetKeys mocks base method. -func (m *MockClient) GetKeys(ctx context.Context, name string, store cachepb.Store) (chan *cache0.Update, error) { +// InstanceIntentGetAll mocks base method. +func (m *MockClient) InstanceIntentGetAll(ctx context.Context, cacheName string, intentChan chan<- *tree_persist.Intent, errChan chan<- error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetKeys", ctx, name, store) - ret0, _ := ret[0].(chan *cache0.Update) - ret1, _ := ret[1].(error) - return ret0, ret1 + m.ctrl.Call(m, "InstanceIntentGetAll", ctx, cacheName, intentChan, errChan) } -// GetKeys indicates an expected call of GetKeys. -func (mr *MockClientMockRecorder) GetKeys(ctx, name, store any) *gomock.Call { +// InstanceIntentGetAll indicates an expected call of InstanceIntentGetAll. +func (mr *MockClientMockRecorder) InstanceIntentGetAll(ctx, cacheName, intentChan, errChan any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetKeys", reflect.TypeOf((*MockClient)(nil).GetKeys), ctx, name, store) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstanceIntentGetAll", reflect.TypeOf((*MockClient)(nil).InstanceIntentGetAll), ctx, cacheName, intentChan, errChan) } -// HasCandidate mocks base method. -func (m *MockClient) HasCandidate(ctx context.Context, name, candidate string) (bool, error) { +// InstanceIntentModify mocks base method. +func (m *MockClient) InstanceIntentModify(ctx context.Context, cacheName string, intent *tree_persist.Intent) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "HasCandidate", ctx, name, candidate) - ret0, _ := ret[0].(bool) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// HasCandidate indicates an expected call of HasCandidate. -func (mr *MockClientMockRecorder) HasCandidate(ctx, name, candidate any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasCandidate", reflect.TypeOf((*MockClient)(nil).HasCandidate), ctx, name, candidate) -} - -// List mocks base method. -func (m *MockClient) List(ctx context.Context) ([]string, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "List", ctx) - ret0, _ := ret[0].([]string) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// List indicates an expected call of List. -func (mr *MockClientMockRecorder) List(ctx any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockClient)(nil).List), ctx) -} - -// Modify mocks base method. -func (m *MockClient) Modify(ctx context.Context, name string, opts *cache0.Opts, dels [][]string, upds []*cache0.Update) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Modify", ctx, name, opts, dels, upds) + ret := m.ctrl.Call(m, "InstanceIntentModify", ctx, cacheName, intent) ret0, _ := ret[0].(error) return ret0 } -// Modify indicates an expected call of Modify. -func (mr *MockClientMockRecorder) Modify(ctx, name, opts, dels, upds any) *gomock.Call { +// InstanceIntentModify indicates an expected call of InstanceIntentModify. +func (mr *MockClientMockRecorder) InstanceIntentModify(ctx, cacheName, intent any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Modify", reflect.TypeOf((*MockClient)(nil).Modify), ctx, name, opts, dels, upds) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstanceIntentModify", reflect.TypeOf((*MockClient)(nil).InstanceIntentModify), ctx, cacheName, intent) } -// NewUpdate mocks base method. -func (m *MockClient) NewUpdate(arg0 *schema_server.Update) (*cache0.Update, error) { +// InstanceIntentsList mocks base method. +func (m *MockClient) InstanceIntentsList(ctx context.Context, cacheInstanceName string) ([]string, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NewUpdate", arg0) - ret0, _ := ret[0].(*cache0.Update) + ret := m.ctrl.Call(m, "InstanceIntentsList", ctx, cacheInstanceName) + ret0, _ := ret[0].([]string) ret1, _ := ret[1].(error) return ret0, ret1 } -// NewUpdate indicates an expected call of NewUpdate. -func (mr *MockClientMockRecorder) NewUpdate(arg0 any) *gomock.Call { +// InstanceIntentsList indicates an expected call of InstanceIntentsList. +func (mr *MockClientMockRecorder) InstanceIntentsList(ctx, cacheInstanceName any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewUpdate", reflect.TypeOf((*MockClient)(nil).NewUpdate), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstanceIntentsList", reflect.TypeOf((*MockClient)(nil).InstanceIntentsList), ctx, cacheInstanceName) } -// Read mocks base method. -func (m *MockClient) Read(ctx context.Context, name string, opts *cache0.Opts, paths [][]string, period time.Duration) []*cache0.Update { +// InstancesList mocks base method. +func (m *MockClient) InstancesList(ctx context.Context) []string { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Read", ctx, name, opts, paths, period) - ret0, _ := ret[0].([]*cache0.Update) - return ret0 -} - -// Read indicates an expected call of Read. -func (mr *MockClientMockRecorder) Read(ctx, name, opts, paths, period any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockClient)(nil).Read), ctx, name, opts, paths, period) -} - -// ReadCh mocks base method. -func (m *MockClient) ReadCh(ctx context.Context, name string, opts *cache0.Opts, paths [][]string, period time.Duration) chan *cache0.Update { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ReadCh", ctx, name, opts, paths, period) - ret0, _ := ret[0].(chan *cache0.Update) + ret := m.ctrl.Call(m, "InstancesList", ctx) + ret0, _ := ret[0].([]string) return ret0 } -// ReadCh indicates an expected call of ReadCh. -func (mr *MockClientMockRecorder) ReadCh(ctx, name, opts, paths, period any) *gomock.Call { +// InstancesList indicates an expected call of InstancesList. +func (mr *MockClientMockRecorder) InstancesList(ctx any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadCh", reflect.TypeOf((*MockClient)(nil).ReadCh), ctx, name, opts, paths, period) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstancesList", reflect.TypeOf((*MockClient)(nil).InstancesList), ctx) } diff --git a/mocks/mocktreeentry/entry.go b/mocks/mocktreeentry/entry.go index a79b8f2c..032e640a 100644 --- a/mocks/mocktreeentry/entry.go +++ b/mocks/mocktreeentry/entry.go @@ -14,10 +14,10 @@ import ( reflect "reflect" etree "github.com/beevik/etree" - cache "github.com/sdcio/data-server/pkg/cache" tree "github.com/sdcio/data-server/pkg/tree" importer "github.com/sdcio/data-server/pkg/tree/importer" - types "github.com/sdcio/data-server/pkg/types" + tree_persist "github.com/sdcio/data-server/pkg/tree/tree_persist" + types "github.com/sdcio/data-server/pkg/tree/types" schema_server "github.com/sdcio/sdc-protos/sdcpb" gomock "go.uber.org/mock/gomock" ) @@ -46,19 +46,19 @@ func (m *MockEntry) EXPECT() *MockEntryMockRecorder { return m.recorder } -// AddCacheUpdateRecursive mocks base method. -func (m *MockEntry) AddCacheUpdateRecursive(ctx context.Context, u *cache.Update, flags *tree.UpdateInsertFlags) (tree.Entry, error) { +// AddUpdateRecursive mocks base method. +func (m *MockEntry) AddUpdateRecursive(ctx context.Context, u *types.Update, flags *types.UpdateInsertFlags) (tree.Entry, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AddCacheUpdateRecursive", ctx, u, flags) + ret := m.ctrl.Call(m, "AddUpdateRecursive", ctx, u, flags) ret0, _ := ret[0].(tree.Entry) ret1, _ := ret[1].(error) return ret0, ret1 } -// AddCacheUpdateRecursive indicates an expected call of AddCacheUpdateRecursive. -func (mr *MockEntryMockRecorder) AddCacheUpdateRecursive(ctx, u, flags any) *gomock.Call { +// AddUpdateRecursive indicates an expected call of AddUpdateRecursive. +func (mr *MockEntryMockRecorder) AddUpdateRecursive(ctx, u, flags any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddCacheUpdateRecursive", reflect.TypeOf((*MockEntry)(nil).AddCacheUpdateRecursive), ctx, u, flags) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddUpdateRecursive", reflect.TypeOf((*MockEntry)(nil).AddUpdateRecursive), ctx, u, flags) } // FilterChilds mocks base method. @@ -77,9 +77,11 @@ func (mr *MockEntryMockRecorder) FilterChilds(keys any) *gomock.Call { } // FinishInsertionPhase mocks base method. -func (m *MockEntry) FinishInsertionPhase(ctx context.Context) { +func (m *MockEntry) FinishInsertionPhase(ctx context.Context) error { m.ctrl.T.Helper() - m.ctrl.Call(m, "FinishInsertionPhase", ctx) + ret := m.ctrl.Call(m, "FinishInsertionPhase", ctx) + ret0, _ := ret[0].(error) + return ret0 } // FinishInsertionPhase indicates an expected call of FinishInsertionPhase. @@ -89,10 +91,10 @@ func (mr *MockEntryMockRecorder) FinishInsertionPhase(ctx any) *gomock.Call { } // GetByOwner mocks base method. -func (m *MockEntry) GetByOwner(owner string, result []*tree.LeafEntry) []*tree.LeafEntry { +func (m *MockEntry) GetByOwner(owner string, result []*tree.LeafEntry) tree.LeafVariantSlice { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetByOwner", owner, result) - ret0, _ := ret[0].([]*tree.LeafEntry) + ret0, _ := ret[0].(tree.LeafVariantSlice) return ret0 } @@ -103,10 +105,10 @@ func (mr *MockEntryMockRecorder) GetByOwner(owner, result any) *gomock.Call { } // GetDeletes mocks base method. -func (m *MockEntry) GetDeletes(entries []tree.DeleteEntry, aggregatePaths bool) ([]tree.DeleteEntry, error) { +func (m *MockEntry) GetDeletes(entries []types.DeleteEntry, aggregatePaths bool) ([]types.DeleteEntry, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetDeletes", entries, aggregatePaths) - ret0, _ := ret[0].([]tree.DeleteEntry) + ret0, _ := ret[0].([]types.DeleteEntry) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -244,6 +246,18 @@ func (mr *MockEntryMockRecorder) IsRoot() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsRoot", reflect.TypeOf((*MockEntry)(nil).IsRoot)) } +// MarkOwnerDelete mocks base method. +func (m *MockEntry) MarkOwnerDelete(o string, onlyIntended bool) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "MarkOwnerDelete", o, onlyIntended) +} + +// MarkOwnerDelete indicates an expected call of MarkOwnerDelete. +func (mr *MockEntryMockRecorder) MarkOwnerDelete(o, onlyIntended any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkOwnerDelete", reflect.TypeOf((*MockEntry)(nil).MarkOwnerDelete), o, onlyIntended) +} + // Navigate mocks base method. func (m *MockEntry) Navigate(ctx context.Context, path []string, isRootPath bool) (tree.Entry, error) { m.ctrl.T.Helper() @@ -290,10 +304,10 @@ func (mr *MockEntryMockRecorder) NavigateSdcpbPath(ctx, path, isRootPath any) *g } // Path mocks base method. -func (m *MockEntry) Path() tree.PathSlice { +func (m *MockEntry) Path() types.PathSlice { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Path") - ret0, _ := ret[0].(tree.PathSlice) + ret0, _ := ret[0].(types.PathSlice) return ret0 } @@ -406,6 +420,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) } +// 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) +} + // Validate mocks base method. func (m *MockEntry) Validate(ctx context.Context, resultChan chan<- *types.ValidationResultEntry, concurrent bool) { m.ctrl.T.Helper() @@ -490,29 +519,17 @@ func (mr *MockEntryMockRecorder) getHighestPrecedenceLeafValue(arg0 any) *gomock } // getHighestPrecedenceValueOfBranch mocks base method. -func (m *MockEntry) getHighestPrecedenceValueOfBranch() int32 { +func (m *MockEntry) getHighestPrecedenceValueOfBranch(includeDeleted bool) int32 { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "getHighestPrecedenceValueOfBranch") + ret := m.ctrl.Call(m, "getHighestPrecedenceValueOfBranch", includeDeleted) ret0, _ := ret[0].(int32) return ret0 } // getHighestPrecedenceValueOfBranch indicates an expected call of getHighestPrecedenceValueOfBranch. -func (mr *MockEntryMockRecorder) getHighestPrecedenceValueOfBranch() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getHighestPrecedenceValueOfBranch", reflect.TypeOf((*MockEntry)(nil).getHighestPrecedenceValueOfBranch)) -} - -// markOwnerDelete mocks base method. -func (m *MockEntry) markOwnerDelete(o string, onlyIntended bool) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "markOwnerDelete", o, onlyIntended) -} - -// markOwnerDelete indicates an expected call of markOwnerDelete. -func (mr *MockEntryMockRecorder) markOwnerDelete(o, onlyIntended any) *gomock.Call { +func (mr *MockEntryMockRecorder) getHighestPrecedenceValueOfBranch(includeDeleted any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "markOwnerDelete", reflect.TypeOf((*MockEntry)(nil).markOwnerDelete), o, onlyIntended) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getHighestPrecedenceValueOfBranch", reflect.TypeOf((*MockEntry)(nil).getHighestPrecedenceValueOfBranch), includeDeleted) } // remainsToExist mocks base method. diff --git a/mocks/mockvalidationclient/client.go b/mocks/mockvalidationclient/client.go deleted file mode 100644 index 3a4077e5..00000000 --- a/mocks/mockvalidationclient/client.go +++ /dev/null @@ -1,148 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: pkg/datastore/clients/validationClient.go -// -// Generated by this command: -// -// mockgen -package=mockvalidationclient -source=pkg/datastore/clients/validationClient.go -destination=./mocks/mockvalidationclient/client.go -// - -// Package mockvalidationclient is a generated GoMock package. -package mockvalidationclient - -import ( - context "context" - reflect "reflect" - time "time" - - cache "github.com/sdcio/data-server/pkg/cache" - schema_server "github.com/sdcio/sdc-protos/sdcpb" - gomock "go.uber.org/mock/gomock" -) - -// MockValidationClient is a mock of ValidationClient interface. -type MockValidationClient struct { - ctrl *gomock.Controller - recorder *MockValidationClientMockRecorder - isgomock struct{} -} - -// MockValidationClientMockRecorder is the mock recorder for MockValidationClient. -type MockValidationClientMockRecorder struct { - mock *MockValidationClient -} - -// NewMockValidationClient creates a new mock instance. -func NewMockValidationClient(ctrl *gomock.Controller) *MockValidationClient { - mock := &MockValidationClient{ctrl: ctrl} - mock.recorder = &MockValidationClientMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockValidationClient) EXPECT() *MockValidationClientMockRecorder { - return m.recorder -} - -// GetSchemaElements mocks base method. -func (m *MockValidationClient) GetSchemaElements(ctx context.Context, p *schema_server.Path, done chan struct{}) (chan *schema_server.GetSchemaResponse, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetSchemaElements", ctx, p, done) - ret0, _ := ret[0].(chan *schema_server.GetSchemaResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetSchemaElements indicates an expected call of GetSchemaElements. -func (mr *MockValidationClientMockRecorder) GetSchemaElements(ctx, p, done any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSchemaElements", reflect.TypeOf((*MockValidationClient)(nil).GetSchemaElements), ctx, p, done) -} - -// GetSchemaSdcpbPath mocks base method. -func (m *MockValidationClient) GetSchemaSdcpbPath(ctx context.Context, path *schema_server.Path) (*schema_server.GetSchemaResponse, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetSchemaSdcpbPath", ctx, path) - ret0, _ := ret[0].(*schema_server.GetSchemaResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetSchemaSdcpbPath indicates an expected call of GetSchemaSdcpbPath. -func (mr *MockValidationClientMockRecorder) GetSchemaSdcpbPath(ctx, path any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSchemaSdcpbPath", reflect.TypeOf((*MockValidationClient)(nil).GetSchemaSdcpbPath), ctx, path) -} - -// GetSchemaSlicePath mocks base method. -func (m *MockValidationClient) GetSchemaSlicePath(ctx context.Context, path []string) (*schema_server.GetSchemaResponse, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetSchemaSlicePath", ctx, path) - ret0, _ := ret[0].(*schema_server.GetSchemaResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetSchemaSlicePath indicates an expected call of GetSchemaSlicePath. -func (mr *MockValidationClientMockRecorder) GetSchemaSlicePath(ctx, path any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSchemaSlicePath", reflect.TypeOf((*MockValidationClient)(nil).GetSchemaSlicePath), ctx, path) -} - -// GetValue mocks base method. -func (m *MockValidationClient) GetValue(ctx context.Context, candidateName string, path *schema_server.Path) (*schema_server.TypedValue, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetValue", ctx, candidateName, path) - ret0, _ := ret[0].(*schema_server.TypedValue) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetValue indicates an expected call of GetValue. -func (mr *MockValidationClientMockRecorder) GetValue(ctx, candidateName, path any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetValue", reflect.TypeOf((*MockValidationClient)(nil).GetValue), ctx, candidateName, path) -} - -// GetValues mocks base method. -func (m *MockValidationClient) GetValues(ctx context.Context, candidateName string, path *schema_server.Path) ([]*schema_server.TypedValue, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetValues", ctx, candidateName, path) - ret0, _ := ret[0].([]*schema_server.TypedValue) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetValues indicates an expected call of GetValues. -func (mr *MockValidationClientMockRecorder) GetValues(ctx, candidateName, path any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetValues", reflect.TypeOf((*MockValidationClient)(nil).GetValues), ctx, candidateName, path) -} - -// ReadIntended mocks base method. -func (m *MockValidationClient) ReadIntended(ctx context.Context, opts *cache.Opts, paths [][]string, period time.Duration) []*cache.Update { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ReadIntended", ctx, opts, paths, period) - ret0, _ := ret[0].([]*cache.Update) - return ret0 -} - -// ReadIntended indicates an expected call of ReadIntended. -func (mr *MockValidationClientMockRecorder) ReadIntended(ctx, opts, paths, period any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadIntended", reflect.TypeOf((*MockValidationClient)(nil).ReadIntended), ctx, opts, paths, period) -} - -// ToPath mocks base method. -func (m *MockValidationClient) ToPath(ctx context.Context, path []string) (*schema_server.Path, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ToPath", ctx, path) - ret0, _ := ret[0].(*schema_server.Path) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ToPath indicates an expected call of ToPath. -func (mr *MockValidationClientMockRecorder) ToPath(ctx, path any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToPath", reflect.TypeOf((*MockValidationClient)(nil).ToPath), ctx, path) -} diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go index 0bc4e7bc..4a58f8a0 100644 --- a/pkg/cache/cache.go +++ b/pkg/cache/cache.go @@ -15,69 +15,26 @@ package cache import ( - "bytes" "context" "fmt" - "time" - "github.com/sdcio/cache/pkg/cache" - "github.com/sdcio/cache/proto/cachepb" + "github.com/sdcio/data-server/pkg/tree/tree_persist" sdcpb "github.com/sdcio/sdc-protos/sdcpb" "google.golang.org/protobuf/proto" ) type Client interface { - // Create a cache - Create(ctx context.Context, name string, ephemeral bool, cached bool) error - // List caches - List(ctx context.Context) ([]string, error) - // Delete delete cache or cache candidate - Delete(ctx context.Context, name string) error - // Exists check if cache instance exists - Exists(ctx context.Context, name string) (bool, error) - // CreateCandidate create a candidate - CreateCandidate(ctx context.Context, name, candidate, owner string, priority int32) error - // GetCandidates get list of candidates - GetCandidates(ctx context.Context, name string) ([]*cache.CandidateDetails, error) - // HasCandidate check if a candidate exists - HasCandidate(ctx context.Context, name, candidate string) (bool, error) - // DeleteCandidate deletes a candidate - DeleteCandidate(ctx context.Context, name, candidate string) error - // Clone a cache - Clone(ctx context.Context, name, clone string) error - // CreatePruneID - CreatePruneID(ctx context.Context, name string, force bool) (string, error) - // ApplyPrune - ApplyPrune(ctx context.Context, name, id string) error - // Modify send a stream of modifications (update or delete) to a cache, or candidate - Modify(ctx context.Context, name string, opts *Opts, dels [][]string, upds []*Update) error - // Read from a cache or candidate - Read(ctx context.Context, name string, opts *Opts, paths [][]string, period time.Duration) []*Update - // ReadCh read from a cache or candidate, get results through a channel - ReadCh(ctx context.Context, name string, opts *Opts, paths [][]string, period time.Duration) chan *Update - // GetChanges present in a candidate - GetChanges(ctx context.Context, name, candidate string) ([]*Change, error) - // Discard changes made to a candidate - Discard(ctx context.Context, name, candidate string) error - // Commit a candidate changes into the intended store - Commit(ctx context.Context, name, candidate string) error - // NewUpdate build a cache update from a sdcpb.Update - NewUpdate(*sdcpb.Update) (*Update, error) - // GetKeys retrieve the Keys of the specified store - GetKeys(ctx context.Context, name string, store cachepb.Store) (chan *Update, error) - // disconnect from the cache - Close() error -} - -type KeyData struct { - Path []string - Intents []IntentMeta -} - -type IntentMeta struct { - Owner string - Priority int32 - Ts int64 + InstanceCreate(ctx context.Context, cacheInstanceName string) error + InstanceDelete(ctx context.Context, cacheInstanceName string) error + InstanceClose(ctx context.Context, cacheInstanceName string) error + InstanceExists(ctx context.Context, cacheInstanceName string) bool + InstancesList(ctx context.Context) []string + InstanceIntentsList(ctx context.Context, cacheInstanceName string) ([]string, error) + InstanceIntentGet(ctx context.Context, cacheName string, intentName string) (*tree_persist.Intent, error) + InstanceIntentModify(ctx context.Context, cacheName string, intent *tree_persist.Intent) error + InstanceIntentDelete(ctx context.Context, cacheName string, intentName string) error + InstanceIntentExists(ctx context.Context, cacheName string, intentName string) (bool, error) + InstanceIntentGetAll(ctx context.Context, cacheName string, excludeIntentNames []string, intentChan chan<- *tree_persist.Intent, errChan chan<- error) } type Update struct { @@ -136,36 +93,3 @@ func (u *Update) String() string { } return fmt.Sprintf("path: %s, owner: %s, priority: %d, value: %s", u.path, u.owner, u.priority, val) } - -// EqualSkipPath checks the equality of two updates. -// It however skips comparing paths and timestamps. -// This is a shortcut for performace, for cases in which it is already clear that the path is definately equal. -func (u *Update) EqualSkipPath(other *Update) bool { - return u.owner == other.owner && u.priority == other.priority && bytes.Equal(u.value, other.value) -} - -type Change struct { - Update *Update - Delete []string -} - -type Opts struct { - Store cachepb.Store - Owner string // represents the intent name - Priority int32 - PriorityCount uint64 - KeysOnly bool -} - -func getStore(s cachepb.Store) cache.Store { - switch s { - default: //case cachepb.Store_CONFIG: - return cache.StoreConfig - case cachepb.Store_STATE: - return cache.StoreState - case cachepb.Store_INTENDED: - return cache.StoreIntended - case cachepb.Store_INTENTS: - return cache.StoreIntents - } -} diff --git a/pkg/cache/cacheClientBound.go b/pkg/cache/cacheClientBound.go new file mode 100644 index 00000000..65cef808 --- /dev/null +++ b/pkg/cache/cacheClientBound.go @@ -0,0 +1,77 @@ +// Copyright 2024 Nokia +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cache + +import ( + "context" + + "github.com/sdcio/data-server/pkg/tree/tree_persist" +) + +type CacheClientBound interface { + InstanceCreate(ctx context.Context) error + InstanceDelete(ctx context.Context) error + InstanceExists(ctx context.Context) bool + IntentsList(ctx context.Context) ([]string, error) + IntentGet(ctx context.Context, intentName string) (*tree_persist.Intent, error) + IntentModify(ctx context.Context, intent *tree_persist.Intent) error + IntentDelete(ctx context.Context, intentName string) error + IntentExists(ctx context.Context, intentName string) (bool, error) + IntentGetAll(ctx context.Context, excludeIntentNames []string, intentChan chan<- *tree_persist.Intent, errChan chan<- error) + InstanceClose(ctx context.Context) error +} + +type CacheClientBoundImpl struct { + cacheClient Client + cacheName string +} + +func NewCacheClientBound(name string, c Client) *CacheClientBoundImpl { + return &CacheClientBoundImpl{ + cacheClient: c, + cacheName: name, + } +} + +func (c *CacheClientBoundImpl) InstanceCreate(ctx context.Context) error { + return c.cacheClient.InstanceCreate(ctx, c.cacheName) +} +func (c *CacheClientBoundImpl) InstanceDelete(ctx context.Context) error { + return c.cacheClient.InstanceDelete(ctx, c.cacheName) +} +func (c *CacheClientBoundImpl) InstanceClose(ctx context.Context) error { + return c.cacheClient.InstanceClose(ctx, c.cacheName) +} +func (c *CacheClientBoundImpl) InstanceExists(ctx context.Context) bool { + return c.cacheClient.InstanceExists(ctx, c.cacheName) +} +func (c *CacheClientBoundImpl) IntentsList(ctx context.Context) ([]string, error) { + return c.cacheClient.InstanceIntentsList(ctx, c.cacheName) +} +func (c *CacheClientBoundImpl) IntentGet(ctx context.Context, intentName string) (*tree_persist.Intent, error) { + return c.cacheClient.InstanceIntentGet(ctx, c.cacheName, intentName) +} +func (c *CacheClientBoundImpl) IntentModify(ctx context.Context, intent *tree_persist.Intent) error { + return c.cacheClient.InstanceIntentModify(ctx, c.cacheName, intent) +} +func (c *CacheClientBoundImpl) IntentDelete(ctx context.Context, intentName string) error { + return c.cacheClient.InstanceIntentDelete(ctx, c.cacheName, intentName) +} +func (c *CacheClientBoundImpl) IntentExists(ctx context.Context, intentName string) (bool, error) { + return c.cacheClient.InstanceIntentExists(ctx, c.cacheName, intentName) +} +func (c *CacheClientBoundImpl) IntentGetAll(ctx context.Context, excludeIntentNames []string, intentChan chan<- *tree_persist.Intent, errChan chan<- error) { + c.cacheClient.InstanceIntentGetAll(ctx, c.cacheName, excludeIntentNames, intentChan, errChan) +} diff --git a/pkg/cache/local.go b/pkg/cache/local.go index 9d9a8568..348a868a 100644 --- a/pkg/cache/local.go +++ b/pkg/cache/local.go @@ -16,262 +16,92 @@ package cache import ( "context" - "fmt" - "sort" - "time" "github.com/sdcio/cache/pkg/cache" "github.com/sdcio/cache/pkg/config" - "github.com/sdcio/cache/proto/cachepb" - "github.com/sdcio/schema-server/pkg/utils" - sdcpb "github.com/sdcio/sdc-protos/sdcpb" - log "github.com/sirupsen/logrus" + "github.com/sdcio/cache/pkg/store/filesystem" + "github.com/sdcio/cache/pkg/types" + "github.com/sdcio/data-server/pkg/tree/tree_persist" "google.golang.org/protobuf/proto" ) -type localCache struct { - c cache.Cache -} - func NewLocalCache(cfg *config.CacheConfig) (Client, error) { - lc := &localCache{ - c: cache.New(cfg), - } - err := lc.c.Init(context.TODO()) + + fnc, err := filesystem.PreConfigureFilesystemInitFunc(cfg.Dir) if err != nil { return nil, err } - return lc, nil -} - -func (c *localCache) Create(ctx context.Context, name string, _ bool, _ bool) error { - return c.c.Create(ctx, &cache.CacheInstanceConfig{ - Name: name, - }) -} - -func (c *localCache) List(ctx context.Context) ([]string, error) { - return c.c.List(ctx), nil -} -func (c *localCache) HasCandidate(ctx context.Context, name, candidate string) (bool, error) { - cands, err := c.GetCandidates(ctx, name) + cache, err := cache.NewCache(fnc) if err != nil { - return false, err - } - for _, cand := range cands { - if cand.CandidateName == candidate { - return true, nil - } + return nil, err } - return false, nil -} -func (c *localCache) GetCandidates(ctx context.Context, name string) ([]*cache.CandidateDetails, error) { - return c.c.Candidates(ctx, name) + return &LocalCache{ + Cache: cache, + }, nil } -func (c *localCache) Delete(ctx context.Context, name string) error { - return c.c.Delete(ctx, name) +type LocalCache struct { + *cache.Cache } -func (c *localCache) DeleteCandidate(ctx context.Context, name, candidate string) error { - return c.c.Delete(ctx, fmt.Sprintf("%s/%s", name, candidate)) -} - -func (c *localCache) Exists(ctx context.Context, name string) (bool, error) { - return c.c.Exists(ctx, name), nil -} +func (l *LocalCache) InstanceIntentGet(ctx context.Context, cacheName string, intentName string) (*tree_persist.Intent, error) { + b, err := l.Cache.InstanceIntentGet(ctx, cacheName, intentName) + if err != nil { + return nil, err + } -func (c *localCache) CreateCandidate(ctx context.Context, name, candidate, owner string, priority int32) error { - _, err := c.c.CreateCandidate(ctx, name, candidate, owner, priority) - return err + result := &tree_persist.Intent{} + err = proto.Unmarshal(b, result) + if err != nil { + return nil, err + } + return result, nil } -func (c *localCache) Clone(ctx context.Context, name, clone string) error { - _, err := c.c.Clone(ctx, name, clone) - return err -} +func (l *LocalCache) InstanceIntentGetAll(ctx context.Context, cacheName string, excludeIntentNames []string, intentChanOrig chan<- *tree_persist.Intent, errChanOrig chan<- error) { + // create new channels + intentChan := make(chan *types.Intent, 5) + errChan := make(chan error, 1) -func (c *localCache) Modify(ctx context.Context, name string, opts *Opts, dels [][]string, upds []*Update) error { - if opts == nil { - opts = &Opts{} - } - // - var err error - for _, del := range dels { - err = c.c.DeletePrefix(ctx, name, &cache.Opts{ - Store: getStore(opts.Store), - Path: [][]string{del}, // TODO: - Owner: opts.Owner, - Priority: opts.Priority, - }) - if err != nil { - return err - } - } + go l.Cache.InstanceIntentGetAll(ctx, cacheName, excludeIntentNames, intentChan, errChan) - for _, upd := range upds { - err = c.c.WriteValue(ctx, name, &cache.Opts{ - Store: getStore(opts.Store), - Path: [][]string{upd.GetPath()}, - Owner: opts.Owner, - Priority: opts.Priority, - }, upd.Bytes()) - if err != nil { - return err - } - } - return nil -} + defer close(intentChanOrig) + defer close(errChanOrig) -func (c *localCache) Read(ctx context.Context, name string, opts *Opts, paths [][]string, period time.Duration) []*Update { - ch := c.ReadCh(ctx, name, opts, paths, period) - var upds = make([]*Update, 0, len(paths)) for { select { - case <-ctx.Done(): - return nil - case u, ok := <-ch: + case <-ctx.Done(): // Or stop if context is canceled + return + case intent, ok := <-intentChan: // retieve intent if !ok { - sort.Slice(upds, func(i, j int) bool { - return upds[i].ts < upds[j].ts - }) - return upds - } - upds = append(upds, u) - } - } -} - -func (c *localCache) GetKeys(ctx context.Context, name string, store cachepb.Store) (chan *Update, error) { - - if store != cachepb.Store_CONFIG && store != cachepb.Store_INTENDED { - return nil, fmt.Errorf("getkeys only available with config or intended store") - } - - cacheStore := getStore(store) - entryCh, err := c.c.ReadKeys(ctx, name, cacheStore) - outCh := make(chan *Update) - if err != nil { - close(outCh) - return nil, err - } - go func() { - defer close(outCh) - for { - select { - case <-ctx.Done(): return - case e, ok := <-entryCh: - if !ok { - return - } - if e == nil { - continue // - } - outCh <- &Update{ - path: e.P, - value: nil, - priority: e.Priority, - owner: e.Owner, - ts: int64(e.Timestamp), - } } - } - }() - return outCh, nil -} - -func (c *localCache) ReadCh(ctx context.Context, name string, opts *Opts, paths [][]string, period time.Duration) chan *Update { - if opts == nil { - opts = &Opts{} - } - outCh := make(chan *Update, len(paths)) - go func() { - defer close(outCh) - ch, err := c.c.ReadValue(ctx, name, &cache.Opts{ - Store: getStore(opts.Store), - Path: paths, - Owner: opts.Owner, - Priority: opts.Priority, - PriorityCount: opts.PriorityCount, - KeysOnly: opts.KeysOnly, - }) - if err != nil { - log.Errorf("failed to read path %v: %v", paths, err) - return - } - for { - select { - case <-ctx.Done(): + // unmarshall it into a tree_persit.Intent + tpIntent := &tree_persist.Intent{} + err := proto.Unmarshal(intent.Data(), tpIntent) + if err != nil { + errChanOrig <- err return - case e, ok := <-ch: - if !ok { - return - } - if e == nil { - continue // - } - outCh <- &Update{ - path: e.P, - value: e.V, - priority: e.Priority, - owner: e.Owner, - ts: int64(e.Timestamp), - } } + // forward to caller + intentChanOrig <- tpIntent + case err, ok := <-errChan: // Handle errors after intents + if !ok { + errChan = nil // Mark errChan as nil so select ignores it + continue + } + errChanOrig <- err + return } - }() - return outCh -} - -func (c *localCache) GetChanges(ctx context.Context, name, candidate string) ([]*Change, error) { - dels, entries, err := c.c.Diff(ctx, name, candidate) - if err != nil { - return nil, err - } - changes := make([]*Change, 0, len(dels)+len(entries)) - for _, del := range dels { - changes = append(changes, &Change{Delete: del}) - } - for _, entry := range entries { - changes = append(changes, &Change{Update: &Update{ - path: entry.P, - value: entry.V, - }}) } - return changes, nil -} - -func (c *localCache) Discard(ctx context.Context, name, candidate string) error { - return c.c.Discard(ctx, name, candidate) -} - -func (c *localCache) Commit(ctx context.Context, name, candidate string) error { - return c.c.Commit(ctx, name, candidate) } -func (c *localCache) CreatePruneID(ctx context.Context, name string, force bool) (string, error) { - return c.c.CreatePruneID(ctx, name, force) -} - -func (c *localCache) ApplyPrune(ctx context.Context, name, id string) error { - return c.c.ApplyPrune(ctx, name, id) -} - -func (c *localCache) NewUpdate(upd *sdcpb.Update) (*Update, error) { - b, err := proto.Marshal(upd.Value) +func (l *LocalCache) InstanceIntentModify(ctx context.Context, cacheName string, intent *tree_persist.Intent) error { + b, err := proto.Marshal(intent) if err != nil { - return nil, err - } - lupd := &Update{ - path: utils.ToStrings(upd.GetPath(), false, false), - value: b, + return err } - return lupd, nil -} - -func (c *localCache) Close() error { - return c.c.Close() + return l.Cache.InstanceIntentModify(ctx, cacheName, intent.GetIntentName(), b) } diff --git a/pkg/cache/remote.go b/pkg/cache/remote.go deleted file mode 100644 index 2844cf6d..00000000 --- a/pkg/cache/remote.go +++ /dev/null @@ -1,287 +0,0 @@ -// Copyright 2024 Nokia -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cache - -import ( - "context" - "errors" - "fmt" - "sort" - "time" - - "github.com/sdcio/cache/pkg/cache" - "github.com/sdcio/cache/pkg/client" - "github.com/sdcio/cache/proto/cachepb" - "github.com/sdcio/schema-server/pkg/utils" - sdcpb "github.com/sdcio/sdc-protos/sdcpb" - log "github.com/sirupsen/logrus" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/anypb" -) - -type remoteCache struct { - c *client.Client -} - -func NewRemoteCache(ctx context.Context, addr string) (Client, error) { - cc, err := client.New(ctx, &client.ClientConfig{Address: addr}) - if err != nil { - return nil, err - } - return &remoteCache{ - c: cc, - }, nil -} - -func (c *remoteCache) Create(ctx context.Context, name string, ephemeral bool, cached bool) error { - return c.c.Create(ctx, name) -} - -func (c *remoteCache) List(ctx context.Context) ([]string, error) { - return c.c.List(ctx) -} - -func (c *remoteCache) GetKeys(ctx context.Context, name string, store cachepb.Store) (chan *Update, error) { - - if store != cachepb.Store_CONFIG && store != cachepb.Store_INTENDED { - return nil, fmt.Errorf("getkeys only available with config or intended store") - } - - outCh := make(chan *Update) - entryCh, err := c.c.ReadKeys(ctx, name, store) - if err != nil { - close(outCh) - return nil, err - } - - go func() { - defer close(outCh) - for { - select { - case <-ctx.Done(): - return - case e, ok := <-entryCh: - if !ok { - return - } - if e == nil { - continue // - } - outCh <- &Update{ - path: e.Path, - value: nil, - priority: e.Priority, - owner: e.Owner, - ts: int64(e.Timestamp), - } - } - } - }() - return outCh, nil -} - -func (c *remoteCache) GetCandidates(ctx context.Context, name string) ([]*cache.CandidateDetails, error) { - rsp, err := c.c.Get(ctx, name) - if err != nil { - return nil, err - } - rs := make([]*cache.CandidateDetails, 0, len(rsp.GetCandidate())) - for _, candpb := range rsp.GetCandidate() { - rs = append(rs, &cache.CandidateDetails{ - CacheName: name, - CandidateName: candpb.GetName(), - Owner: candpb.GetOwner(), - Priority: candpb.GetPriority(), - }) - } - return rs, nil -} - -func (c *remoteCache) HasCandidate(ctx context.Context, name, candidate string) (bool, error) { - cands, err := c.GetCandidates(ctx, name) - if err != nil { - return false, err - } - for _, cand := range cands { - if cand.CandidateName == candidate { - return true, nil - } - } - return false, nil -} - -func (c *remoteCache) Delete(ctx context.Context, name string) error { - return c.c.Delete(ctx, name) -} - -func (c *remoteCache) Exists(ctx context.Context, name string) (bool, error) { - return c.c.Exists(ctx, name) -} - -func (c *remoteCache) CreateCandidate(ctx context.Context, name, candidate, owner string, priority int32) error { - return c.c.CreateCandidate(ctx, name, candidate, owner, priority) -} - -func (c *remoteCache) DeleteCandidate(ctx context.Context, name, candidate string) error { - return c.c.Delete(ctx, fmt.Sprintf("%s/%s", name, candidate)) -} - -func (c *remoteCache) Clone(ctx context.Context, name, clone string) error { - return c.c.Clone(ctx, name, clone) -} - -func (c *remoteCache) Modify(ctx context.Context, name string, opts *Opts, dels [][]string, upds []*Update) error { - pbUpds := make([]*cachepb.Update, 0, len(upds)) - for _, upd := range upds { - pbUpds = append(pbUpds, - &cachepb.Update{ - Path: upd.GetPath(), - Value: &anypb.Any{ - Value: upd.Bytes(), - }, - }, - ) - } - - wo := &client.ClientOpts{ - Owner: opts.Owner, - Priority: opts.Priority, - Store: getStore(opts.Store), - PriorityCount: opts.PriorityCount, - } - - return c.c.Modify(ctx, name, wo, dels, pbUpds) -} - -func (c *remoteCache) Read(ctx context.Context, name string, opts *Opts, paths [][]string, period time.Duration) []*Update { - outCh := c.ReadCh(ctx, name, opts, paths, period) - updates := make([]*Update, 0) - for { - select { - case <-ctx.Done(): - return nil - case upd, ok := <-outCh: - if !ok { - sort.Slice(updates, func(i, j int) bool { - return updates[i].ts < updates[j].ts - }) - return updates - } - updates = append(updates, upd) - } - } -} - -func (c *remoteCache) ReadCh(ctx context.Context, name string, opts *Opts, paths [][]string, period time.Duration) chan *Update { - ro := &client.ClientOpts{ - Owner: opts.Owner, - Priority: opts.Priority, - Store: getStore(opts.Store), - PriorityCount: opts.PriorityCount, - KeysOnly: opts.KeysOnly, - } - inCh := c.c.Read(ctx, name, ro, paths, period) - outCh := make(chan *Update, len(paths)) - go func() { - defer close(outCh) - for { - select { - case <-ctx.Done(): - if !errors.Is(ctx.Err(), context.Canceled) { - log.Errorf("ctx done: %v", ctx.Err()) - } - return - case readResponse, ok := <-inCh: - if !ok { - return - } - rUpd := &Update{ - path: readResponse.GetPath(), - value: readResponse.GetValue().GetValue(), - priority: readResponse.GetPriority(), - owner: readResponse.GetOwner(), - ts: readResponse.GetTimestamp(), - } - select { - case <-ctx.Done(): - if !errors.Is(ctx.Err(), context.Canceled) { - log.Errorf("ctx done: %v", ctx.Err()) - } - return - case outCh <- rUpd: - } - } - } - }() - return outCh -} - -func (c *remoteCache) GetChanges(ctx context.Context, name, candidate string) ([]*Change, error) { - changes, err := c.c.GetChanges(ctx, name, candidate) - if err != nil { - return nil, err - } - lcs := make([]*Change, 0, len(changes)) - for _, change := range changes { - lcs = append(lcs, &Change{ - Update: &Update{ - path: change.GetUpdate().GetPath(), - value: change.GetUpdate().GetValue().GetValue(), - }, - Delete: change.GetDelete(), - }) - } - return lcs, nil -} - -func (c *remoteCache) Discard(ctx context.Context, name, candidate string) error { - return c.c.Discard(ctx, name, candidate) -} - -func (c *remoteCache) Commit(ctx context.Context, name, candidate string) error { - return c.c.Commit(ctx, name, candidate) -} - -func (c *remoteCache) CreatePruneID(ctx context.Context, name string, force bool) (string, error) { - rsp, err := c.c.Prune(ctx, name, "", force) - if err != nil { - return "", err - } - return rsp.GetId(), nil -} - -func (c *remoteCache) ApplyPrune(ctx context.Context, name, id string) error { - _, err := c.c.Prune(ctx, name, id, false) - if err != nil { - return err - } - return err -} - -func (c *remoteCache) NewUpdate(upd *sdcpb.Update) (*Update, error) { - b, err := proto.Marshal(upd.GetValue()) - if err != nil { - return nil, err - } - rupd := &Update{ - path: utils.ToStrings(upd.GetPath(), false, false), - value: b, - } - return rupd, nil -} - -func (c *remoteCache) Close() error { - return c.c.Close() -} diff --git a/pkg/config/config.go b/pkg/config/config.go index 1a32a117..0dac8f83 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -109,6 +109,9 @@ func (c *Config) validateSetDefaults() error { return fmt.Errorf("unknown schema store type %q", c.SchemaStore.Type) } } + if c.SchemaStore != nil && c.SchemaStore.UploadPath == "" { + c.SchemaStore.UploadPath = "/schemas" + } if c.SchemaStore == nil && (c.GRPCServer.SchemaServer == nil || !c.GRPCServer.SchemaServer.Enabled) { return errors.New("schema-server RPCs cannot be exposed if the schema server is not enabled") } diff --git a/pkg/datastore/clients/cache/cacheClientBound.go b/pkg/datastore/clients/cache/cacheClientBound.go deleted file mode 100644 index 7017c97a..00000000 --- a/pkg/datastore/clients/cache/cacheClientBound.go +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2024 Nokia -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package CacheClient - -import ( - "context" - "time" - - "github.com/sdcio/cache/proto/cachepb" - "github.com/sdcio/schema-server/pkg/utils" - sdcpb "github.com/sdcio/sdc-protos/sdcpb" - - "github.com/sdcio/data-server/pkg/cache" -) - -type CacheClientBoundImpl struct { - cacheClient cache.Client - name string -} - -type CacheClientBound interface { - // GetValue retrieves config value for the provided path - GetValue(ctx context.Context, candidateName string, path *sdcpb.Path) (*sdcpb.TypedValue, error) - // GetValues retrieves config value from the provided path. If path is not a leaf path, all the sub paths will be returned. - GetValues(ctx context.Context, candidateName string, path *sdcpb.Path) ([]*sdcpb.TypedValue, error) - // ReadIntended retrieves the highes priority value from the intended store - ReadIntended(ctx context.Context, opts *cache.Opts, paths [][]string, period time.Duration) []*cache.Update -} - -func NewCacheClientBound(name string, c cache.Client) *CacheClientBoundImpl { - return &CacheClientBoundImpl{ - cacheClient: c, - name: name, // the datastore name - } -} - -// GetValue retrieves config value for the provided path -func (ccb *CacheClientBoundImpl) GetValue(ctx context.Context, candidateName string, path *sdcpb.Path) (*sdcpb.TypedValue, error) { - cacheupds, err := ccb.getValues(ctx, candidateName, path) - if err != nil { - return nil, err - } - if len(cacheupds) == 0 { - return nil, nil - } - return cacheupds[0].Value() -} - -// GetValues retrieves config value from the provided path. If path is not a leaf path, all the sub paths will be returned. -func (ccb *CacheClientBoundImpl) GetValues(ctx context.Context, candidateName string, path *sdcpb.Path) ([]*sdcpb.TypedValue, error) { - cacheupds, err := ccb.getValues(ctx, candidateName, path) - if err != nil { - return nil, err - } - - result := make([]*sdcpb.TypedValue, 0, len(cacheupds)) - - // collect the cachupdate Values to return them - for _, c := range cacheupds { - val, err := c.Value() - if err != nil { - return nil, err - } - result = append(result, val) - } - - return result, nil -} - -// getValues internal function that retrieves config value for the provided path, with its sub-paths -func (ccb *CacheClientBoundImpl) getValues(ctx context.Context, candidateName string, path *sdcpb.Path) ([]*cache.Update, error) { - spath, err := utils.CompletePath(nil, path) - if err != nil { - return nil, err - } - cacheupds := ccb.cacheClient.Read(ctx, ccb.name+"/"+candidateName, &cache.Opts{Store: cachepb.Store_CONFIG}, [][]string{spath}, 0) - if len(cacheupds) == 0 { - return nil, nil - } - return cacheupds, nil -} - -// Read -func (ccb *CacheClientBoundImpl) ReadIntended(ctx context.Context, opts *cache.Opts, paths [][]string, period time.Duration) []*cache.Update { - if opts == nil { - opts = &cache.Opts{} - } - opts.Store = cachepb.Store_INTENDED - opts.PriorityCount = 1 - return ccb.cacheClient.Read(ctx, ccb.name, opts, paths, period) -} diff --git a/pkg/datastore/clients/schema/schemaClientBound.go b/pkg/datastore/clients/schema/schemaClientBound.go index abced38d..064486c1 100644 --- a/pkg/datastore/clients/schema/schemaClientBound.go +++ b/pkg/datastore/clients/schema/schemaClientBound.go @@ -21,6 +21,7 @@ import ( sdcpb "github.com/sdcio/sdc-protos/sdcpb" + "github.com/sdcio/data-server/pkg/config" "github.com/sdcio/data-server/pkg/schema" "github.com/sdcio/data-server/pkg/utils" ) @@ -47,9 +48,9 @@ type SchemaClientBoundImpl struct { indexMutex sync.RWMutex } -func NewSchemaClientBound(s *sdcpb.Schema, sc schema.Client) *SchemaClientBoundImpl { +func NewSchemaClientBound(s *config.SchemaConfig, sc schema.Client) *SchemaClientBoundImpl { result := &SchemaClientBoundImpl{ - schema: s, + schema: s.GetSchema(), schemaClient: sc, index: sync.Map{}, } diff --git a/pkg/datastore/clients/validationClient.go b/pkg/datastore/clients/validationClient.go deleted file mode 100644 index d33306ad..00000000 --- a/pkg/datastore/clients/validationClient.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2024 Nokia -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package clients - -import ( - schema_server "github.com/sdcio/sdc-protos/sdcpb" - - "github.com/sdcio/data-server/pkg/cache" - CacheClient "github.com/sdcio/data-server/pkg/datastore/clients/cache" - SchemaClient "github.com/sdcio/data-server/pkg/datastore/clients/schema" - "github.com/sdcio/data-server/pkg/schema" -) - -type ValidationClientImpl struct { - CacheClient.CacheClientBound - SchemaClient.SchemaClientBound -} - -func NewValidationClient(datastoreName string, c cache.Client, s *schema_server.Schema, sc schema.Client) *ValidationClientImpl { - return &ValidationClientImpl{ - CacheClientBound: CacheClient.NewCacheClientBound(datastoreName, c), - SchemaClientBound: SchemaClient.NewSchemaClientBound(s, sc), - } -} - -// ValidationClient provides a client that bundles the bound clients for the cache as well as for the schema, of a certain device. -type ValidationClient interface { - CacheClient.CacheClientBound - SchemaClient.SchemaClientBound -} diff --git a/pkg/datastore/data_rpc_test.go b/pkg/datastore/converter_test.go similarity index 97% rename from pkg/datastore/data_rpc_test.go rename to pkg/datastore/converter_test.go index c9027ea2..eba8cbff 100644 --- a/pkg/datastore/data_rpc_test.go +++ b/pkg/datastore/converter_test.go @@ -229,7 +229,8 @@ func TestDatastore_expandUpdateLeafAsKeys(t *testing.T) { t.Fatal(err) } - converter := utils.NewConverter(SchemaClient.NewSchemaClientBound(schema.GetSchema(), schemaClient)) + scb := SchemaClient.NewSchemaClientBound(schema, schemaClient) + converter := utils.NewConverter(scb) got, err := converter.ExpandUpdateKeysAsLeaf(tt.args.ctx, tt.args.upd) if (err != nil) != tt.wantErr { diff --git a/pkg/datastore/data_rpc.go b/pkg/datastore/data_rpc.go deleted file mode 100644 index de695378..00000000 --- a/pkg/datastore/data_rpc.go +++ /dev/null @@ -1,710 +0,0 @@ -// Copyright 2024 Nokia -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package datastore - -import ( - "context" - "encoding/json" - "fmt" - "math" - "strconv" - "strings" - "sync" - "time" - - "github.com/sdcio/cache/proto/cachepb" - sdcpb "github.com/sdcio/sdc-protos/sdcpb" - log "github.com/sirupsen/logrus" - "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/emptypb" - - "github.com/sdcio/data-server/pkg/cache" - "github.com/sdcio/data-server/pkg/tree" - "github.com/sdcio/data-server/pkg/utils" -) - -const ( - // to be used for candidates created without an owner - DefaultOwner = "__sdcio" -) - -func (d *Datastore) Get(ctx context.Context, req *sdcpb.GetDataRequest, nCh chan *sdcpb.GetDataResponse) error { - defer close(nCh) - switch req.GetDatastore().GetType() { - case sdcpb.Type_MAIN: - case sdcpb.Type_CANDIDATE: - case sdcpb.Type_INTENDED: - if req.GetDataType() == sdcpb.DataType_STATE { - return status.Error(codes.InvalidArgument, "cannot query STATE data from INTENDED store") - } - } - - switch req.GetEncoding() { - case sdcpb.Encoding_STRING: - case sdcpb.Encoding_JSON: - case sdcpb.Encoding_JSON_IETF: - case sdcpb.Encoding_PROTO: - default: - return fmt.Errorf("unknown encoding: %v", req.GetEncoding()) - } - - var err error - // validate that path(s) exist in the schema - for _, p := range req.GetPath() { - err = d.validatePath(ctx, p) - if err != nil { - return err - } - } - - // build target cache name - name := req.GetName() - if req.GetDatastore().GetName() != "" { - name = fmt.Sprintf("%s/%s", req.GetName(), req.GetDatastore().GetName()) - } - - // convert sdcpb paths to a string list - paths := make([][]string, 0, len(req.GetPath())) - for _, p := range req.GetPath() { - paths = append(paths, utils.ToStrings(p, false, false)) - } - - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - switch req.GetEncoding() { - case sdcpb.Encoding_STRING: - err = d.handleGetDataUpdatesSTRING(ctx, name, req, paths, nCh) - case sdcpb.Encoding_JSON: - err = d.handleGetDataUpdatesJSON(ctx, name, req, paths, nCh, false) - case sdcpb.Encoding_JSON_IETF: - err = d.handleGetDataUpdatesJSON(ctx, name, req, paths, nCh, true) - case sdcpb.Encoding_PROTO: - err = d.handleGetDataUpdatesPROTO(ctx, name, req, paths, nCh) - } - if err != nil { - return err - } - return nil -} - -func (d *Datastore) handleGetDataUpdatesSTRING(ctx context.Context, name string, req *sdcpb.GetDataRequest, paths [][]string, out chan *sdcpb.GetDataResponse) error { -NEXT_STORE: - for _, store := range getStores(req) { - in := d.cacheClient.ReadCh(ctx, name, &cache.Opts{ - Store: store, - Owner: req.GetDatastore().GetOwner(), - Priority: req.GetDatastore().GetPriority(), - }, paths, 0) - - for { - select { - case <-ctx.Done(): - return ctx.Err() - case upd, ok := <-in: - //log.Debugf("ds=%s read path=%v from store=%v: %v", name, paths, store, upd) - if !ok { - continue NEXT_STORE - } - if len(upd.GetPath()) == 0 { - continue - } - scp, err := d.schemaClient.ToPath(ctx, upd.GetPath()) - if err != nil { - return err - } - switch len(scp.GetElem()) { - case 0: - continue - case 1: - if scp.GetElem()[0].GetName() == "" { - continue - } - } - tv, err := upd.Value() - if err != nil { - return err - } - notification := &sdcpb.Notification{ - Timestamp: time.Now().UnixNano(), - Update: []*sdcpb.Update{{ - Path: scp, - Value: tv, - }}, - } - rsp := &sdcpb.GetDataResponse{ - Notification: []*sdcpb.Notification{notification}, - } - select { - case <-ctx.Done(): - return ctx.Err() - case out <- rsp: - } - } - } - } - return nil -} - -func (d *Datastore) handleGetDataUpdatesJSON(ctx context.Context, name string, req *sdcpb.GetDataRequest, paths [][]string, out chan *sdcpb.GetDataResponse, ietf bool) error { - now := time.Now().UnixNano() - - treeSCC := tree.NewTreeCacheClient(d.Name(), d.cacheClient) - tc := tree.NewTreeContext(treeSCC, d.schemaClient, "") - root, err := tree.NewTreeRoot(ctx, tc) - if err != nil { - return err - } - - flagsExisting := tree.NewUpdateInsertFlags() - - for _, store := range getStores(req) { - in := d.cacheClient.ReadCh(ctx, name, &cache.Opts{ - Store: store, - Owner: req.GetDatastore().GetOwner(), - Priority: req.GetDatastore().GetPriority(), - }, paths, 0) - OUTER: - for { - select { - case <-ctx.Done(): - return ctx.Err() - case upd, ok := <-in: - if !ok { - break OUTER - } - - if len(upd.GetPath()) == 0 { - continue - } - - scp, err := d.schemaClient.ToPath(ctx, upd.GetPath()) - if err != nil { - return err - } - switch len(scp.GetElem()) { - case 0: - continue - case 1: - if scp.GetElem()[0].GetName() == "" { - continue - } - } - root.AddCacheUpdateRecursive(ctx, upd, flagsExisting) - } - } - } - - root.FinishInsertionPhase(ctx) - - var j any - // marshal map into JSON bytes - if ietf { - j, err = root.ToJsonIETF(false) - if err != nil { - return err - } - } else { - j, err = root.ToJson(false) - if err != nil { - return err - } - } - b, err := json.Marshal(j) - if err != nil { - err = fmt.Errorf("failed json builder indent : %v", err) - log.Error(err) - return err - } - - select { - case <-ctx.Done(): - return ctx.Err() - case out <- &sdcpb.GetDataResponse{ - Notification: []*sdcpb.Notification{ - { - Timestamp: now, - Update: []*sdcpb.Update{{ - Value: &sdcpb.TypedValue{Value: &sdcpb.TypedValue_JsonVal{JsonVal: b}}, - }}, - }, - }, - }: - } - return nil -} - -func (d *Datastore) handleGetDataUpdatesPROTO(ctx context.Context, name string, req *sdcpb.GetDataRequest, paths [][]string, out chan *sdcpb.GetDataResponse) error { - converter := utils.NewConverter(d.schemaClient) -NEXT_STORE: - for _, store := range getStores(req) { - in := d.cacheClient.ReadCh(ctx, name, &cache.Opts{ - Store: store, - Owner: req.GetDatastore().GetOwner(), - Priority: req.GetDatastore().GetPriority(), - }, paths, 0) - for { - select { - case <-ctx.Done(): - return ctx.Err() - case upd, ok := <-in: - //log.Debugf("ds=%s read path=%v from store=%v: %v", name, paths, store, upd) - if !ok { - continue NEXT_STORE - } - - if len(upd.GetPath()) == 0 { - continue - } - scp, err := d.schemaClient.ToPath(ctx, upd.GetPath()) - if err != nil { - return err - } - switch len(scp.GetElem()) { - case 0: - continue - case 1: - if scp.GetElem()[0].GetName() == "" { - continue - } - } - tv, err := upd.Value() - if err != nil { - return err - } - ctv, err := converter.ConvertTypedValueToProto(ctx, scp, tv) - if err != nil { - return err - } - notification := &sdcpb.Notification{ - Timestamp: time.Now().UnixNano(), - Update: []*sdcpb.Update{{ - Path: scp, - Value: ctv, - }}, - } - rsp := &sdcpb.GetDataResponse{ - Notification: []*sdcpb.Notification{notification}, - } - select { - case <-ctx.Done(): - return ctx.Err() - case out <- rsp: - } - } - } - } - return nil -} - -func (d *Datastore) Subscribe(req *sdcpb.SubscribeRequest, stream sdcpb.DataServer_SubscribeServer) error { - ctx, cancel := context.WithCancel(stream.Context()) - defer cancel() - var err error - for _, subsc := range req.GetSubscription() { - err := d.doSubscribeOnce(ctx, subsc, stream) - if err != nil { - return err - } - } - err = stream.Send(&sdcpb.SubscribeResponse{ - Response: &sdcpb.SubscribeResponse_SyncResponse{ - SyncResponse: true, - }, - }) - if err != nil { - return err - } - // start periodic gets, TODO: optimize using cache RPC - wg := new(sync.WaitGroup) - wg.Add(len(req.GetSubscription())) - errCh := make(chan error, 1) - doneCh := make(chan struct{}) - for _, subsc := range req.GetSubscription() { - go func(subsc *sdcpb.Subscription) { - ticker := time.NewTicker(time.Duration(subsc.GetSampleInterval())) - defer ticker.Stop() - defer wg.Done() - for { - select { - case <-doneCh: - return - case <-ctx.Done(): - errCh <- ctx.Err() - return - case <-ticker.C: - err := d.doSubscribeOnce(ctx, subsc, stream) - if err != nil { - errCh <- err - close(doneCh) - return - } - } - } - }(subsc) - } - wg.Wait() - return nil -} - -func (d *Datastore) validateUpdate(ctx context.Context, upd *sdcpb.Update) error { - // 1.validate the path i.e check that the path exists - // 2.validate that the value is compliant with the schema - - // 1. validate the path - rsp, err := d.schemaClient.GetSchemaSdcpbPath(ctx, upd.GetPath()) - if err != nil { - return err - } - // 2. convert value to its YANG type - upd.Value, err = utils.ConvertTypedValueToYANGType(rsp.GetSchema(), upd.GetValue()) - if err != nil { - return err - } - // 2. validate value - val, err := utils.GetSchemaValue(upd.GetValue()) - if err != nil { - return err - } - switch obj := rsp.GetSchema().Schema.(type) { - case *sdcpb.SchemaElem_Container: - if !pathIsKeyAsLeaf(upd.GetPath()) && !obj.Container.IsPresence { - return fmt.Errorf("cannot set value on container %q object", obj.Container.Name) - } - // TODO: validate key as leaf - case *sdcpb.SchemaElem_Field: - if obj.Field.IsState { - return fmt.Errorf("cannot set state field: %v", obj.Field.Name) - } - err = validateFieldValue(obj.Field, val) - if err != nil { - return err - } - case *sdcpb.SchemaElem_Leaflist: - err = validateLeafListValue(obj.Leaflist, val) - if err != nil { - return err - } - } - return nil -} - -func validateFieldValue(f *sdcpb.LeafSchema, v any) error { - return validateLeafTypeValue(f.GetType(), v) -} - -func validateLeafTypeValue(lt *sdcpb.SchemaLeafType, v any) error { - switch lt.GetType() { - case "string": - // TODO: validate length and range - return nil - case "int8": - switch v := v.(type) { - case string: - _, err := strconv.ParseInt(v, 10, 8) - if err != nil { - return err - } - case int64: - if v > math.MaxInt8 || v < math.MinInt8 { - return fmt.Errorf("value %v out of bound for type %s", v, lt.GetType()) - } - default: - return fmt.Errorf("unexpected casted type %T in %v", v, lt.GetType()) - } - return nil - case "int16": - switch v := v.(type) { - case string: - _, err := strconv.ParseInt(v, 10, 16) - if err != nil { - return err - } - case int64: - if v > math.MaxInt16 || v < math.MinInt16 { - return fmt.Errorf("value %v out of bound for type %s", v, lt.GetType()) - } - default: - return fmt.Errorf("unexpected casted type %T in %v", v, lt.GetType()) - } - return nil - case "int32": - switch v := v.(type) { - case string: - _, err := strconv.ParseInt(v, 10, 32) - if err != nil { - return err - } - case int64: - if v > math.MaxInt32 || v < math.MinInt32 { - return fmt.Errorf("value %v out of bound for type %s", v, lt.GetType()) - } - default: - return fmt.Errorf("unexpected casted type %T in %v", v, lt.GetType()) - } - return nil - case "int64": - switch v := v.(type) { - case string: - _, err := strconv.ParseInt(v, 10, 64) - if err != nil { - return err - } - case int64: - // No need to do anything, same type - default: - return fmt.Errorf("unexpected casted type %T in %v", v, lt.GetType()) - } - return nil - case "uint8": - switch v := v.(type) { - case string: - _, err := strconv.ParseUint(v, 10, 8) - if err != nil { - return err - } - case uint64: - if v > math.MaxUint8 { - return fmt.Errorf("value %v out of bound for type %s", v, lt.GetType()) - } - default: - return fmt.Errorf("unexpected casted type %T in %v", v, lt.GetType()) - } - return nil - case "uint16": - switch v := v.(type) { - case string: - _, err := strconv.ParseUint(v, 10, 16) - if err != nil { - return err - } - case uint64: - if v > math.MaxUint16 { - return fmt.Errorf("value %v out of bound for type %s", v, lt.GetType()) - } - default: - return fmt.Errorf("unexpected casted type %T in %v", v, lt.GetType()) - } - return nil - case "uint32": - switch v := v.(type) { - case string: - _, err := strconv.ParseUint(v, 10, 32) - if err != nil { - return err - } - case uint64: - if v > math.MaxUint32 { - return fmt.Errorf("value %v out of bound for type %s", v, lt.GetType()) - } - default: - return fmt.Errorf("unexpected casted type %T in %v", v, lt.GetType()) - } - return nil - case "uint64": - switch v := v.(type) { - case string: - _, err := strconv.ParseUint(v, 10, 64) - if err != nil { - return err - } - case uint64: - // No need to do anything, same type - default: - return fmt.Errorf("unexpected casted type %T in %v", v, lt.GetType()) - } - return nil - case "boolean": - switch v := v.(type) { - case string: - _, err := strconv.ParseBool(v) - if err != nil { - return fmt.Errorf("value %v must be a boolean: %v", v, err) - } - case bool: - return nil - default: - return fmt.Errorf("unexpected casted type %T in %v", v, lt.GetType()) - } - return nil - case "enumeration": - valid := false - for _, vv := range lt.EnumNames { - if fmt.Sprintf("%s", v) == vv { - valid = true - break - } - } - if !valid { - return fmt.Errorf("value %q does not match enum type %q, must be one of [%s]", v, lt.TypeName, strings.Join(lt.EnumNames, ", ")) - } - return nil - case "union": - valid := false - for _, ut := range lt.GetUnionTypes() { - err := validateLeafTypeValue(ut, v) - if err == nil { - valid = true - break - } - } - if !valid { - return fmt.Errorf("value %v does not match union type %v", v, lt.TypeName) - } - return nil - case "identityref": - valid := false - identities := make([]string, 0, len(lt.IdentityPrefixesMap)) - for vv := range lt.IdentityPrefixesMap { - identities = append(identities, vv) - if fmt.Sprintf("%s", v) == vv { - valid = true - break - } - } - if !valid { - return fmt.Errorf("value %q does not match identityRef type %q, must be one of [%s]", v, lt.TypeName, strings.Join(identities, ", ")) - } - return nil - case "decimal64": - switch v := v.(type) { - case float64: // if it's a float64 then it's a valid decimal64 - case string: - if c := strings.Count(v, "."); c == 0 || c > 1 { - return fmt.Errorf("value %q is not a valid Decimal64", v) - } - case sdcpb.Decimal64, *sdcpb.Decimal64: - // No need to do anything, same type - default: - return fmt.Errorf("unexpected type for a Decimal64 value %q: %T", v, v) - } - return nil - case "leafref": - // TODO: does this need extra validation? - return nil - case "empty": - switch v.(type) { - case *emptypb.Empty: - return nil - } - return fmt.Errorf("value %v is not an empty JSON object '{}' so does not match empty type", v) - default: - return fmt.Errorf("unhandled type %v for value %q", lt.GetType(), v) - } -} - -func validateLeafListValue(ll *sdcpb.LeafListSchema, v any) error { - switch vTyped := v.(type) { - case *sdcpb.ScalarArray: - for _, elem := range vTyped.Element { - val, err := utils.GetSchemaValue(elem) - if err != nil { - return err - } - err = validateLeafTypeValue(ll.GetType(), val) - if err != nil { - return err - } - } - } - - return nil -} - -func (d *Datastore) doSubscribeOnce(ctx context.Context, subscription *sdcpb.Subscription, stream sdcpb.DataServer_SubscribeServer) error { - paths := make([][]string, 0, len(subscription.GetPath())) - for _, path := range subscription.GetPath() { - paths = append(paths, utils.ToStrings(path, false, false)) - } - - for _, store := range getStores(subscription) { - for upd := range d.cacheClient.ReadCh(ctx, d.config.Name, &cache.Opts{ - Store: store, - }, paths, 0) { - log.Debugf("ds=%s read path=%v from store=%v: %v", d.config.Name, paths, store, upd) - rsp, err := d.subscribeResponseFromCacheUpdate(ctx, upd) - if err != nil { - return err - } - log.Debugf("ds=%s sending subscribe response: %v", d.config.Name, rsp) - select { - case <-ctx.Done(): - return ctx.Err() - default: - err = stream.Send(rsp) - if err != nil { - return err - } - } - } - } - return nil -} - -func getStores(req proto.Message) []cachepb.Store { - var dt sdcpb.DataType - var candName string - switch req := req.(type) { - case *sdcpb.GetDataRequest: - if req.GetDatastore().GetType() == sdcpb.Type_INTENDED { - return []cachepb.Store{cachepb.Store_INTENDED} - } - dt = req.GetDataType() - candName = req.GetDatastore().GetName() - case *sdcpb.Subscription: - dt = req.GetDataType() - } - - var stores []cachepb.Store - switch dt { - case sdcpb.DataType_ALL: - stores = []cachepb.Store{cachepb.Store_CONFIG} - if candName == "" { - stores = append(stores, cachepb.Store_STATE) - } - case sdcpb.DataType_CONFIG: - stores = []cachepb.Store{cachepb.Store_CONFIG} - case sdcpb.DataType_STATE: - if candName == "" { - stores = []cachepb.Store{cachepb.Store_STATE} - } - } - return stores -} - -func (d *Datastore) subscribeResponseFromCacheUpdate(ctx context.Context, upd *cache.Update) (*sdcpb.SubscribeResponse, error) { - scp, err := d.schemaClient.ToPath(ctx, upd.GetPath()) - if err != nil { - return nil, err - } - tv, err := upd.Value() - if err != nil { - return nil, err - } - notification := &sdcpb.Notification{ - Timestamp: time.Now().UnixNano(), - Update: []*sdcpb.Update{{ - Path: scp, - Value: tv, - }}, - } - return &sdcpb.SubscribeResponse{ - Response: &sdcpb.SubscribeResponse_Update{ - Update: notification, - }, - }, nil -} diff --git a/pkg/datastore/datastore_rpc.go b/pkg/datastore/datastore_rpc.go index f56b65f5..5b248930 100644 --- a/pkg/datastore/datastore_rpc.go +++ b/pkg/datastore/datastore_rpc.go @@ -17,16 +17,11 @@ package datastore import ( "context" "errors" - "fmt" - "sort" - "strings" "sync" "time" - "github.com/sdcio/cache/proto/cachepb" sdcpb "github.com/sdcio/sdc-protos/sdcpb" log "github.com/sirupsen/logrus" - "golang.org/x/sync/semaphore" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/peer" @@ -38,14 +33,15 @@ import ( "github.com/sdcio/data-server/pkg/datastore/target" "github.com/sdcio/data-server/pkg/datastore/types" "github.com/sdcio/data-server/pkg/schema" - "github.com/sdcio/data-server/pkg/utils" + "github.com/sdcio/data-server/pkg/tree" + treetypes "github.com/sdcio/data-server/pkg/tree/types" ) type Datastore struct { // datastore config config *config.DatastoreConfig - cacheClient cache.Client + cacheClient cache.CacheClientBound // SBI target of this datastore sbi target.Target @@ -73,20 +69,39 @@ type Datastore struct { // TransactionManager transactionManager *types.TransactionManager + + // SyncTree + syncTree *tree.RootEntry + syncTreeMutex *sync.RWMutex + + // owned by sync + syncTreeCandidat *tree.RootEntry } // New creates a new datastore, its schema server client and initializes the SBI target // func New(c *config.DatastoreConfig, schemaServer *config.RemoteSchemaServer) *Datastore { -func New(ctx context.Context, c *config.DatastoreConfig, sc schema.Client, cc cache.Client, opts ...grpc.DialOption) *Datastore { +func New(ctx context.Context, c *config.DatastoreConfig, sc schema.Client, cc cache.Client, opts ...grpc.DialOption) (*Datastore, error) { + + scb := schemaClient.NewSchemaClientBound(c.Schema, sc) + tc := tree.NewTreeContext(scb, tree.RunningIntentName) + syncTreeRoot, err := tree.NewTreeRoot(ctx, tc) + if err != nil { + return nil, err + } + + ccb := cache.NewCacheClientBound(c.Name, cc) + ds := &Datastore{ config: c, - schemaClient: schemaClient.NewSchemaClientBound(c.Schema.GetSchema(), sc), - cacheClient: cc, + schemaClient: scb, + cacheClient: ccb, m: &sync.RWMutex{}, md: &sync.RWMutex{}, dmutex: &sync.Mutex{}, deviationClients: make(map[string]sdcpb.DataServer_WatchDeviationsServer), currentIntentsDeviations: make(map[string][]*sdcpb.WatchDeviationResponse), + syncTree: syncTreeRoot, + syncTreeMutex: &sync.RWMutex{}, } ds.transactionManager = types.NewTransactionManager(NewDatastoreRollbackAdapter(ds)) @@ -97,7 +112,7 @@ func New(ctx context.Context, c *config.DatastoreConfig, sc schema.Client, cc ca ds.cfn = cancel // create cache instance if needed - // this is a blocking call + // this is a blocking call ds.initCache(ctx) go func() { @@ -117,25 +132,23 @@ func New(ctx context.Context, c *config.DatastoreConfig, sc schema.Client, cc ca // start deviation goroutine ds.DeviationMgr(ctx) }() - return ds + return ds, nil +} + +func (d *Datastore) IntentsList(ctx context.Context) ([]string, error) { + return d.cacheClient.IntentsList(ctx) } func (d *Datastore) initCache(ctx context.Context) { -START: - ok, err := d.cacheClient.Exists(ctx, d.config.Name) - if err != nil { - log.Errorf("failed to check cache instance %s, %s", d.config.Name, err) - time.Sleep(time.Second) - goto START - } - if ok { + + exists := d.cacheClient.InstanceExists(ctx) + if exists { log.Debugf("cache %q already exists", d.config.Name) return } - log.Infof("cache %s does not exist, creating it", d.config.Name) CREATE: - err = d.cacheClient.Create(ctx, d.config.Name, false, false) + err := d.cacheClient.InstanceCreate(ctx) if err != nil { log.Errorf("failed to create cache %s: %v", d.config.Name, err) time.Sleep(time.Second) @@ -181,42 +194,8 @@ func (d *Datastore) Config() *config.DatastoreConfig { return d.config } -func (d *Datastore) Candidates(ctx context.Context) ([]*sdcpb.DataStore, error) { - cand, err := d.cacheClient.GetCandidates(ctx, d.Name()) - if err != nil { - return nil, err - } - rsp := make([]*sdcpb.DataStore, 0, len(cand)) - for _, cd := range cand { - rsp = append(rsp, &sdcpb.DataStore{ - Type: sdcpb.Type_CANDIDATE, - Name: cd.CandidateName, - Owner: cd.Owner, - Priority: cd.Priority, - }) - } - return rsp, nil -} - -func (d *Datastore) Discard(ctx context.Context, req *sdcpb.DiscardRequest) error { - return d.cacheClient.Discard(ctx, req.GetName(), req.Datastore.GetName()) -} - -func (d *Datastore) CreateCandidate(ctx context.Context, ds *sdcpb.DataStore) error { - if ds.GetPriority() < 0 { - return fmt.Errorf("invalid priority value must be >0") - } - if ds.GetPriority() <= 0 { - ds.Priority = 1 - } - if ds.GetOwner() == "" { - ds.Owner = DefaultOwner - } - return d.cacheClient.CreateCandidate(ctx, d.Name(), ds.GetName(), ds.GetOwner(), ds.GetPriority()) -} - -func (d *Datastore) DeleteCandidate(ctx context.Context, name string) error { - return d.cacheClient.DeleteCandidate(ctx, d.Name(), name) +func (d *Datastore) Delete(ctx context.Context) error { + return d.cacheClient.InstanceDelete(ctx) } func (d *Datastore) ConnectionState() *target.TargetStatus { @@ -241,21 +220,21 @@ func (d *Datastore) Stop() error { return nil } -func (d *Datastore) DeleteCache(ctx context.Context) error { - return d.cacheClient.Delete(ctx, d.config.Name) -} - func (d *Datastore) Sync(ctx context.Context) { - // this semaphore controls the number of concurrent writes to the cache - sem := semaphore.NewWeighted(d.config.Sync.WriteWorkers) go d.sbi.Sync(ctx, d.config.Sync, d.synCh, ) var err error - var pruneID string -MAIN: + var startTs int64 + + d.syncTreeCandidat, err = tree.NewTreeRoot(ctx, tree.NewTreeContext(d.schemaClient, tree.RunningIntentName)) + if err != nil { + log.Errorf("creating a new synctree candidate: %v", err) + return + } + for { select { case <-ctx.Done(): @@ -264,179 +243,64 @@ MAIN: } return case syncup := <-d.synCh: - if syncup.Start { + switch { + case syncup.Start: log.Debugf("%s: sync start", d.Name()) - for { - pruneID, err = d.cacheClient.CreatePruneID(ctx, d.Name(), syncup.Force) - if err != nil { - log.Errorf("datastore %s failed to create prune ID: %v", d.Name(), err) - time.Sleep(time.Second) - continue // retry - } - continue MAIN - } - } - if syncup.End && pruneID != "" { - log.Debugf("%s: sync end", d.Name()) - for { - err = d.cacheClient.ApplyPrune(ctx, d.Name(), pruneID) - if err != nil { - log.Errorf("datastore %s failed to prune cache after update: %v", d.Name(), err) - time.Sleep(time.Second) - continue // retry - } - break - } - log.Debugf("%s: sync resetting pruneID", d.Name()) - pruneID = "" - continue // MAIN FOR loop - } - // a regular notification - log.Debugf("%s: sync acquire semaphore", d.Name()) - err = sem.Acquire(ctx, 1) - if err != nil { - if errors.Is(err, context.Canceled) { - log.Infof("datastore %s sync stopped", d.config.Name) - return - } - log.Errorf("failed to acquire semaphore: %v", err) - continue - } - log.Debugf("%s: sync acquired semaphore", d.Name()) - go d.storeSyncMsg(ctx, syncup, sem) - } - } -} + startTs = time.Now().Unix() -func isState(r *sdcpb.GetSchemaResponse) bool { - switch r := r.Schema.Schema.(type) { - case *sdcpb.SchemaElem_Container: - return r.Container.IsState - case *sdcpb.SchemaElem_Field: - return r.Field.IsState - case *sdcpb.SchemaElem_Leaflist: - return r.Leaflist.IsState - } - return false -} - -func (d *Datastore) storeSyncMsg(ctx context.Context, syncup *target.SyncUpdate, sem *semaphore.Weighted) { - defer sem.Release(1) - - converter := utils.NewConverter(d.schemaClient) - - cNotification, err := converter.ConvertNotificationTypedValues(ctx, syncup.Update) - if err != nil { - log.Errorf("failed to convert notification typedValue: %v", err) - return - } + case syncup.End: + log.Debugf("%s: sync end", d.Name()) - upds := NewSdcpbUpdateDedup() - for _, x := range cNotification.GetUpdate() { - addUpds, err := converter.ExpandUpdateKeysAsLeaf(ctx, x) - if err != nil { - continue - } - upds.AddUpdate(x) - upds.AddUpdates(addUpds) - } - cNotification.Update = upds.Updates() + startTs = 0 - for _, x := range cNotification.GetUpdate() { - fmt.Printf("%s\n", x.String()) - } + d.syncTreeMutex.Lock() + d.syncTree = d.syncTreeCandidat + d.syncTreeMutex.Unlock() - for _, del := range cNotification.GetDelete() { - store := cachepb.Store_CONFIG - if d.config.Sync != nil && d.config.Sync.Validate { - scRsp, err := d.schemaClient.GetSchemaSdcpbPath(ctx, del) - if err != nil { - log.Errorf("datastore %s failed to get schema for delete path %v: %v", d.config.Name, del, err) - continue - } - if isState(scRsp) { - store = cachepb.Store_STATE - } - } - delPath := utils.ToStrings(del, false, false) - rctx, cancel := context.WithTimeout(ctx, time.Minute) // TODO: - defer cancel() - err = d.cacheClient.Modify(rctx, d.Config().Name, - &cache.Opts{ - Store: store, - }, - [][]string{delPath}, nil) - if err != nil { - log.Errorf("datastore %s failed to delete path %v: %v", d.config.Name, delPath, err) - } - } + // create new syncTreeCandidat + d.syncTreeCandidat, err = tree.NewTreeRoot(ctx, tree.NewTreeContext(d.schemaClient, tree.RunningIntentName)) + if err != nil { + log.Errorf("creating a new synctree candidate: %v", err) + return + } - for _, upd := range cNotification.GetUpdate() { - store := cachepb.Store_CONFIG - if d.config.Sync != nil && d.config.Sync.Validate { - scRsp, err := d.schemaClient.GetSchemaSdcpbPath(ctx, upd.GetPath()) - if err != nil { - log.Errorf("datastore %s failed to get schema for update path %v: %v", d.config.Name, upd.GetPath(), err) - continue - } - if isState(scRsp) { - store = cachepb.Store_STATE + // export and write to cache + runningExport, err := d.syncTree.TreeExport(tree.RunningIntentName, tree.RunningValuesPrio) + if err != nil { + log.Error(err) + continue + } + err = d.cacheClient.IntentModify(ctx, runningExport) + if err != nil { + log.Errorf("issue modifying running cache content: %v", err) + continue + } + default: + if startTs == 0 { + startTs = time.Now().Unix() + } + err := d.writeToSyncTreeCandidate(ctx, syncup.Update.GetUpdate(), startTs) + if err != nil { + log.Errorf("failed to write to sync tree: %v", err) + } } } - // TODO:[KR] convert update typedValue if needed - cUpd, err := d.cacheClient.NewUpdate(upd) - if err != nil { - log.Errorf("datastore %s failed to create update from %v: %v", d.config.Name, upd, err) - continue - } - - rctx, cancel := context.WithTimeout(ctx, time.Minute) // TODO:[KR] make this timeout configurable ? - defer cancel() - err = d.cacheClient.Modify(rctx, d.Config().Name, &cache.Opts{ - Store: store, - }, nil, []*cache.Update{cUpd}) - if err != nil { - log.Errorf("datastore %s failed to send modify request to cache: %v", d.config.Name, err) - } } } -type SdcpbUpdateDedup struct { - lookup map[string]*sdcpb.Update -} - -func NewSdcpbUpdateDedup() *SdcpbUpdateDedup { - return &SdcpbUpdateDedup{ - lookup: map[string]*sdcpb.Update{}, +func (d *Datastore) writeToSyncTreeCandidate(ctx context.Context, updates []*sdcpb.Update, ts int64) error { + upds, err := treetypes.ExpandAndConvertIntent(ctx, d.schemaClient, tree.RunningIntentName, tree.RunningValuesPrio, updates, ts) + if err != nil { + return err } -} -func (s *SdcpbUpdateDedup) AddUpdates(upds []*sdcpb.Update) { for _, upd := range upds { - s.AddUpdate(upd) - } -} - -func (s *SdcpbUpdateDedup) AddUpdate(upd *sdcpb.Update) { - path := upd.Path.String() - if _, exists := s.lookup[path]; exists { - return - } - s.lookup[path] = upd -} - -func (s *SdcpbUpdateDedup) Updates() []*sdcpb.Update { - result := make([]*sdcpb.Update, 0, len(s.lookup)) - - for _, v := range s.lookup { - result = append(result, v) + _, err := d.syncTreeCandidat.AddUpdateRecursive(ctx, upd, treetypes.NewUpdateInsertFlags()) + if err != nil { + return err + } } - return result -} - -func (d *Datastore) validatePath(ctx context.Context, p *sdcpb.Path) error { - _, err := d.schemaClient.GetSchemaSdcpbPath(ctx, p) - return err + return nil } func (d *Datastore) WatchDeviations(req *sdcpb.WatchDeviationRequest, stream sdcpb.DataServer_WatchDeviationsServer) error { @@ -467,261 +331,113 @@ func (d *Datastore) StopDeviationsWatch(peer string) { func (d *Datastore) DeviationMgr(ctx context.Context) { log.Infof("%s: starting deviationMgr...", d.Name()) ticker := time.NewTicker(30 * time.Second) - defer ticker.Stop() + defer func() { + ticker.Stop() + }() for { select { case <-ctx.Done(): return case <-ticker.C: - // TODO: send deviation START d.m.RLock() - // copy deviation streams - dm := make(map[string]sdcpb.DataServer_WatchDeviationsServer) - for n, devStream := range d.deviationClients { - dm[n] = devStream + deviationClients := make([]sdcpb.DataServer_WatchDeviationsServer, 0, len(d.deviationClients)) + for _, devStream := range d.deviationClients { + deviationClients = append(deviationClients, devStream) } d.m.RUnlock() - d.runDeviationUpdate(ctx, dm) - } - } -} - -func (d *Datastore) runDeviationUpdate(ctx context.Context, dm map[string]sdcpb.DataServer_WatchDeviationsServer) { - - sep := "/" - - // send deviation START - for _, dc := range dm { - err := dc.Send(&sdcpb.WatchDeviationResponse{ - Name: d.Name(), - Event: sdcpb.DeviationEvent_START, - }) - if err != nil { - log.Errorf("%s: failed to send deviation start: %v", d.Name(), err) - continue - } - } - // collect intent deviations and store them for clearing - newDeviations := make(map[string][]*sdcpb.WatchDeviationResponse) - - configPaths := map[string]struct{}{} - - // go through config and calculate deviations - for upd := range d.cacheClient.ReadCh(ctx, d.Name(), &cache.Opts{Store: cachepb.Store_CONFIG}, [][]string{nil}, 0) { - // save the updates path as an already checked path - configPaths[strings.Join(upd.GetPath(), sep)] = struct{}{} - - v, err := upd.Value() - if err != nil { - log.Errorf("%s: failed to convert value: %v", d.Name(), err) - continue - } - - intentsUpdates := d.cacheClient.Read(ctx, d.Name(), &cache.Opts{ - Store: cachepb.Store_INTENDED, - Owner: "", - Priority: 0, - PriorityCount: 0, - }, [][]string{upd.GetPath()}, 0) - if len(intentsUpdates) == 0 { - log.Debugf("%s: has unhandled config %v: %v", d.Name(), upd.GetPath(), v) - // TODO: generate an unhandled config deviation - sp, err := d.schemaClient.ToPath(ctx, upd.GetPath()) - if err != nil { - log.Errorf("%s: failed to convert cached path to xpath: %v", d.Name(), err) - } - - rsp := &sdcpb.WatchDeviationResponse{ - Name: d.Name(), - Intent: upd.Owner(), - Event: sdcpb.DeviationEvent_UPDATE, - Reason: sdcpb.DeviationReason_UNHANDLED, - Path: sp, - CurrentValue: v, - } - for _, dc := range dm { - err = dc.Send(rsp) - if err != nil { - log.Errorf("%s: failed to send deviation: %v", d.Name(), err) - continue - } - } - continue - } - // NOT_APPLIED or OVERRULED deviation - // sort intent updates by priority/TS - sort.Slice(intentsUpdates, func(i, j int) bool { - if intentsUpdates[i].Priority() == intentsUpdates[j].Priority() { - return intentsUpdates[i].TS() < intentsUpdates[j].TS() - } - return intentsUpdates[i].Priority() < intentsUpdates[j].Priority() - }) - // first intent - // // compare values with config - fiv, err := intentsUpdates[0].Value() - if err != nil { - log.Errorf("%s: failed to convert intent value: %v", d.Name(), err) - continue - } - sp, err := d.schemaClient.ToPath(ctx, intentsUpdates[0].GetPath()) - if err != nil { - log.Errorf("%s: failed to convert path %v: %v", d.Name(), intentsUpdates[0].GetPath(), err) - continue - } - scRsp, err := d.schemaClient.GetSchemaSdcpbPath(ctx, sp) - if err != nil { - log.Errorf("%s: failed to get path schema: %v ", d.Name(), err) - continue - } - nfiv, err := utils.TypedValueToYANGType(fiv, scRsp.GetSchema()) - if err != nil { - log.Errorf("%s: failed to convert value to its YANG type: %v ", d.Name(), err) - continue - } - if !utils.EqualTypedValues(nfiv, v) { - log.Debugf("%s: intent %s has a NOT_APPLIED deviation: configured: %v -> expected %v", - d.Name(), intentsUpdates[0].Owner(), v, nfiv) - rsp := &sdcpb.WatchDeviationResponse{ - Name: d.Name(), - Intent: intentsUpdates[0].Owner(), - Event: sdcpb.DeviationEvent_UPDATE, - Reason: sdcpb.DeviationReason_NOT_APPLIED, - Path: sp, - ExpectedValue: nfiv, - CurrentValue: v, - } - for _, dc := range dm { - err = dc.Send(rsp) - if err != nil { - log.Errorf("%s: failed to send deviation: %v", d.Name(), err) - continue - } - } - xp := utils.ToXPath(sp, false) - if _, ok := newDeviations[xp]; !ok { - newDeviations[xp] = make([]*sdcpb.WatchDeviationResponse, 0, 1) - } - newDeviations[xp] = append(newDeviations[xp], rsp) - } - // remaining intents - for _, intUpd := range intentsUpdates[1:] { - iv, err := intUpd.Value() - if err != nil { - log.Errorf("%s: failed to convert intent value: %v", d.Name(), err) + if len(deviationClients) == 0 { continue } - sp, err := d.schemaClient.ToPath(ctx, intUpd.GetPath()) - if err != nil { - log.Errorf("%s: failed to convert path %v: %v", d.Name(), intUpd.GetPath(), err) - continue + for _, dc := range deviationClients { + dc.Send(&sdcpb.WatchDeviationResponse{ + Name: d.config.Name, + Event: sdcpb.DeviationEvent_START, + }) } - scRsp, err := d.schemaClient.GetSchemaSdcpbPath(ctx, sp) + deviationChan, err := d.calculateDeviations(ctx) if err != nil { - log.Errorf("%s: failed to get path schema: %v ", d.Name(), err) + log.Error(err) continue } - niv, err := utils.TypedValueToYANGType(iv, scRsp.GetSchema()) - if err != nil { - log.Errorf("%s: failed to convert value to its YANG type: %v ", d.Name(), err) - continue + d.SendDeviations(deviationChan, deviationClients) + for _, dc := range deviationClients { + dc.Send(&sdcpb.WatchDeviationResponse{ + Name: d.config.Name, + Event: sdcpb.DeviationEvent_END, + }) } - if !utils.EqualTypedValues(nfiv, niv) { - log.Debugf("%s: intent %s has an OVERRULED deviation: ruling intent has: %v -> overruled intent has: %v", - d.Name(), intUpd.Owner(), nfiv, niv) - // TODO: generate an OVERRULED deviation - - rsp := &sdcpb.WatchDeviationResponse{ - Name: d.Name(), - Intent: intUpd.Owner(), - Event: sdcpb.DeviationEvent_UPDATE, - Reason: sdcpb.DeviationReason_OVERRULED, - Path: sp, - ExpectedValue: iv, - CurrentValue: fiv, - } - for _, dc := range dm { - err = dc.Send(rsp) - if err != nil { - log.Errorf("%s: failed to send deviation: %v", d.Name(), err) - continue - } - } - xp := utils.ToXPath(sp, false) - if _, ok := newDeviations[xp]; !ok { - newDeviations[xp] = make([]*sdcpb.WatchDeviationResponse, 0, 1) - } - newDeviations[xp] = append(newDeviations[xp], rsp) + } + } +} + +func (d *Datastore) SendDeviations(ch <-chan *treetypes.DeviationEntry, deviationClients []sdcpb.DataServer_WatchDeviationsServer) { + wg := &sync.WaitGroup{} + for { + select { + case de, ok := <-ch: + if !ok { + wg.Wait() + return } + wg.Add(1) + go func(de DeviationEntry, dcs []sdcpb.DataServer_WatchDeviationsServer) { + for _, dc := range dcs { + dc.Send(&sdcpb.WatchDeviationResponse{ + Name: d.config.Name, + Intent: de.IntentName(), + Event: sdcpb.DeviationEvent_UPDATE, + Reason: sdcpb.DeviationReason(de.Reason()), + Path: de.Path(), + ExpectedValue: de.ExpectedValue(), + CurrentValue: de.CurrentValue(), + }) + } + wg.Done() + }(de, deviationClients) } } +} + +type DeviationEntry interface { + IntentName() string + Reason() treetypes.DeviationReason + Path() *sdcpb.Path + CurrentValue() *sdcpb.TypedValue + ExpectedValue() *sdcpb.TypedValue +} + +func (d *Datastore) calculateDeviations(ctx context.Context) (<-chan *treetypes.DeviationEntry, error) { + deviationChan := make(chan *treetypes.DeviationEntry, 10) - intendedUpdates, err := d.readStoreKeysMeta(ctx, cachepb.Store_INTENDED) + d.syncTreeMutex.RLock() + defer d.syncTreeMutex.RUnlock() + + deviationTree, err := d.syncTree.DeepCopy(ctx) if err != nil { - log.Error(err) - return + return nil, err } - for _, upds := range intendedUpdates { - for _, upd := range upds { - path := strings.Join(upd.GetPath(), sep) - if _, exists := configPaths[path]; !exists { - - // iv, err := upd.Value() - // if err != nil { - // log.Errorf("%s: failed to convert intent value: %v", d.Name(), err) - // continue - // } + addedIntentNames, err := d.LoadAllButRunningIntents(ctx, deviationTree) + if err != nil { + return nil, err + } - path, err := d.schemaClient.ToPath(ctx, upd.GetPath()) - if err != nil { - log.Error(err) - continue - } - // scRsp, err := d.getSchema(ctx, path) - // if err != nil { - // log.Errorf("%s: failed to get path schema: %v ", d.Name(), err) - // continue - // } - // niv, err := d.typedValueToYANGType(iv, scRsp.GetSchema()) - // if err != nil { - // log.Errorf("%s: failed to convert value to its YANG type: %v ", d.Name(), err) - // continue - // } - - rsp := &sdcpb.WatchDeviationResponse{ - Name: d.Name(), - Intent: upd.Owner(), - Event: sdcpb.DeviationEvent_UPDATE, - Reason: sdcpb.DeviationReason_NOT_APPLIED, - Path: path, - ExpectedValue: nil, // TODO this need to be fixed - CurrentValue: nil, - } - for _, dc := range dm { - err = dc.Send(rsp) - if err != nil { - log.Errorf("%s: failed to send deviation: %v", d.Name(), err) - continue - } - } - } - } + // Send IntentExists + for _, n := range addedIntentNames { + deviationChan <- treetypes.NewDeviationEntry(n, treetypes.DeviationReasonIntentExists, nil) } - // send deviation event END - for _, dc := range dm { - err := dc.Send(&sdcpb.WatchDeviationResponse{ - Name: d.Name(), - Event: sdcpb.DeviationEvent_END, - }) - if err != nil { - log.Errorf("%s: failed to send deviation end: %v", d.Name(), err) - continue - } + err = deviationTree.FinishInsertionPhase(ctx) + if err != nil { + return nil, err } - d.md.Lock() - d.currentIntentsDeviations = newDeviations - d.md.Unlock() + + go func() { + deviationTree.GetDeviations(deviationChan) + close(deviationChan) + }() + + return deviationChan, nil } // DatastoreRollbackAdapter implements the types.RollbackInterface and encapsulates the Datastore. diff --git a/pkg/datastore/intent_rpc.go b/pkg/datastore/intent_rpc.go index 9ebe1a73..f14d62be 100644 --- a/pkg/datastore/intent_rpc.go +++ b/pkg/datastore/intent_rpc.go @@ -18,17 +18,15 @@ import ( "context" "errors" "fmt" - "sort" - "strconv" - "strings" - "github.com/sdcio/cache/proto/cachepb" + "github.com/beevik/etree" sdcpb "github.com/sdcio/sdc-protos/sdcpb" log "github.com/sirupsen/logrus" - "google.golang.org/protobuf/proto" - "github.com/sdcio/data-server/pkg/cache" "github.com/sdcio/data-server/pkg/datastore/target" + "github.com/sdcio/data-server/pkg/tree" + "github.com/sdcio/data-server/pkg/tree/importer/proto" + "github.com/sdcio/data-server/pkg/tree/types" ) var rawIntentPrefix = "__raw_intent__" @@ -39,69 +37,6 @@ const ( var ErrIntentNotFound = errors.New("intent not found") -func (d *Datastore) GetIntent(ctx context.Context, req *sdcpb.GetIntentRequest) (*sdcpb.GetIntentResponse, error) { - r, err := d.getRawIntent(ctx, req.GetIntent(), req.GetPriority()) - if err != nil { - return nil, err - } - - rsp := &sdcpb.GetIntentResponse{ - Name: d.Name(), - Intent: &sdcpb.Intent{ - Intent: r.GetIntent(), - Priority: r.GetPriority(), - Update: r.GetUpdate(), - }, - } - return rsp, nil -} - -// func (d *Datastore) SetIntent(ctx context.Context, req *sdcpb.SetIntentRequest) (*sdcpb.SetIntentResponse, error) { -// if !d.intentMutex.TryLock() { -// return nil, status.Errorf(codes.ResourceExhausted, "datastore %s has an ongoing SetIntentRequest", d.Name()) -// } -// defer d.intentMutex.Unlock() - -// log.Infof("received SetIntentRequest: ds=%s intent=%s", req.GetName(), req.GetIntent()) -// now := time.Now().UnixNano() -// candidateName := fmt.Sprintf("%s-%d", req.GetIntent(), now) -// err := d.CreateCandidate(ctx, &sdcpb.DataStore{ -// Type: sdcpb.Type_CANDIDATE, -// Name: candidateName, -// Owner: req.GetIntent(), -// Priority: req.GetPriority(), -// }) -// if err != nil { -// return nil, err -// } -// defer func() { -// // delete candidate -// err := d.cacheClient.DeleteCandidate(ctx, d.Name(), candidateName) -// if err != nil { -// log.Errorf("%s: failed to delete candidate %s: %v", d.Name(), candidateName, err) -// } -// }() - -// setIntentResponse, err := d.SetIntentUpdate(ctx, req, candidateName) -// if err != nil { -// log.Errorf("%s: failed to SetIntentUpdate: %v", d.Name(), err) -// return nil, err -// } - -// return setIntentResponse, nil -// } - -func (d *Datastore) ListIntent(ctx context.Context, req *sdcpb.ListIntentRequest) (*sdcpb.ListIntentResponse, error) { - intents, err := d.listRawIntent(ctx) - if err != nil { - return nil, err - } - return &sdcpb.ListIntentResponse{ - Name: req.GetName(), - Intent: intents, - }, nil -} - func (d *Datastore) applyIntent(ctx context.Context, source target.TargetSource) (*sdcpb.SetDataResponse, error) { var err error @@ -121,107 +56,62 @@ func (d *Datastore) applyIntent(ctx context.Context, source target.TargetSource) return rsp, nil } -func (d *Datastore) saveRawIntent(ctx context.Context, intentName string, req *sdcpb.SetIntentRequest) error { - b, err := proto.Marshal(req) - if err != nil { - return err - } - // - rin := rawIntentName(intentName, req.GetPriority()) - upd, err := d.cacheClient.NewUpdate( - &sdcpb.Update{ - Path: &sdcpb.Path{ - Elem: []*sdcpb.PathElem{{Name: rin}}, - }, - Value: &sdcpb.TypedValue{ - Value: &sdcpb.TypedValue_BytesVal{BytesVal: b}, - }, - }, - ) +func (d *Datastore) GetIntent(ctx context.Context, intentName string) (GetIntentResponse, error) { + root, err := tree.NewTreeRoot(ctx, tree.NewTreeContext(d.schemaClient, intentName)) if err != nil { - return err - } - err = d.cacheClient.Modify(ctx, d.config.Name, - &cache.Opts{ - Store: cachepb.Store_INTENTS, - }, - nil, - []*cache.Update{upd}) - if err != nil { - return err + return nil, err } - return nil -} -func (d *Datastore) getRawIntent(ctx context.Context, intentName string, priority int32) (*sdcpb.SetIntentRequest, error) { - rin := rawIntentName(intentName, priority) - upds := d.cacheClient.Read(ctx, d.config.Name, &cache.Opts{ - Store: cachepb.Store_INTENTS, - }, [][]string{{rin}}, 0) - if len(upds) == 0 { - return nil, ErrIntentNotFound + tp, err := d.cacheClient.IntentGet(ctx, intentName) + if err != nil { + return nil, err } + protoImporter := proto.NewProtoTreeImporter(tp.GetRoot()) - val, err := upds[0].Value() + err = root.ImportConfig(ctx, nil, protoImporter, tp.GetIntentName(), tp.GetPriority(), types.NewUpdateInsertFlags()) if err != nil { return nil, err } - req := &sdcpb.SetIntentRequest{} - err = proto.Unmarshal(val.GetBytesVal(), req) + + err = root.FinishInsertionPhase(ctx) if err != nil { return nil, err } - return req, nil + return newTreeRootToGetIntentResponse(root), nil } -func (d *Datastore) listRawIntent(ctx context.Context) ([]*sdcpb.Intent, error) { - upds := d.cacheClient.Read(ctx, d.config.Name, &cache.Opts{ - Store: cachepb.Store_INTENTS, - KeysOnly: true, - }, [][]string{{"*"}}, 0) - numUpds := len(upds) - if numUpds == 0 { - return nil, nil - } - intents := make([]*sdcpb.Intent, 0, numUpds) - for _, upd := range upds { - if len(upd.GetPath()) == 0 { - return nil, fmt.Errorf("malformed raw intent name: %q", upd.GetPath()[0]) - } - intentRawName := strings.TrimPrefix(upd.GetPath()[0], rawIntentPrefix) - intentNameComp := strings.Split(intentRawName, intentRawNameSep) - inc := len(intentNameComp) - if inc < 2 { - return nil, fmt.Errorf("malformed raw intent name: %q", upd.GetPath()[0]) - } - pr, err := strconv.Atoi(intentNameComp[inc-1]) - if err != nil { - return nil, fmt.Errorf("malformed raw intent name: %q: %v", upd.GetPath()[0], err) - } - in := &sdcpb.Intent{ - Intent: strings.Join(intentNameComp[:inc-1], intentRawNameSep), - Priority: int32(pr), - } - intents = append(intents, in) +type GetIntentResponse interface { + // ToJson returns the Tree contained structure as JSON + // use e.g. json.MarshalIndent() on the returned struct + ToJson() (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) + ToProtoUpdates(ctx context.Context) ([]*sdcpb.Update, error) +} + +type treeRootToGetIntentResponse struct { + root *tree.RootEntry +} + +func newTreeRootToGetIntentResponse(root *tree.RootEntry) *treeRootToGetIntentResponse { + return &treeRootToGetIntentResponse{ + root: root, } - sort.Slice(intents, func(i, j int) bool { - if intents[i].GetPriority() == intents[j].GetPriority() { - return intents[i].GetIntent() < intents[j].GetIntent() - } - return intents[i].GetPriority() < intents[j].GetPriority() - }) - return intents, nil } -func (d *Datastore) deleteRawIntent(ctx context.Context, intentName string, priority int32) error { - return d.cacheClient.Modify(ctx, d.config.Name, - &cache.Opts{ - Store: cachepb.Store_INTENTS, - }, - [][]string{{rawIntentName(intentName, priority)}}, - nil) +func (t *treeRootToGetIntentResponse) ToJson() (any, error) { + return t.root.ToJson(false) } -func rawIntentName(name string, pr int32) string { - return fmt.Sprintf("%s%s%s%d", rawIntentPrefix, name, intentRawNameSep, pr) +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/target/gnmi.go b/pkg/datastore/target/gnmi.go index 3f09e2c5..579d4405 100644 --- a/pkg/datastore/target/gnmi.go +++ b/pkg/datastore/target/gnmi.go @@ -26,8 +26,8 @@ import ( "github.com/AlekSi/pointer" "github.com/openconfig/gnmi/proto/gnmi" gapi "github.com/openconfig/gnmic/pkg/api" - gtarget "github.com/openconfig/gnmic/pkg/target" - "github.com/openconfig/gnmic/pkg/types" + gtarget "github.com/openconfig/gnmic/pkg/api/target" + "github.com/openconfig/gnmic/pkg/api/types" sdcpb "github.com/sdcio/sdc-protos/sdcpb" log "github.com/sirupsen/logrus" "google.golang.org/grpc" @@ -376,9 +376,6 @@ func (t *gnmiTarget) getSync(ctx context.Context, gnmiSync *config.SyncProtocol, Name: gnmiSync.Name, Path: paths, DataType: sdcpb.DataType_CONFIG, - Datastore: &sdcpb.DataStore{ - Type: sdcpb.Type_MAIN, - }, Encoding: sdcpb.Encoding(sdcpbEncoding(gnmiSync.Encoding)), } @@ -413,6 +410,7 @@ func (t *gnmiTarget) internalGetSync(ctx context.Context, req *sdcpb.GetDataRequ Start: true, } notificationsCount := 0 + for _, n := range resp.GetNotification() { syncCh <- &SyncUpdate{ Update: n, diff --git a/pkg/datastore/target/nc.go b/pkg/datastore/target/nc.go index 4cea0702..9ab93740 100644 --- a/pkg/datastore/target/nc.go +++ b/pkg/datastore/target/nc.go @@ -65,14 +65,7 @@ func (t *ncTarget) Get(ctx context.Context, req *sdcpb.GetDataRequest) (*sdcpb.G if !t.Status().IsConnected() { return nil, fmt.Errorf("%s", TargetStatusNotConnected) } - var source string - - switch req.Datastore.Type { - case sdcpb.Type_MAIN: - source = "running" - case sdcpb.Type_CANDIDATE: - source = "candidate" - } + source := "running" // init a new XMLConfigBuilder for the pathfilter pathfilterXmlBuilder := netconf.NewXMLConfigBuilder(t.schemaClient, @@ -205,9 +198,6 @@ func (t *ncTarget) internalSync(ctx context.Context, sc *config.SyncProtocol, fo Name: sc.Name, Path: paths, DataType: sdcpb.DataType_CONFIG, - Datastore: &sdcpb.DataStore{ - Type: sdcpb.Type_MAIN, - }, } // execute netconf get diff --git a/pkg/datastore/target/nc_test.go b/pkg/datastore/target/nc_test.go index 20c9c3de..7fc82359 100644 --- a/pkg/datastore/target/nc_test.go +++ b/pkg/datastore/target/nc_test.go @@ -159,9 +159,6 @@ func Test_ncTarget_Get(t *testing.T) { args: args{ ctx: context.Background(), req: &sdcpb.GetDataRequest{ - Datastore: &sdcpb.DataStore{ - Type: sdcpb.Type_MAIN, - }, Path: []*sdcpb.Path{ { Elem: []*sdcpb.PathElem{ diff --git a/pkg/datastore/transaction_rpc.go b/pkg/datastore/transaction_rpc.go index 9b26782a..4a803307 100644 --- a/pkg/datastore/transaction_rpc.go +++ b/pkg/datastore/transaction_rpc.go @@ -4,64 +4,27 @@ import ( "context" "errors" "fmt" - "slices" "strings" "time" - "github.com/sdcio/cache/proto/cachepb" - "github.com/sdcio/data-server/pkg/cache" + "github.com/sdcio/data-server/pkg/config" "github.com/sdcio/data-server/pkg/datastore/types" "github.com/sdcio/data-server/pkg/tree" - "github.com/sdcio/data-server/pkg/utils" + treeproto "github.com/sdcio/data-server/pkg/tree/importer/proto" + "github.com/sdcio/data-server/pkg/tree/tree_persist" + treetypes "github.com/sdcio/data-server/pkg/tree/types" sdcpb "github.com/sdcio/sdc-protos/sdcpb" log "github.com/sirupsen/logrus" - "google.golang.org/protobuf/proto" ) var ( - ErrDatastoreLocked = errors.New("Datastore is locked, other action is ongoing.") + ErrDatastoreLocked = errors.New("Datastore is locked, other action is ongoing") + ErrValidationError = errors.New("validation error") ) -// expandAndConvertIntent takes a slice of Updates ([]*sdcpb.Update) and converts it into a tree.UpdateSlice, that contains *cache.Updates. -func (d *Datastore) expandAndConvertIntent(ctx context.Context, intentName string, priority int32, upds []*sdcpb.Update) (tree.UpdateSlice, error) { - converter := utils.NewConverter(d.schemaClient) - - // list of updates to be added to the cache - // Expands the value, in case of json to single typed value updates - expandedReqUpdates, err := converter.ExpandUpdates(ctx, upds, true) - if err != nil { - return nil, err - } - - // temp storage for cache.Update of the req. They are to be added later. - newCacheUpdates := make([]*cache.Update, 0, len(expandedReqUpdates)) - - for _, u := range expandedReqUpdates { - pathslice, err := utils.CompletePath(nil, u.GetPath()) - if err != nil { - return nil, err - } - - // since we already have the pathslice, we construct the cache.Update, but keep it for later - // addition to the tree. First we need to mark the existing once for deletion - - // make sure typedValue is carrying the correct type - err = d.validateUpdate(ctx, u) - if err != nil { - return nil, err - } - - // convert value to []byte for cache insertion - val, err := proto.Marshal(u.GetValue()) - if err != nil { - return nil, err - } - - // construct the cache.Update - newCacheUpdates = append(newCacheUpdates, cache.NewUpdate(pathslice, val, priority, intentName, 0)) - } - return newCacheUpdates, nil -} +const ( + ConcurrentValidate = false +) // SdcpbTransactionIntentToInternalTI converts sdcpb.TransactionIntent to types.TransactionIntent func (d *Datastore) SdcpbTransactionIntentToInternalTI(ctx context.Context, req *sdcpb.TransactionIntent) (*types.TransactionIntent, error) { @@ -78,13 +41,13 @@ func (d *Datastore) SdcpbTransactionIntentToInternalTI(ctx context.Context, req } // convert the sdcpb.updates to tree.UpdateSlice - cacheUpdates, err := d.expandAndConvertIntent(ctx, req.GetIntent(), req.GetPriority(), req.GetUpdate()) + Updates, err := treetypes.ExpandAndConvertIntent(ctx, d.schemaClient, req.GetIntent(), req.GetPriority(), req.GetUpdate(), time.Now().Unix()) if err != nil { return nil, err } // add the intent to the TransactionIntent - ti.AddUpdates(cacheUpdates) + ti.AddUpdates(Updates) return ti, nil } @@ -93,11 +56,8 @@ func (d *Datastore) SdcpbTransactionIntentToInternalTI(ctx context.Context, req // 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) { - treeSCC := tree.NewTreeCacheClient(d.Name(), d.cacheClient) // create a new TreeContext - tc := tree.NewTreeContext(treeSCC, d.schemaClient, d.Name()) - // refresh the Cache content of the treeCacheSchemaClient - tc.GetTreeSchemaCacheClient().RefreshCaches(ctx) + tc := tree.NewTreeContext(d.schemaClient, d.Name()) // create a new TreeRoot to collect validate and hand to SBI.Set() root, err := tree.NewTreeRoot(ctx, tc) @@ -109,25 +69,31 @@ func (d *Datastore) replaceIntent(ctx context.Context, transaction *types.Transa tc.SetActualOwner(tree.ReplaceIntentName) // store the actual / old running in the transaction - runningUpds, err := tc.GetTreeSchemaCacheClient().ReadRunningFull(ctx) - transaction.GetOldRunning().AddUpdates(runningUpds) + runningProto, err := d.cacheClient.IntentGet(ctx, tree.RunningIntentName) + err = root.ImportConfig(ctx, nil, treeproto.NewProtoTreeImporter(runningProto.GetRoot()), tree.RunningIntentName, tree.RunningValuesPrio, treetypes.NewUpdateInsertFlags()) + if err != nil { + return nil, err + } // creat a InsertFlags struct with the New flag set. - flagNew := tree.NewUpdateInsertFlags() + flagNew := treetypes.NewUpdateInsertFlags() flagNew.SetNewFlag() // add all the replace transaction updates with the New flag set - err = root.AddCacheUpdatesRecursive(ctx, transaction.GetReplace().GetUpdates(), flagNew) + err = root.AddUpdatesRecursive(ctx, transaction.GetReplace().GetUpdates(), flagNew) if err != nil { return nil, err } log.Debugf("Transaction Replace: %s - finish tree insertion phase", transaction.GetTransactionId()) - root.FinishInsertionPhase(ctx) + err = root.FinishInsertionPhase(ctx) + if err != nil { + return nil, err + } log.Debug(root.String()) // perform validation - validationResult := root.Validate(ctx, d.config.Validation) + validationResult := root.Validate(ctx, &config.Validation{DisableConcurrency: !ConcurrentValidate}) validationResult.ErrorsStr() if validationResult.HasErrors() { return nil, validationResult.JoinErrors() @@ -149,36 +115,53 @@ func (d *Datastore) replaceIntent(ctx context.Context, transaction *types.Transa // collect warnings warnings = append(warnings, dataResp.GetWarnings()...) - // query tree for deletes - deletes, err := root.GetDeletes(true) - if err != nil { - return nil, err - } - - // fast and optimistic writeback to the config store - err = d.cacheClient.Modify(ctx, d.Name(), &cache.Opts{ - Store: cachepb.Store_CONFIG, - }, deletes.PathSlices().ToStringSlice(), root.GetHighestPrecedence(false).ToCacheUpdateSlice()) - if err != nil { - return nil, fmt.Errorf("failed updating the running config store for %s: %w", d.Name(), err) - } - log.Infof("ds=%s transaction=%s applied", d.Name(), transaction.GetTransactionId()+" - replace") return warnings, nil } +func (d *Datastore) LoadAllButRunningIntents(ctx context.Context, root *tree.RootEntry) ([]string, error) { + + IntentNames := []string{} + IntentChan := make(chan *tree_persist.Intent, 0) + ErrChan := make(chan error, 1) + + go d.cacheClient.IntentGetAll(ctx, []string{"running"}, IntentChan, ErrChan) + + for { + select { + case err := <-ErrChan: + return nil, err + case <-ctx.Done(): + return nil, fmt.Errorf("context closed while retrieving all intents") + case intent, ok := <-IntentChan: + if !ok { + // IntentChan closed due to finish + return nil, nil + } + IntentNames = append(IntentNames, intent.GetIntentName()) + log.Debugf("adding intent %s to tree", intent.GetIntentName()) + protoLoader := treeproto.NewProtoTreeImporter(intent.GetRoot()) + log.Debugf(intent.String()) + err := root.ImportConfig(ctx, nil, protoLoader, intent.GetIntentName(), intent.GetPriority(), treetypes.NewUpdateInsertFlags()) + if err != nil { + return nil, err + } + } + } +} + // lowlevelTransactionSet func (d *Datastore) lowlevelTransactionSet(ctx context.Context, transaction *types.Transaction, dryRun bool) (*sdcpb.TransactionSetResponse, error) { + // create a new TreeRoot + d.syncTreeMutex.Lock() + root, err := d.syncTree.DeepCopy(ctx) + d.syncTreeMutex.Unlock() + if err != nil { + return nil, err + } - treeSCC := tree.NewTreeCacheClient(d.Name(), d.cacheClient) - // create a new TreeContext - tc := tree.NewTreeContext(treeSCC, d.schemaClient, d.Name()) - // refresh the SchemaClientCache - tc.GetTreeSchemaCacheClient().RefreshCaches(ctx) - - // creat a new TreeRoot - root, err := tree.NewTreeRoot(ctx, tc) + _, err = d.LoadAllButRunningIntents(ctx, root) if err != nil { return nil, err } @@ -187,25 +170,22 @@ func (d *Datastore) lowlevelTransactionSet(ctx context.Context, transaction *typ // this is then used to load the IntendedStore highes prio into the tree, to decide if an update // is to be applied or if a higher precedence update exists and is therefore not applicable. Also if the value got // deleted and a previousely shadowed entry becomes active. - involvedPaths := tree.NewPathSet() + involvedPaths := treetypes.NewPathSet() // create a flags attribute - flagNew := tree.NewUpdateInsertFlags() + flagNew := treetypes.NewUpdateInsertFlags() // where the New flag is set flagNew.SetNewFlag() // iterate through all the intents for _, intent := range transaction.GetNewIntents() { // update the TreeContext to reflect the actual owner (intent name) - tc.SetActualOwner(intent.GetName()) + lvs := tree.LeafVariantSlice{} + lvs = root.GetByOwner(intent.GetName(), lvs) - log.Debugf("Transaction: %s - adding intent %s to tree", transaction.GetTransactionId(), intent.GetName()) + oldIntentContent := lvs.ToUpdateSlice() - // load the old intent content into the tree and return it - oldIntentContent, err := root.LoadIntendedStoreOwnerData(ctx, intent.GetName(), intent.GetOnlyIntended()) - if err != nil { - return nil, err - } + root.MarkOwnerDelete(intent.GetName(), intent.GetOnlyIntended()) // store the old intent content in the transaction as the old intent. err = transaction.AddIntentContent(intent.GetName(), types.TransactionIntentOld, oldIntentContent.GetFirstPriorityValue(), oldIntentContent) @@ -214,7 +194,7 @@ func (d *Datastore) lowlevelTransactionSet(ctx context.Context, transaction *typ } // add the content to the Tree - err = root.AddCacheUpdatesRecursive(ctx, intent.GetUpdates(), flagNew) + err = root.AddUpdatesRecursive(ctx, intent.GetUpdates(), flagNew) if err != nil { return nil, err } @@ -225,26 +205,22 @@ func (d *Datastore) lowlevelTransactionSet(ctx context.Context, transaction *typ involvedPaths.Join(intent.GetUpdates().ToPathSet()) } - // load the alternatives for the involved paths into the tree - err = loadIntendedStoreHighestPrio(ctx, treeSCC, root, involvedPaths, transaction.GetIntentNames()) - if err != nil { - return nil, err - } + les := tree.LeafVariantSlice{} + les = root.GetByOwner(tree.RunningIntentName, les) - // add running to the tree - err = populateTreeWithRunning(ctx, treeSCC, root) - if err != nil { - return nil, err - } + transaction.GetOldRunning().AddUpdates(les.ToUpdateSlice()) log.Debugf("Transaction: %s - finish tree insertion phase", transaction.GetTransactionId()) // FinishInsertion Phase - root.FinishInsertionPhase(ctx) + err = root.FinishInsertionPhase(ctx) + if err != nil { + return nil, err + } log.Debug(root.String()) // perform validation - validationResult := root.Validate(ctx, d.config.Validation) + validationResult := root.Validate(ctx, &config.Validation{DisableConcurrency: !ConcurrentValidate}) // prepare the response struct result := &sdcpb.TransactionSetResponse{ @@ -271,7 +247,7 @@ func (d *Datastore) lowlevelTransactionSet(ctx context.Context, transaction *typ // convert updates from cache.Update to sdcpb.Update // adding them to the response - result.Update, err = cacheUpdateToSdcpbUpdate(updates) + result.Update, err = updateToSdcpbUpdate(updates) if err != nil { return nil, err } @@ -287,7 +263,7 @@ func (d *Datastore) lowlevelTransactionSet(ctx context.Context, transaction *typ // Error out if validation failed. if validationResult.HasErrors() { - return result, nil + return result, ErrValidationError } log.Infof("Transaction: %s - validation passed", transaction.GetTransactionId()) @@ -312,11 +288,8 @@ func (d *Datastore) lowlevelTransactionSet(ctx context.Context, transaction *typ ///////////////////////////////////// // logging - strSl := tree.Map(updates.ToCacheUpdateSlice(), func(u *cache.Update) string { return u.String() }) + strSl := treetypes.Map(updates.ToUpdateSlice(), func(u *treetypes.Update) string { return u.String() }) log.Debugf("Updates\n%s", strings.Join(strSl, "\n")) - - delSl := deletes.PathSlices() - log.Debugf("Deletes:\n%s", strings.Join(strSl, "\n")) for _, intent := range transaction.GetNewIntents() { @@ -325,30 +298,47 @@ func (d *Datastore) lowlevelTransactionSet(ctx context.Context, transaction *typ deletesOwner := root.GetDeletesForOwner(intent.GetName()) // logging - strSl := tree.Map(updatesOwner, func(u *cache.Update) string { return u.String() }) + strSl := treetypes.Map(updatesOwner, func(u *treetypes.Update) string { return u.String() }) log.Debugf("Updates Owner: %s\n%s", intent.GetName(), strings.Join(strSl, "\n")) delSl := deletesOwner.StringSlice() - log.Debugf("Deletes Owner: %s \n%s", intent.GetName(), strings.Join(delSl, "\n")) - - // modify intended store per intent - err = d.cacheClient.Modify(ctx, d.Name(), &cache.Opts{ - Store: cachepb.Store_INTENDED, - Owner: intent.GetName(), - Priority: intent.GetPriority(), - }, deletesOwner.ToStringSlice(), updatesOwner) - + log.Debugf("Deletes Owner: %s\n%s", intent.GetName(), strings.Join(delSl, "\n")) + + protoIntent, err := root.TreeExport(intent.GetName(), intent.GetPriority()) + switch { + case errors.Is(err, tree.ErrorIntentNotPresent): + err = d.cacheClient.IntentDelete(ctx, intent.GetName()) + if err != nil { + return nil, fmt.Errorf("failed deleting intent from store for %s: %w", d.Name(), err) + } + continue + case err != nil: + return nil, err + } + err = d.cacheClient.IntentModify(ctx, protoIntent) if err != nil { return nil, fmt.Errorf("failed updating the intended store for %s: %w", d.Name(), err) } } - // fast and optimistic writeback to the config store - err = d.cacheClient.Modify(ctx, d.Name(), &cache.Opts{ - Store: cachepb.Store_CONFIG, - }, delSl.ToStringSlice(), updates.ToCacheUpdateSlice()) + // OPTIMISTIC WRITEBACK TO RUNNING + runningUpdates := updates.ToUpdateSlice().CopyWithNewOwnerAndPrio(tree.RunningIntentName, tree.RunningValuesPrio) + + // add the calculated updates to the tree, as running with adjusted prio and owner + err = root.AddUpdatesRecursive(ctx, runningUpdates, treetypes.NewUpdateInsertFlags()) if err != nil { - return nil, fmt.Errorf("failed updating the running config store for %s: %w", d.Name(), err) + return nil, err + } + + // perform deletes + root.DeleteSubtreePaths(deletes, tree.RunningIntentName) + + newRunningIntent, err := root.TreeExport(tree.RunningIntentName, tree.RunningValuesPrio) + if newRunningIntent != nil { + err = d.cacheClient.IntentModify(ctx, newRunningIntent) + if err != nil { + return nil, fmt.Errorf("failed updating the running store for %s: %w", d.Name(), err) + } } log.Infof("ds=%s transaction=%s: completed", d.Name(), transaction.GetTransactionId()) @@ -425,6 +415,13 @@ 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) { + log.Errorf("Transaction: %s - validation failed\n%s", transactionId, strings.Join(response.GetErrors(), "\n")) + return response, nil + } + // if it is any other error, return a regular error if err != nil { log.Errorf("error executing transaction: %v", err) return nil, err @@ -437,17 +434,14 @@ func (d *Datastore) TransactionSet(ctx context.Context, transactionId string, tr return response, err } -func cacheUpdateToSdcpbUpdate(lvs tree.LeafVariantSlice) ([]*sdcpb.Update, error) { +func updateToSdcpbUpdate(lvs tree.LeafVariantSlice) ([]*sdcpb.Update, error) { result := make([]*sdcpb.Update, 0, len(lvs)) for _, lv := range lvs { path, err := lv.GetEntry().SdcpbPath() if err != nil { return nil, err } - value, err := lv.Update.Value() - if err != nil { - return nil, err - } + value := lv.Value() upd := &sdcpb.Update{ Path: path, Value: value, @@ -478,78 +472,3 @@ func (d *Datastore) TransactionCancel(ctx context.Context, transactionId string) return d.transactionManager.Cancel(ctx, transactionId) } - -func loadIntendedStoreHighestPrio(ctx context.Context, tscc tree.TreeCacheClient, r *tree.RootEntry, pathKeySet *tree.PathSet, skipIntents []string) error { - - // Get all entries of the already existing intent - cacheEntries := tscc.ReadCurrentUpdatesHighestPriorities(ctx, pathKeySet.GetPaths(), 2) - - flags := tree.NewUpdateInsertFlags() - - // add all the existing entries - for _, entry := range cacheEntries { - // we need to skip the actual owner entries - if slices.Contains(skipIntents, entry.Owner()) { - continue - } - _, err := r.AddCacheUpdateRecursive(ctx, entry, flags) - if err != nil { - return err - } - } - return nil -} - -func populateTreeWithRunning(ctx context.Context, tscc tree.TreeCacheClient, r *tree.RootEntry) error { - upds, err := tscc.ReadRunningFull(ctx) - if err != nil { - return err - } - - flags := tree.NewUpdateInsertFlags() - - for _, upd := range upds { - newUpd := cache.NewUpdate(upd.GetPath(), upd.Bytes(), tree.RunningValuesPrio, tree.RunningIntentName, 0) - _, err := r.AddCacheUpdateRecursive(ctx, newUpd, flags) - if err != nil { - return err - } - } - - return nil -} - -func pathIsKeyAsLeaf(p *sdcpb.Path) bool { - numPElem := len(p.GetElem()) - if numPElem < 2 { - return false - } - - _, ok := p.GetElem()[numPElem-2].GetKey()[p.GetElem()[numPElem-1].GetName()] - return ok -} - -func (d *Datastore) readStoreKeysMeta(ctx context.Context, store cachepb.Store) (map[string]tree.UpdateSlice, error) { - entryCh, err := d.cacheClient.GetKeys(ctx, d.config.Name, store) - if err != nil { - return nil, err - } - - result := map[string]tree.UpdateSlice{} - for { - select { - case <-ctx.Done(): - return nil, ctx.Err() - case e, ok := <-entryCh: - if !ok { - return result, nil - } - key := strings.Join(e.GetPath(), tree.KeysIndexSep) - _, exists := result[key] - if !exists { - result[key] = tree.UpdateSlice{} - } - result[key] = append(result[key], e) - } - } -} diff --git a/pkg/datastore/tree_operation_test.go b/pkg/datastore/tree_operation_test.go index 43810b8b..212d62f2 100644 --- a/pkg/datastore/tree_operation_test.go +++ b/pkg/datastore/tree_operation_test.go @@ -16,37 +16,29 @@ package datastore import ( "context" + "encoding/json" "fmt" "strings" "testing" "github.com/openconfig/ygot/ygot" - "github.com/sdcio/data-server/mocks/mockcacheclient" - "github.com/sdcio/data-server/mocks/mocktarget" - "github.com/sdcio/data-server/pkg/cache" "github.com/sdcio/data-server/pkg/config" schemaClient "github.com/sdcio/data-server/pkg/datastore/clients/schema" "github.com/sdcio/data-server/pkg/tree" - "github.com/sdcio/data-server/pkg/utils" + 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" sdcio_schema "github.com/sdcio/data-server/tests/sdcioygot" sdcpb "github.com/sdcio/sdc-protos/sdcpb" "go.uber.org/mock/gomock" - "google.golang.org/protobuf/proto" ) var ( // TypedValue Bool True and false - TypedValueTrue []byte - TypedValueFalse []byte + TypedValueTrue = &sdcpb.TypedValue{Value: &sdcpb.TypedValue_BoolVal{BoolVal: true}} + TypedValueFalse = &sdcpb.TypedValue{Value: &sdcpb.TypedValue_BoolVal{BoolVal: false}} ) -func init() { - // Calculate the TypedValues for true and false - TypedValueTrue, _ = proto.Marshal(&sdcpb.TypedValue{Value: &sdcpb.TypedValue_BoolVal{BoolVal: true}}) - TypedValueFalse, _ = proto.Marshal(&sdcpb.TypedValue{Value: &sdcpb.TypedValue_BoolVal{BoolVal: true}}) -} - func TestDatastore_populateTree(t *testing.T) { prio15 := int32(15) prio10 := int32(10) @@ -65,23 +57,23 @@ func TestDatastore_populateTree(t *testing.T) { tests := []struct { name string intentReqValue func() (string, error) // depending on the path, this should be *testhelper.TestConfig or any sub-value - intentReqPath string + intentReqPath types.PathSlice intentName string intentPrio int32 intentDelete bool - expectedModify []*cache.Update + expectedModify []*types.Update expectedDeletes [][]string expectedOwnerDeletes [][]string - expectedOwnerUpdates []*cache.Update - intendedStoreUpdates []*cache.Update - runningStoreUpdates []*cache.Update + expectedOwnerUpdates []*types.Update + intendedStoreUpdates []*types.Update + runningStoreUpdates []*types.Update NotOnlyNewOrUpdated bool // it negated when used in the call, usually we want it to be true }{ { name: "DoubleKey - Delete Single item", intentName: owner2, intentPrio: prio10, - intentReqPath: "/", + intentReqPath: nil, intentReqValue: func() (string, error) { d := &sdcio_schema.Device{ Doublekey: map[sdcio_schema.SdcioModel_Doublekey_Key]*sdcio_schema.SdcioModel_Doublekey{ @@ -116,33 +108,33 @@ func TestDatastore_populateTree(t *testing.T) { SkipValidation: false, }) }, - runningStoreUpdates: []*cache.Update{ - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "key1"}, testhelper.GetStringTvProto(t, "k1.1"), tree.RunningValuesPrio, tree.RunningIntentName, 0), - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "key2"}, testhelper.GetStringTvProto(t, "k1.2"), tree.RunningValuesPrio, tree.RunningIntentName, 0), - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "mandato"}, testhelper.GetStringTvProto(t, "TheMandatoryValue1"), tree.RunningValuesPrio, tree.RunningIntentName, 0), - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "cont", "value1"}, testhelper.GetStringTvProto(t, "containerval1.1"), tree.RunningValuesPrio, tree.RunningIntentName, 0), - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "cont", "value2"}, testhelper.GetStringTvProto(t, "containerval1.2"), tree.RunningValuesPrio, tree.RunningIntentName, 0), - - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "key1"}, testhelper.GetStringTvProto(t, "k1.1"), tree.RunningValuesPrio, tree.RunningIntentName, 0), - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "key2"}, testhelper.GetStringTvProto(t, "k1.3"), tree.RunningValuesPrio, tree.RunningIntentName, 0), - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "mandato"}, testhelper.GetStringTvProto(t, "TheMandatoryValue1"), tree.RunningValuesPrio, tree.RunningIntentName, 0), - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "cont", "value1"}, testhelper.GetStringTvProto(t, "containerval1.1"), tree.RunningValuesPrio, tree.RunningIntentName, 0), - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "cont", "value2"}, testhelper.GetStringTvProto(t, "containerval1.2"), tree.RunningValuesPrio, tree.RunningIntentName, 0), - - cache.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "key1"}, testhelper.GetStringTvProto(t, "k2.1"), tree.RunningValuesPrio, tree.RunningIntentName, 0), - cache.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "key2"}, testhelper.GetStringTvProto(t, "k2.2"), tree.RunningValuesPrio, tree.RunningIntentName, 0), - cache.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "mandato"}, testhelper.GetStringTvProto(t, "TheMandatoryValue2"), tree.RunningValuesPrio, tree.RunningIntentName, 0), - cache.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "cont", "value1"}, testhelper.GetStringTvProto(t, "containerval2.1"), tree.RunningValuesPrio, tree.RunningIntentName, 0), - cache.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "cont", "value2"}, testhelper.GetStringTvProto(t, "containerval2.2"), tree.RunningValuesPrio, tree.RunningIntentName, 0), + runningStoreUpdates: []*types.Update{ + types.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "key1"}, testhelper.GetStringTvProto("k1.1"), tree.RunningValuesPrio, tree.RunningIntentName, 0), + types.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "key2"}, testhelper.GetStringTvProto("k1.2"), tree.RunningValuesPrio, tree.RunningIntentName, 0), + types.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "mandato"}, testhelper.GetStringTvProto("TheMandatoryValue1"), tree.RunningValuesPrio, tree.RunningIntentName, 0), + types.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "cont", "value1"}, testhelper.GetStringTvProto("containerval1.1"), tree.RunningValuesPrio, tree.RunningIntentName, 0), + types.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "cont", "value2"}, testhelper.GetStringTvProto("containerval1.2"), tree.RunningValuesPrio, tree.RunningIntentName, 0), + + types.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "key1"}, testhelper.GetStringTvProto("k1.1"), tree.RunningValuesPrio, tree.RunningIntentName, 0), + types.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "key2"}, testhelper.GetStringTvProto("k1.3"), tree.RunningValuesPrio, tree.RunningIntentName, 0), + types.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "mandato"}, testhelper.GetStringTvProto("TheMandatoryValue1"), tree.RunningValuesPrio, tree.RunningIntentName, 0), + types.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "cont", "value1"}, testhelper.GetStringTvProto("containerval1.1"), tree.RunningValuesPrio, tree.RunningIntentName, 0), + types.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "cont", "value2"}, testhelper.GetStringTvProto("containerval1.2"), tree.RunningValuesPrio, tree.RunningIntentName, 0), + + types.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "key1"}, testhelper.GetStringTvProto("k2.1"), tree.RunningValuesPrio, tree.RunningIntentName, 0), + types.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "key2"}, testhelper.GetStringTvProto("k2.2"), tree.RunningValuesPrio, tree.RunningIntentName, 0), + types.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "mandato"}, testhelper.GetStringTvProto("TheMandatoryValue2"), tree.RunningValuesPrio, tree.RunningIntentName, 0), + types.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "cont", "value1"}, testhelper.GetStringTvProto("containerval2.1"), tree.RunningValuesPrio, tree.RunningIntentName, 0), + types.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "cont", "value2"}, testhelper.GetStringTvProto("containerval2.2"), tree.RunningValuesPrio, tree.RunningIntentName, 0), }, expectedDeletes: [][]string{ {"doublekey", "k1.1", "k1.3"}, }, - expectedModify: []*cache.Update{ - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "mandato"}, testhelper.GetStringTvProto(t, "TheMandatoryValueOther"), prio10, owner2, 0), + expectedModify: []*types.Update{ + types.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "mandato"}, testhelper.GetStringTvProto("TheMandatoryValueOther"), prio10, owner2, 0), }, - expectedOwnerUpdates: []*cache.Update{ - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "mandato"}, testhelper.GetStringTvProto(t, "TheMandatoryValueOther"), prio10, owner2, 0), + expectedOwnerUpdates: []*types.Update{ + types.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "mandato"}, testhelper.GetStringTvProto("TheMandatoryValueOther"), prio10, owner2, 0), }, expectedOwnerDeletes: [][]string{ {"doublekey", "k1.1", "k1.3", "key1"}, @@ -151,31 +143,31 @@ func TestDatastore_populateTree(t *testing.T) { {"doublekey", "k1.1", "k1.3", "cont", "value1"}, {"doublekey", "k1.1", "k1.3", "cont", "value2"}, }, - intendedStoreUpdates: []*cache.Update{ - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "key1"}, testhelper.GetStringTvProto(t, "k1.1"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "key2"}, testhelper.GetStringTvProto(t, "k1.2"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "mandato"}, testhelper.GetStringTvProto(t, "TheMandatoryValue1"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "cont", "value1"}, testhelper.GetStringTvProto(t, "containerval1.1"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "cont", "value2"}, testhelper.GetStringTvProto(t, "containerval1.2"), prio10, owner2, 0), - - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "key1"}, testhelper.GetStringTvProto(t, "k1.1"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "key2"}, testhelper.GetStringTvProto(t, "k1.3"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "mandato"}, testhelper.GetStringTvProto(t, "TheMandatoryValue1"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "cont", "value1"}, testhelper.GetStringTvProto(t, "containerval1.1"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "cont", "value2"}, testhelper.GetStringTvProto(t, "containerval1.2"), prio10, owner2, 0), - - cache.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "key1"}, testhelper.GetStringTvProto(t, "k2.1"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "key2"}, testhelper.GetStringTvProto(t, "k2.2"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "mandato"}, testhelper.GetStringTvProto(t, "TheMandatoryValue2"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "cont", "value1"}, testhelper.GetStringTvProto(t, "containerval2.1"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "cont", "value2"}, testhelper.GetStringTvProto(t, "containerval2.2"), prio10, owner2, 0), + intendedStoreUpdates: []*types.Update{ + types.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "key1"}, testhelper.GetStringTvProto("k1.1"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "key2"}, testhelper.GetStringTvProto("k1.2"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "mandato"}, testhelper.GetStringTvProto("TheMandatoryValue1"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "cont", "value1"}, testhelper.GetStringTvProto("containerval1.1"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "cont", "value2"}, testhelper.GetStringTvProto("containerval1.2"), prio10, owner2, 0), + + types.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "key1"}, testhelper.GetStringTvProto("k1.1"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "key2"}, testhelper.GetStringTvProto("k1.3"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "mandato"}, testhelper.GetStringTvProto("TheMandatoryValue1"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "cont", "value1"}, testhelper.GetStringTvProto("containerval1.1"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "cont", "value2"}, testhelper.GetStringTvProto("containerval1.2"), prio10, owner2, 0), + + types.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "key1"}, testhelper.GetStringTvProto("k2.1"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "key2"}, testhelper.GetStringTvProto("k2.2"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "mandato"}, testhelper.GetStringTvProto("TheMandatoryValue2"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "cont", "value1"}, testhelper.GetStringTvProto("containerval2.1"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "cont", "value2"}, testhelper.GetStringTvProto("containerval2.2"), prio10, owner2, 0), }, }, { name: "DoubleKey - New Data", intentName: owner2, intentPrio: prio10, - intentReqPath: "/", + intentReqPath: nil, intentReqValue: func() (string, error) { d := &sdcio_schema.Device{ Doublekey: map[sdcio_schema.SdcioModel_Doublekey_Key]*sdcio_schema.SdcioModel_Doublekey{ @@ -222,51 +214,51 @@ func TestDatastore_populateTree(t *testing.T) { SkipValidation: false, }) }, - expectedModify: []*cache.Update{ - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "key1"}, testhelper.GetStringTvProto(t, "k1.1"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "key2"}, testhelper.GetStringTvProto(t, "k1.2"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "mandato"}, testhelper.GetStringTvProto(t, "TheMandatoryValue1"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "cont", "value1"}, testhelper.GetStringTvProto(t, "containerval1.1"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "cont", "value2"}, testhelper.GetStringTvProto(t, "containerval1.2"), prio10, owner2, 0), - - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "key1"}, testhelper.GetStringTvProto(t, "k1.1"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "key2"}, testhelper.GetStringTvProto(t, "k1.3"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "mandato"}, testhelper.GetStringTvProto(t, "TheMandatoryValue1"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "cont", "value1"}, testhelper.GetStringTvProto(t, "containerval1.1"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "cont", "value2"}, testhelper.GetStringTvProto(t, "containerval1.2"), prio10, owner2, 0), - - cache.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "key1"}, testhelper.GetStringTvProto(t, "k2.1"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "key2"}, testhelper.GetStringTvProto(t, "k2.2"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "mandato"}, testhelper.GetStringTvProto(t, "TheMandatoryValue2"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "cont", "value1"}, testhelper.GetStringTvProto(t, "containerval2.1"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "cont", "value2"}, testhelper.GetStringTvProto(t, "containerval2.2"), prio10, owner2, 0), - }, - expectedOwnerUpdates: []*cache.Update{ - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "key1"}, testhelper.GetStringTvProto(t, "k1.1"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "key2"}, testhelper.GetStringTvProto(t, "k1.2"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "mandato"}, testhelper.GetStringTvProto(t, "TheMandatoryValue1"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "cont", "value1"}, testhelper.GetStringTvProto(t, "containerval1.1"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "cont", "value2"}, testhelper.GetStringTvProto(t, "containerval1.2"), prio10, owner2, 0), - - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "key1"}, testhelper.GetStringTvProto(t, "k1.1"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "key2"}, testhelper.GetStringTvProto(t, "k1.3"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "mandato"}, testhelper.GetStringTvProto(t, "TheMandatoryValue1"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "cont", "value1"}, testhelper.GetStringTvProto(t, "containerval1.1"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "cont", "value2"}, testhelper.GetStringTvProto(t, "containerval1.2"), prio10, owner2, 0), - - cache.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "key1"}, testhelper.GetStringTvProto(t, "k2.1"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "key2"}, testhelper.GetStringTvProto(t, "k2.2"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "mandato"}, testhelper.GetStringTvProto(t, "TheMandatoryValue2"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "cont", "value1"}, testhelper.GetStringTvProto(t, "containerval2.1"), prio10, owner2, 0), - cache.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "cont", "value2"}, testhelper.GetStringTvProto(t, "containerval2.2"), prio10, owner2, 0), - }, - intendedStoreUpdates: []*cache.Update{}, + expectedModify: []*types.Update{ + types.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "key1"}, testhelper.GetStringTvProto("k1.1"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "key2"}, testhelper.GetStringTvProto("k1.2"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "mandato"}, testhelper.GetStringTvProto("TheMandatoryValue1"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "cont", "value1"}, testhelper.GetStringTvProto("containerval1.1"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "cont", "value2"}, testhelper.GetStringTvProto("containerval1.2"), prio10, owner2, 0), + + types.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "key1"}, testhelper.GetStringTvProto("k1.1"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "key2"}, testhelper.GetStringTvProto("k1.3"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "mandato"}, testhelper.GetStringTvProto("TheMandatoryValue1"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "cont", "value1"}, testhelper.GetStringTvProto("containerval1.1"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "cont", "value2"}, testhelper.GetStringTvProto("containerval1.2"), prio10, owner2, 0), + + types.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "key1"}, testhelper.GetStringTvProto("k2.1"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "key2"}, testhelper.GetStringTvProto("k2.2"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "mandato"}, testhelper.GetStringTvProto("TheMandatoryValue2"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "cont", "value1"}, testhelper.GetStringTvProto("containerval2.1"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "cont", "value2"}, testhelper.GetStringTvProto("containerval2.2"), prio10, owner2, 0), + }, + expectedOwnerUpdates: []*types.Update{ + types.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "key1"}, testhelper.GetStringTvProto("k1.1"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "key2"}, testhelper.GetStringTvProto("k1.2"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "mandato"}, testhelper.GetStringTvProto("TheMandatoryValue1"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "cont", "value1"}, testhelper.GetStringTvProto("containerval1.1"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k1.1", "k1.2", "cont", "value2"}, testhelper.GetStringTvProto("containerval1.2"), prio10, owner2, 0), + + types.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "key1"}, testhelper.GetStringTvProto("k1.1"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "key2"}, testhelper.GetStringTvProto("k1.3"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "mandato"}, testhelper.GetStringTvProto("TheMandatoryValue1"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "cont", "value1"}, testhelper.GetStringTvProto("containerval1.1"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "cont", "value2"}, testhelper.GetStringTvProto("containerval1.2"), prio10, owner2, 0), + + types.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "key1"}, testhelper.GetStringTvProto("k2.1"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "key2"}, testhelper.GetStringTvProto("k2.2"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "mandato"}, testhelper.GetStringTvProto("TheMandatoryValue2"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "cont", "value1"}, testhelper.GetStringTvProto("containerval2.1"), prio10, owner2, 0), + types.NewUpdate([]string{"doublekey", "k2.1", "k2.2", "cont", "value2"}, testhelper.GetStringTvProto("containerval2.2"), prio10, owner2, 0), + }, + intendedStoreUpdates: []*types.Update{}, }, { name: "Simple add to root path", intentName: owner1, intentPrio: prio10, - intentReqPath: "/", + intentReqPath: nil, intentReqValue: func() (string, error) { d := &sdcio_schema.Device{ Interface: map[string]*sdcio_schema.SdcioModel_Interface{}, @@ -281,13 +273,13 @@ func TestDatastore_populateTree(t *testing.T) { }) }, - expectedModify: []*cache.Update{ - cache.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/1"), prio10, owner1, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto(t, "MyDescription"), prio10, owner1, 0), + expectedModify: []*types.Update{ + types.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto("ethernet-1/1"), prio10, owner1, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto("MyDescription"), prio10, owner1, 0), }, - expectedOwnerUpdates: []*cache.Update{ - cache.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/1"), prio10, owner1, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto(t, "MyDescription"), prio10, owner1, 0), + expectedOwnerUpdates: []*types.Update{ + types.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto("ethernet-1/1"), prio10, owner1, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto("MyDescription"), prio10, owner1, 0), }, intendedStoreUpdates: nil, }, @@ -295,7 +287,7 @@ func TestDatastore_populateTree(t *testing.T) { name: "Simple add with a specific path", intentName: owner1, intentPrio: prio10, - intentReqPath: "interface", + intentReqPath: types.PathSlice{"interface"}, intentReqValue: func() (string, error) { i := &sdcio_schema.SdcioModel_Interface{ @@ -307,21 +299,21 @@ func TestDatastore_populateTree(t *testing.T) { SkipValidation: false, }) }, - expectedModify: []*cache.Update{ - cache.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/1"), prio10, owner1, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto(t, "MyDescription"), prio10, owner1, 0), + expectedModify: []*types.Update{ + types.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto("ethernet-1/1"), prio10, owner1, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto("MyDescription"), prio10, owner1, 0), }, - expectedOwnerUpdates: []*cache.Update{ - cache.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/1"), prio10, owner1, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto(t, "MyDescription"), prio10, owner1, 0), + expectedOwnerUpdates: []*types.Update{ + types.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto("ethernet-1/1"), prio10, owner1, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto("MyDescription"), prio10, owner1, 0), }, intendedStoreUpdates: nil, }, { - name: "Add with existing better prio same intent / owner", + name: "Add with existing better prio same intent name", intentName: owner1, intentPrio: prio10, - intentReqPath: "interface", + intentReqPath: types.PathSlice{"interface"}, intentReqValue: func() (string, error) { i := &sdcio_schema.SdcioModel_Interface{ Name: ygot.String("ethernet-1/1"), @@ -332,50 +324,50 @@ func TestDatastore_populateTree(t *testing.T) { SkipValidation: false, }) }, - expectedModify: []*cache.Update{ + expectedModify: []*types.Update{ // Right now, although the value stays the same, but the priority changes, we'll receive an update for these values. // This maybe needs to be mitigated, but is not considered harmfull atm. - cache.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/1"), prio10, owner1, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto(t, "MyDescription"), prio10, owner1, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto("ethernet-1/1"), prio10, owner1, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto("MyDescription"), prio10, owner1, 0), }, - expectedOwnerUpdates: []*cache.Update{ - cache.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/1"), prio10, owner1, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto(t, "MyDescription"), prio10, owner1, 0), + expectedOwnerUpdates: []*types.Update{ + types.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto("ethernet-1/1"), prio10, owner1, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto("MyDescription"), prio10, owner1, 0), }, - intendedStoreUpdates: []*cache.Update{ - cache.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/1"), prio5, owner1, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto(t, "MyDescription"), prio5, owner1, 0), + intendedStoreUpdates: []*types.Update{ + types.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto("ethernet-1/1"), prio5, owner1, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto("MyDescription"), prio5, owner1, 0), }, }, { name: "Delete the highes priority values, making shadowed values become active", intentName: owner1, intentPrio: prio5, - intentReqPath: "/", + intentReqPath: nil, intentReqValue: func() (string, error) { return "{}", nil }, intentDelete: true, - expectedModify: []*cache.Update{ - cache.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/1"), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto(t, "MyDescriptionOwner2"), prio10, owner2, 0), + expectedModify: []*types.Update{ + types.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto("ethernet-1/1"), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto("MyDescriptionOwner2"), prio10, owner2, 0), }, expectedOwnerDeletes: [][]string{ {"interface", "ethernet-1/1", "name"}, {"interface", "ethernet-1/1", "description"}, }, - intendedStoreUpdates: []*cache.Update{ - cache.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/1"), prio5, owner1, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto(t, "MyDescription"), prio5, owner1, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/1"), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto(t, "MyDescriptionOwner2"), prio10, owner2, 0), + intendedStoreUpdates: []*types.Update{ + types.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto("ethernet-1/1"), prio5, owner1, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto("MyDescription"), prio5, owner1, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto("ethernet-1/1"), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto("MyDescriptionOwner2"), prio10, owner2, 0), }, }, { name: "Delete - aggregate branch via keys", intentName: owner2, intentPrio: prio10, - intentReqPath: "/", + intentReqPath: nil, intentReqValue: func() (string, error) { return "{}", nil }, @@ -388,17 +380,17 @@ func TestDatastore_populateTree(t *testing.T) { {"interface", "ethernet-1/1", "description"}, {"interface", "ethernet-1/1", "admin-state"}, }, - intendedStoreUpdates: []*cache.Update{ - cache.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/1"), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto(t, "MyDescriptionOwner2"), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/1", "admin-state"}, testhelper.GetStringTvProto(t, "enable"), prio10, owner2, 0), + intendedStoreUpdates: []*types.Update{ + types.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto("ethernet-1/1"), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto("MyDescriptionOwner2"), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "admin-state"}, testhelper.GetStringTvProto("enable"), prio10, owner2, 0), }, }, { name: "Delete - aggregate branch via keys, multiple entries (different keys)", intentName: owner2, intentPrio: prio10, - intentReqPath: "/", + intentReqPath: nil, intentReqValue: func() (string, error) { d := &sdcio_schema.Device{ Interface: map[string]*sdcio_schema.SdcioModel_Interface{ @@ -414,16 +406,16 @@ func TestDatastore_populateTree(t *testing.T) { }) }, intentDelete: true, - runningStoreUpdates: []*cache.Update{ - // cache.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/1"), prio10, owner2, 0), - // cache.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto(t, "MyDescriptionOwner2"), prio10, owner2, 0), - // cache.NewUpdate([]string{"interface", "ethernet-1/1", "admin-state"}, testhelper.GetStringTvProto(t, "enable"), prio10, owner2, 0), - // cache.NewUpdate([]string{"interface", "ethernet-1/2", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/2"), prio10, owner2, 0), - // cache.NewUpdate([]string{"interface", "ethernet-1/2", "description"}, testhelper.GetStringTvProto(t, "MyDescriptionOwner2"), prio10, owner2, 0), - // cache.NewUpdate([]string{"interface", "ethernet-1/2", "admin-state"}, testhelper.GetStringTvProto(t, "enable"), prio10, owner2, 0), - // cache.NewUpdate([]string{"interface", "ethernet-1/3", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/3"), prio10, owner2, 0), - // cache.NewUpdate([]string{"interface", "ethernet-1/3", "description"}, testhelper.GetStringTvProto(t, "MyDescriptionOwner2"), prio10, owner2, 0), - // cache.NewUpdate([]string{"interface", "ethernet-1/3", "admin-state"}, testhelper.GetStringTvProto(t, "enable"), prio10, owner2, 0), + runningStoreUpdates: []*types.Update{ + // cache.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto("ethernet-1/1"), prio10, owner2, 0), + // cache.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto("MyDescriptionOwner2"), prio10, owner2, 0), + // cache.NewUpdate([]string{"interface", "ethernet-1/1", "admin-state"}, testhelper.GetStringTvProto("enable"), prio10, owner2, 0), + // cache.NewUpdate([]string{"interface", "ethernet-1/2", "name"}, testhelper.GetStringTvProto("ethernet-1/2"), prio10, owner2, 0), + // cache.NewUpdate([]string{"interface", "ethernet-1/2", "description"}, testhelper.GetStringTvProto("MyDescriptionOwner2"), prio10, owner2, 0), + // cache.NewUpdate([]string{"interface", "ethernet-1/2", "admin-state"}, testhelper.GetStringTvProto("enable"), prio10, owner2, 0), + // cache.NewUpdate([]string{"interface", "ethernet-1/3", "name"}, testhelper.GetStringTvProto("ethernet-1/3"), prio10, owner2, 0), + // cache.NewUpdate([]string{"interface", "ethernet-1/3", "description"}, testhelper.GetStringTvProto("MyDescriptionOwner2"), prio10, owner2, 0), + // cache.NewUpdate([]string{"interface", "ethernet-1/3", "admin-state"}, testhelper.GetStringTvProto("enable"), prio10, owner2, 0), }, expectedDeletes: [][]string{ {"interface", "ethernet-1/1", "admin-state"}, @@ -439,30 +431,30 @@ func TestDatastore_populateTree(t *testing.T) { {"interface", "ethernet-1/3", "description"}, {"interface", "ethernet-1/3", "admin-state"}, }, - expectedOwnerUpdates: []*cache.Update{ - cache.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto(t, "MyNonappliedDescription"), prio10, owner2, 0), + expectedOwnerUpdates: []*types.Update{ + types.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto("MyNonappliedDescription"), prio10, owner2, 0), }, - expectedModify: []*cache.Update{ - cache.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto(t, "MyNonappliedDescription"), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/1"), prio10, owner2, 0), + expectedModify: []*types.Update{ + types.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto("MyNonappliedDescription"), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto("ethernet-1/1"), prio10, owner2, 0), }, - intendedStoreUpdates: []*cache.Update{ - cache.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/1"), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto(t, "MyDescriptionOwner2"), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/1", "admin-state"}, testhelper.GetStringTvProto(t, "enable"), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/2", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/2"), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/2", "description"}, testhelper.GetStringTvProto(t, "MyDescriptionOwner2"), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/2", "admin-state"}, testhelper.GetStringTvProto(t, "enable"), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/3", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/3"), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/3", "description"}, testhelper.GetStringTvProto(t, "MyDescriptionOwner2"), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/3", "admin-state"}, testhelper.GetStringTvProto(t, "enable"), prio10, owner2, 0), + intendedStoreUpdates: []*types.Update{ + types.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto("ethernet-1/1"), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto("MyDescriptionOwner2"), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "admin-state"}, testhelper.GetStringTvProto("enable"), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "name"}, testhelper.GetStringTvProto("ethernet-1/2"), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "description"}, testhelper.GetStringTvProto("MyDescriptionOwner2"), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "admin-state"}, testhelper.GetStringTvProto("enable"), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/3", "name"}, testhelper.GetStringTvProto("ethernet-1/3"), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/3", "description"}, testhelper.GetStringTvProto("MyDescriptionOwner2"), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/3", "admin-state"}, testhelper.GetStringTvProto("enable"), prio10, owner2, 0), }, }, { name: "Add lower precedence intent, every value already shadowed", intentName: owner2, intentPrio: prio10, - intentReqPath: "interface", + intentReqPath: types.PathSlice{"interface"}, intentReqValue: func() (string, error) { i := &sdcio_schema.SdcioModel_Interface{ Name: ygot.String("ethernet-1/1"), @@ -473,23 +465,22 @@ func TestDatastore_populateTree(t *testing.T) { SkipValidation: false, }) }, - runningStoreUpdates: []*cache.Update{ - cache.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/1"), tree.RunningValuesPrio, tree.RunningIntentName, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto(t, "MyDescription"), tree.RunningValuesPrio, tree.RunningIntentName, 0), + runningStoreUpdates: []*types.Update{ + types.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto("ethernet-1/1"), tree.RunningValuesPrio, tree.RunningIntentName, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto("MyDescription"), tree.RunningValuesPrio, tree.RunningIntentName, 0), }, - - expectedOwnerUpdates: []*cache.Update{ - cache.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/1"), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto(t, "MyNonappliedDescription"), prio10, owner2, 0), + expectedOwnerUpdates: []*types.Update{ + types.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto("ethernet-1/1"), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto("MyNonappliedDescription"), prio10, owner2, 0), }, - intendedStoreUpdates: []*cache.Update{ - cache.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/1"), prio5, owner1, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto(t, "MyDescription"), prio5, owner1, 0), + intendedStoreUpdates: []*types.Update{ + types.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto("ethernet-1/1"), prio5, owner1, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto("MyDescription"), prio5, owner1, 0), }, }, { name: "choices delete", - intentReqPath: "interface", + intentReqPath: types.PathSlice{"interface"}, intentReqValue: func() (string, error) { i := &sdcio_schema.SdcioModel_Interface{ Name: ygot.String("ethernet-1/1"), @@ -502,19 +493,19 @@ func TestDatastore_populateTree(t *testing.T) { }, intentPrio: 10, intentName: owner1, - runningStoreUpdates: []*cache.Update{ - cache.NewUpdate([]string{"choices", "case1", "case-elem"}, testhelper.GetStringTvProto(t, "Foobar"), prio10, owner1, 0), + runningStoreUpdates: []*types.Update{ + types.NewUpdate([]string{"choices", "case1", "case-elem"}, testhelper.GetStringTvProto("Foobar"), prio10, owner1, 0), }, - expectedOwnerUpdates: []*cache.Update{ - cache.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/1"), prio10, owner1, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto(t, "MyDescription"), prio10, owner1, 0), + expectedOwnerUpdates: []*types.Update{ + types.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto("ethernet-1/1"), prio10, owner1, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto("MyDescription"), prio10, owner1, 0), }, - intendedStoreUpdates: []*cache.Update{ - cache.NewUpdate([]string{"choices", "case1", "case-elem"}, testhelper.GetStringTvProto(t, "Foobar"), prio10, owner1, 0), + intendedStoreUpdates: []*types.Update{ + types.NewUpdate([]string{"choices", "case1", "case-elem"}, testhelper.GetStringTvProto("Foobar"), prio10, owner1, 0), }, - expectedModify: []*cache.Update{ - cache.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/1"), prio10, owner1, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto(t, "MyDescription"), prio10, owner1, 0), + expectedModify: []*types.Update{ + types.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto("ethernet-1/1"), prio10, owner1, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto("MyDescription"), prio10, owner1, 0), }, expectedDeletes: [][]string{ {"choices"}, @@ -527,7 +518,7 @@ func TestDatastore_populateTree(t *testing.T) { name: "Mixed, new entry, higher and lower precedence", intentName: owner2, intentPrio: prio10, - intentReqPath: "/", + intentReqPath: nil, intentReqValue: func() (string, error) { d := &sdcio_schema.Device{ @@ -559,46 +550,46 @@ func TestDatastore_populateTree(t *testing.T) { SkipValidation: false, }) }, - runningStoreUpdates: []*cache.Update{ - cache.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/1"), prio5, owner1, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto(t, "MyDescription"), prio5, owner1, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/2", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/2"), prio15, owner3, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/2", "description"}, testhelper.GetStringTvProto(t, "Owner3 Description"), prio15, owner3, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/2", "subinterface", "2", "index"}, testhelper.GetUIntTvProto(t, 1), prio15, owner3, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/2", "subinterface", "2", "description"}, testhelper.GetStringTvProto(t, "Subinterface Desc"), prio15, owner3, 0), - }, - expectedModify: []*cache.Update{ - cache.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "1", "index"}, testhelper.GetUIntTvProto(t, 1), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "1", "description"}, testhelper.GetStringTvProto(t, "Subinterface Desc"), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/2", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/2"), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/2", "description"}, testhelper.GetStringTvProto(t, "MyOtherDescription"), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/2", "subinterface", "1", "index"}, testhelper.GetUIntTvProto(t, 1), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/2", "subinterface", "1", "description"}, testhelper.GetStringTvProto(t, "Subinterface Desc"), prio10, owner2, 0), - }, - expectedOwnerUpdates: []*cache.Update{ - cache.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/1"), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto(t, "MyOtherDescription"), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "1", "index"}, testhelper.GetUIntTvProto(t, 1), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "1", "description"}, testhelper.GetStringTvProto(t, "Subinterface Desc"), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/2", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/2"), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/2", "description"}, testhelper.GetStringTvProto(t, "MyOtherDescription"), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/2", "subinterface", "1", "index"}, testhelper.GetUIntTvProto(t, 1), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/2", "subinterface", "1", "description"}, testhelper.GetStringTvProto(t, "Subinterface Desc"), prio10, owner2, 0), - }, - intendedStoreUpdates: []*cache.Update{ - cache.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/1"), prio5, owner1, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto(t, "MyDescription"), prio5, owner1, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/2", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/2"), prio15, owner3, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/2", "description"}, testhelper.GetStringTvProto(t, "Owner3 Description"), prio15, owner3, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/2", "subinterface", "2", "index"}, testhelper.GetUIntTvProto(t, 1), prio15, owner3, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/2", "subinterface", "2", "description"}, testhelper.GetStringTvProto(t, "Subinterface Desc"), prio15, owner3, 0), + runningStoreUpdates: []*types.Update{ + types.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto("ethernet-1/1"), tree.RunningValuesPrio, tree.RunningIntentName, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto("MyDescription"), tree.RunningValuesPrio, tree.RunningIntentName, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "name"}, testhelper.GetStringTvProto("ethernet-1/2"), tree.RunningValuesPrio, tree.RunningIntentName, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "description"}, testhelper.GetStringTvProto("Owner3 Description"), tree.RunningValuesPrio, tree.RunningIntentName, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "subinterface", "2", "index"}, testhelper.GetUIntTvProto(2), tree.RunningValuesPrio, tree.RunningIntentName, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "subinterface", "2", "description"}, testhelper.GetStringTvProto("Subinterface Desc"), tree.RunningValuesPrio, tree.RunningIntentName, 0), + }, + expectedModify: []*types.Update{ + types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "1", "index"}, testhelper.GetUIntTvProto(1), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "1", "description"}, testhelper.GetStringTvProto("Subinterface Desc"), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "name"}, testhelper.GetStringTvProto("ethernet-1/2"), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "description"}, testhelper.GetStringTvProto("MyOtherDescription"), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "subinterface", "1", "index"}, testhelper.GetUIntTvProto(1), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "subinterface", "1", "description"}, testhelper.GetStringTvProto("Subinterface Desc"), prio10, owner2, 0), + }, + expectedOwnerUpdates: []*types.Update{ + types.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto("ethernet-1/1"), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto("MyOtherDescription"), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "1", "index"}, testhelper.GetUIntTvProto(1), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "1", "description"}, testhelper.GetStringTvProto("Subinterface Desc"), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "name"}, testhelper.GetStringTvProto("ethernet-1/2"), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "description"}, testhelper.GetStringTvProto("MyOtherDescription"), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "subinterface", "1", "index"}, testhelper.GetUIntTvProto(1), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "subinterface", "1", "description"}, testhelper.GetStringTvProto("Subinterface Desc"), prio10, owner2, 0), + }, + intendedStoreUpdates: []*types.Update{ + types.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto("ethernet-1/1"), prio5, owner1, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto("MyDescription"), prio5, owner1, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "name"}, testhelper.GetStringTvProto("ethernet-1/2"), prio15, owner3, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "description"}, testhelper.GetStringTvProto("Owner3 Description"), prio15, owner3, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "subinterface", "2", "index"}, testhelper.GetUIntTvProto(2), prio15, owner3, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "subinterface", "2", "description"}, testhelper.GetStringTvProto("Subinterface Desc"), prio15, owner3, 0), }, }, { name: "Mixed, new entry, higher and lower precedence. notOnlyUpdated set to TRUE", intentName: owner2, intentPrio: prio10, - intentReqPath: "/", + intentReqPath: nil, NotOnlyNewOrUpdated: true, intentReqValue: func() (string, error) { @@ -631,44 +622,50 @@ func TestDatastore_populateTree(t *testing.T) { SkipValidation: false, }) }, - - expectedModify: []*cache.Update{ - cache.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/1"), prio5, owner1, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto(t, "MyDescription"), prio5, owner1, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "1", "index"}, testhelper.GetUIntTvProto(t, 1), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "1", "description"}, testhelper.GetStringTvProto(t, "Subinterface Desc"), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/2", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/2"), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/2", "description"}, testhelper.GetStringTvProto(t, "MyOtherDescription"), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/2", "subinterface", "1", "index"}, testhelper.GetUIntTvProto(t, 1), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/2", "subinterface", "1", "description"}, testhelper.GetStringTvProto(t, "Subinterface Desc"), prio10, owner2, 0), - // the next two are not part of the result, we might want to add the behaviour, such that one can query the entire intended. - // cache.NewUpdate([]string{"interface", "ethernet-0/1", "subinterface", "2", "index"}, testhelper.GetUIntTvProto(t, 1), prio15, owner3, 0), - // cache.NewUpdate([]string{"interface", "ethernet-0/1", "subinterface", "2", "description"}, testhelper.GetStringTvProto(t, "Subinterface Desc"), prio15, owner3, 0), - }, - expectedOwnerUpdates: []*cache.Update{ - cache.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/1"), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto(t, "MyOtherDescription"), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "1", "index"}, testhelper.GetUIntTvProto(t, 1), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "1", "description"}, testhelper.GetStringTvProto(t, "Subinterface Desc"), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/2", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/2"), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/2", "description"}, testhelper.GetStringTvProto(t, "MyOtherDescription"), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/2", "subinterface", "1", "index"}, testhelper.GetUIntTvProto(t, 1), prio10, owner2, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/2", "subinterface", "1", "description"}, testhelper.GetStringTvProto(t, "Subinterface Desc"), prio10, owner2, 0), - }, - intendedStoreUpdates: []*cache.Update{ - cache.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/1"), prio5, owner1, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto(t, "MyDescription"), prio5, owner1, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/2", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/2"), prio15, owner3, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/2", "description"}, testhelper.GetStringTvProto(t, "Owner3 Description"), prio15, owner3, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/2", "subinterface", "2", "index"}, testhelper.GetUIntTvProto(t, 1), prio15, owner3, 0), - cache.NewUpdate([]string{"interface", "ethernet-1/2", "subinterface", "2", "description"}, testhelper.GetStringTvProto(t, "Subinterface Desc"), prio15, owner3, 0), + runningStoreUpdates: []*types.Update{ + types.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto("ethernet-1/1"), tree.RunningValuesPrio, tree.RunningIntentName, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto("MyDescription"), tree.RunningValuesPrio, tree.RunningIntentName, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "name"}, testhelper.GetStringTvProto("ethernet-1/2"), tree.RunningValuesPrio, tree.RunningIntentName, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "description"}, testhelper.GetStringTvProto("Owner3 Description"), tree.RunningValuesPrio, tree.RunningIntentName, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "subinterface", "2", "index"}, testhelper.GetUIntTvProto(1), tree.RunningValuesPrio, tree.RunningIntentName, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "subinterface", "2", "description"}, testhelper.GetStringTvProto("Subinterface Desc"), tree.RunningValuesPrio, tree.RunningIntentName, 0), + }, + expectedModify: []*types.Update{ + types.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto("ethernet-1/1"), prio5, owner1, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto("MyDescription"), prio5, owner1, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "1", "index"}, testhelper.GetUIntTvProto(1), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "1", "description"}, testhelper.GetStringTvProto("Subinterface Desc"), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "name"}, testhelper.GetStringTvProto("ethernet-1/2"), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "description"}, testhelper.GetStringTvProto("MyOtherDescription"), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "subinterface", "1", "index"}, testhelper.GetUIntTvProto(1), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "subinterface", "1", "description"}, testhelper.GetStringTvProto("Subinterface Desc"), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "subinterface", "2", "index"}, testhelper.GetUIntTvProto(1), prio15, owner3, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "subinterface", "2", "description"}, testhelper.GetStringTvProto("Subinterface Desc"), prio15, owner3, 0), + }, + expectedOwnerUpdates: []*types.Update{ + types.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto("ethernet-1/1"), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto("MyOtherDescription"), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "1", "index"}, testhelper.GetUIntTvProto(1), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "1", "description"}, testhelper.GetStringTvProto("Subinterface Desc"), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "name"}, testhelper.GetStringTvProto("ethernet-1/2"), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "description"}, testhelper.GetStringTvProto("MyOtherDescription"), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "subinterface", "1", "index"}, testhelper.GetUIntTvProto(1), prio10, owner2, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "subinterface", "1", "description"}, testhelper.GetStringTvProto("Subinterface Desc"), prio10, owner2, 0), + }, + intendedStoreUpdates: []*types.Update{ + types.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto("ethernet-1/1"), prio5, owner1, 0), + types.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto("MyDescription"), prio5, owner1, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "name"}, testhelper.GetStringTvProto("ethernet-1/2"), prio15, owner3, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "description"}, testhelper.GetStringTvProto("Owner3 Description"), prio15, owner3, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "subinterface", "2", "index"}, testhelper.GetUIntTvProto(1), prio15, owner3, 0), + types.NewUpdate([]string{"interface", "ethernet-1/2", "subinterface", "2", "description"}, testhelper.GetStringTvProto("Subinterface Desc"), prio15, owner3, 0), }, }, { - name: "ChoiceCase - new highes case", + name: "ChoiceCase - new highest case", intentName: owner2, intentPrio: prio10, - intentReqPath: "/", + intentReqPath: nil, intentReqValue: func() (string, error) { d := &sdcio_schema.Device{ Choices: &sdcio_schema.SdcioModel_Choices{ @@ -682,25 +679,29 @@ func TestDatastore_populateTree(t *testing.T) { SkipValidation: false, }) }, - expectedModify: []*cache.Update{ - cache.NewUpdate([]string{"choices", "case2", "log"}, TypedValueTrue, prio10, owner2, 0), + expectedModify: []*types.Update{ + types.NewUpdate([]string{"choices", "case2", "log"}, TypedValueTrue, prio10, owner2, 0), }, expectedDeletes: [][]string{ {"choices", "case1"}, }, - expectedOwnerUpdates: []*cache.Update{ - cache.NewUpdate([]string{"choices", "case2", "log"}, TypedValueTrue, prio10, owner2, 0), + expectedOwnerUpdates: []*types.Update{ + types.NewUpdate([]string{"choices", "case2", "log"}, TypedValueTrue, prio10, owner2, 0), + }, + intendedStoreUpdates: []*types.Update{ + types.NewUpdate([]string{"choices", "case1", "case-elem", "elem"}, testhelper.GetStringTvProto("case1-content"), prio15, owner1, 0), + types.NewUpdate([]string{"choices", "case1", "log"}, TypedValueFalse, prio15, owner1, 0), }, - intendedStoreUpdates: []*cache.Update{ - cache.NewUpdate([]string{"choices", "case1", "case-elem", "elem"}, testhelper.GetStringTvProto(t, "case1-content"), prio15, owner1, 0), - cache.NewUpdate([]string{"choices", "case1", "log"}, TypedValueFalse, prio15, owner1, 0), + runningStoreUpdates: []*types.Update{ + types.NewUpdate([]string{"choices", "case1", "case-elem", "elem"}, testhelper.GetStringTvProto("case1-content"), tree.RunningValuesPrio, tree.RunningIntentName, 0), + types.NewUpdate([]string{"choices", "case1", "log"}, TypedValueFalse, tree.RunningValuesPrio, tree.RunningIntentName, 0), }, }, { - name: "ChoiceCase - old highes case", + name: "ChoiceCase - old highest case", intentName: owner2, intentPrio: prio10, - intentReqPath: "/", + intentReqPath: nil, intentReqValue: func() (string, error) { d := &sdcio_schema.Device{ Choices: &sdcio_schema.SdcioModel_Choices{ @@ -714,16 +715,47 @@ func TestDatastore_populateTree(t *testing.T) { SkipValidation: false, }) }, - expectedModify: []*cache.Update{ + expectedModify: []*types.Update{ // no mods expected }, - expectedOwnerUpdates: []*cache.Update{ - cache.NewUpdate([]string{"choices", "case2", "log"}, TypedValueTrue, prio10, owner2, 0), + expectedOwnerUpdates: []*types.Update{ + types.NewUpdate([]string{"choices", "case2", "log"}, TypedValueTrue, prio10, owner2, 0), + }, + intendedStoreUpdates: []*types.Update{ + types.NewUpdate([]string{"choices", "case1", "case-elem", "elem"}, testhelper.GetStringTvProto("case1-content"), prio5, owner1, 0), + types.NewUpdate([]string{"choices", "case1", "log"}, TypedValueFalse, prio5, owner1, 0), + }, + runningStoreUpdates: []*types.Update{ + types.NewUpdate([]string{"choices", "case1", "case-elem", "elem"}, testhelper.GetStringTvProto("case1-content"), tree.RunningValuesPrio, tree.RunningIntentName, 0), + types.NewUpdate([]string{"choices", "case1", "log"}, TypedValueFalse, tree.RunningValuesPrio, tree.RunningIntentName, 0), + }, + }, + { + name: "ChoiceCase - add first case", + intentName: owner2, + intentPrio: prio10, + intentReqPath: nil, + intentReqValue: func() (string, error) { + d := &sdcio_schema.Device{ + Choices: &sdcio_schema.SdcioModel_Choices{ + Case2: &sdcio_schema.SdcioModel_Choices_Case2{ + Log: ygot.Bool(true), + }, + }, + } + return ygot.EmitJSON(d, &ygot.EmitJSONConfig{ + Format: ygot.RFC7951, + SkipValidation: false, + }) + }, + expectedModify: []*types.Update{ + types.NewUpdate([]string{"choices", "case2", "log"}, TypedValueTrue, prio10, owner2, 0), }, - intendedStoreUpdates: []*cache.Update{ - cache.NewUpdate([]string{"choices", "case1", "case-elem", "elem"}, testhelper.GetStringTvProto(t, "case1-content"), prio5, owner1, 0), - cache.NewUpdate([]string{"choices", "case1", "log"}, TypedValueFalse, prio5, owner1, 0), + expectedOwnerUpdates: []*types.Update{ + types.NewUpdate([]string{"choices", "case2", "log"}, TypedValueTrue, prio10, owner2, 0), }, + intendedStoreUpdates: []*types.Update{}, + runningStoreUpdates: []*types.Update{}, }, } @@ -731,108 +763,70 @@ func TestDatastore_populateTree(t *testing.T) { t.Run(tt.name, func(t *testing.T) { // create a gomock controller controller := gomock.NewController(t) + defer controller.Finish() - // create a cache client mock - cacheClient := mockcacheclient.NewMockClient(controller) - testhelper.ConfigureCacheClientMock(t, cacheClient, tt.intendedStoreUpdates, tt.runningStoreUpdates, tt.expectedModify, tt.expectedDeletes) + ctx := context.Background() sc, schema, err := testhelper.InitSDCIOSchema() if err != nil { t.Fatal(err) } + scb := schemaClient.NewSchemaClientBound(schema, sc) + tc := tree.NewTreeContext(scb, tt.intentName) - dsName := "dev1" - ctx := context.Background() - - // create a datastore - d := &Datastore{ - config: &config.DatastoreConfig{ - Name: dsName, - Schema: schema, - Validation: &config.Validation{DisableConcurrency: true}, - }, - - sbi: mocktarget.NewMockTarget(controller), - cacheClient: cacheClient, - schemaClient: schemaClient.NewSchemaClientBound(schema.GetSchema(), sc), - } - - // marshall the intentReqValue into a byte slice - jsonConf, err := tt.intentReqValue() + root, err := tree.NewTreeRoot(ctx, tc) if err != nil { t.Error(err) } - // parse the path under which the intent value is to be put - path, err := utils.ParsePath(tt.intentReqPath) + jconfStr, err := tt.intentReqValue() if err != nil { t.Error(err) } - // prepare the SetintentRequest - reqOne := &sdcpb.SetIntentRequest{ - Name: dsName, - Intent: tt.intentName, - Priority: tt.intentPrio, - Update: []*sdcpb.Update{ - { - Path: path, - Value: &sdcpb.TypedValue{ - Value: &sdcpb.TypedValue_JsonVal{ - JsonVal: []byte(jsonConf)}, - }, - }, - }, - Delete: tt.intentDelete, - } - - tcc := tree.NewTreeCacheClient(d.Name(), cacheClient) - tc := tree.NewTreeContext(tcc, d.schemaClient, tt.intentName) - - root, err := tree.NewTreeRoot(ctx, tc) + var jsonConfAny any + err = json.Unmarshal([]byte(jconfStr), &jsonConfAny) if err != nil { t.Error(err) } - updSlice, err := d.expandAndConvertIntent(ctx, reqOne.GetIntent(), reqOne.GetPriority(), reqOne.GetUpdate()) + // add intended content + err = root.AddUpdatesRecursive(ctx, tt.intendedStoreUpdates, types.NewUpdateInsertFlags()) if err != nil { t.Error(err) } - oldIntentContent, err := root.LoadIntendedStoreOwnerData(ctx, reqOne.GetIntent(), false) + // add running content + err = root.AddUpdatesRecursive(ctx, tt.runningStoreUpdates, types.NewUpdateInsertFlags()) if err != nil { t.Error(err) } - loadHighest := oldIntentContent.ToPathSet() - loadHighest.Join(updSlice.ToPathSet()) + root.MarkOwnerDelete(tt.intentName, false) + + newFlag := types.NewUpdateInsertFlags().SetNewFlag() - err = loadIntendedStoreHighestPrio(ctx, tcc, root, loadHighest, []string{reqOne.GetIntent()}) + err = root.ImportConfig(ctx, tt.intentReqPath, jsonImporter.NewJsonTreeImporter(jsonConfAny), tt.intentName, tt.intentPrio, newFlag) if err != nil { t.Error(err) } - flags := tree.NewUpdateInsertFlags() - flags.SetNewFlag() - root.AddCacheUpdatesRecursive(ctx, updSlice, flags) - - // populate Tree with running - err = populateTreeWithRunning(ctx, tcc, root) + fmt.Println(root.String()) + err = root.FinishInsertionPhase(ctx) if err != nil { t.Error(err) } + fmt.Println(root.String()) - root.FinishInsertionPhase(ctx) - - validationResult := root.Validate(ctx, d.config.Validation) + validationResult := root.Validate(ctx, &config.Validation{DisableConcurrency: true}) fmt.Printf("Validation Errors:\n%v\n", strings.Join(validationResult.ErrorsStr(), "\n")) fmt.Printf("Tree:%s\n", root.String()) // get the updates that are meant to be send down towards the device updates := root.GetHighestPrecedence(!tt.NotOnlyNewOrUpdated) - if diff := testhelper.DiffCacheUpdates(tt.expectedModify, updates.ToCacheUpdateSlice()); diff != "" { - t.Errorf("root.GetHighestPrecedence(true) mismatch (-want +got):\n%s", diff) + if diff := testhelper.DiffUpdates(tt.expectedModify, updates.ToUpdateSlice()); diff != "" { + t.Errorf("root.GetHighestPrecedence(%t) mismatch (-want +got):\n%s", !tt.NotOnlyNewOrUpdated, diff) } // get the deletes that are meant to be send down towards the device @@ -841,7 +835,7 @@ func TestDatastore_populateTree(t *testing.T) { t.Error(err) } - deletePathSlice := make(tree.PathSlices, 0, len(deletes)) + deletePathSlice := make(types.PathSlices, 0, len(deletes)) for _, del := range deletes { deletePathSlice = append(deletePathSlice, del.Path()) } @@ -852,7 +846,7 @@ func TestDatastore_populateTree(t *testing.T) { // get the updates that are meant to be send down towards the cache (INTENDED) updatesOwner := root.GetUpdatesForOwner(tt.intentName) - if diff := testhelper.DiffCacheUpdates(tt.expectedOwnerUpdates, updatesOwner); diff != "" { + if diff := testhelper.DiffUpdates(tt.expectedOwnerUpdates, updatesOwner); diff != "" { t.Errorf("root.GetUpdatesForOwner mismatch (-want +got):\n%s", diff) } diff --git a/pkg/datastore/tree_operation_validation_test.go b/pkg/datastore/tree_operation_validation_test.go index 740fa75a..78ec3566 100644 --- a/pkg/datastore/tree_operation_validation_test.go +++ b/pkg/datastore/tree_operation_validation_test.go @@ -16,22 +16,21 @@ package datastore import ( "context" + "encoding/json" "fmt" "slices" "testing" "github.com/openconfig/ygot/ygot" - "github.com/sdcio/data-server/mocks/mockcacheclient" - "github.com/sdcio/data-server/mocks/mocktarget" "github.com/sdcio/data-server/pkg/cache" "github.com/sdcio/data-server/pkg/config" schemaClient "github.com/sdcio/data-server/pkg/datastore/clients/schema" "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/types" "github.com/sdcio/data-server/pkg/utils" "github.com/sdcio/data-server/pkg/utils/testhelper" sdcio_schema "github.com/sdcio/data-server/tests/sdcioygot" - sdcpb "github.com/sdcio/sdc-protos/sdcpb" - "go.uber.org/mock/gomock" ) func TestDatastore_validateTree(t *testing.T) { @@ -160,37 +159,22 @@ func TestDatastore_validateTree(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - // create a gomock controller - controller := gomock.NewController(t) - - // create a cache client mock - cacheClient := mockcacheclient.NewMockClient(controller) - testhelper.ConfigureCacheClientMock(t, cacheClient, tt.intendedStoreUpdates, nil, nil, nil) sc, schema, err := testhelper.InitSDCIOSchema() if err != nil { - t.Fatal(err) - } - - dsName := "dev1" - - // create a datastore - d := &Datastore{ - config: &config.DatastoreConfig{ - Name: dsName, - Schema: schema, - Validation: &config.Validation{DisableConcurrency: true}, - }, - - sbi: mocktarget.NewMockTarget(controller), - cacheClient: cacheClient, - schemaClient: schemaClient.NewSchemaClientBound(schema.GetSchema(), sc), + t.Error(err) } - + scb := schemaClient.NewSchemaClientBound(schema, sc) ctx := context.Background() // marshall the intentReqValue into a byte slice - jsonConf, err := tt.intentReqValue() + jsonConfString, err := tt.intentReqValue() + if err != nil { + t.Error(err) + } + + var jsonConf any + err = json.Unmarshal([]byte(jsonConfString), &jsonConf) if err != nil { t.Error(err) } @@ -201,35 +185,30 @@ func TestDatastore_validateTree(t *testing.T) { t.Error(err) } - tcc := tree.NewTreeCacheClient(dsName, d.cacheClient) - tc := tree.NewTreeContext(tcc, d.schemaClient, tt.intentName) + tc := tree.NewTreeContext(scb, tt.intentName) root, err := tree.NewTreeRoot(ctx, tc) if err != nil { t.Error(err) } - insertUpdates := []*sdcpb.Update{ - { - Path: path, - Value: &sdcpb.TypedValue{ - Value: &sdcpb.TypedValue_JsonVal{ - JsonVal: []byte(jsonConf)}, - }, - }, - } + flagsNew := types.NewUpdateInsertFlags() + flagsNew.SetNewFlag() + + importer := json_importer.NewJsonTreeImporter(jsonConf) - updSlice, err := d.expandAndConvertIntent(ctx, tt.intentName, tt.intentPrio, insertUpdates) + err = root.ImportConfig(ctx, utils.ToStrings(path, false, false), importer, tt.intentName, tt.intentPrio, flagsNew) if err != nil { t.Error(err) } - flagsNew := tree.NewUpdateInsertFlags() - flagsNew.SetNewFlag() - root.AddCacheUpdatesRecursive(ctx, updSlice, flagsNew) + err = root.FinishInsertionPhase(ctx) + if err != nil { + t.Error(err) + } - root.FinishInsertionPhase(ctx) + validationResult := root.Validate(ctx, &config.Validation{DisableConcurrency: true}) - validationResult := root.Validate(ctx, d.config.Validation) + t.Log(root.String()) for _, x := range tt.expectedWarnings { if !slices.Contains(validationResult.WarningsStr(), x) { diff --git a/pkg/datastore/types/transaction.go b/pkg/datastore/types/transaction.go index d1a9c847..cbc26a92 100644 --- a/pkg/datastore/types/transaction.go +++ b/pkg/datastore/types/transaction.go @@ -6,6 +6,7 @@ import ( "time" "github.com/sdcio/data-server/pkg/tree" + treetypes "github.com/sdcio/data-server/pkg/tree/types" ) type Transaction struct { @@ -26,7 +27,7 @@ func NewTransaction(id string, tm *TransactionManager) *Transaction { transactionManager: tm, newIntents: map[string]*TransactionIntent{}, oldIntents: map[string]*TransactionIntent{}, - oldRunning: NewTransactionIntent("oldrunning", 600), + oldRunning: NewTransactionIntent(tree.RunningIntentName, 600), replace: NewTransactionIntent(tree.ReplaceIntentName, tree.ReplaceValuesPrio), } } @@ -129,7 +130,7 @@ func (t *Transaction) AddTransactionIntent(ti *TransactionIntent, tit Transactio } // AddIntentContent add the content of an intent. If the intent did not exist, add the name of the intent and content == nil. -func (t *Transaction) AddIntentContent(name string, tit TransactionIntentType, priority int32, content tree.UpdateSlice) error { +func (t *Transaction) AddIntentContent(name string, tit TransactionIntentType, priority int32, content treetypes.UpdateSlice) error { dstMap := t.getTransactionIntentTypeMap(tit) _, exists := dstMap[name] if exists { @@ -142,9 +143,9 @@ func (t *Transaction) AddIntentContent(name string, tit TransactionIntentType, p return nil } -func (t *Transaction) GetPathSet(tit TransactionIntentType) *tree.PathSet { +func (t *Transaction) GetPathSet(tit TransactionIntentType) *treetypes.PathSet { srcMap := t.getTransactionIntentTypeMap(tit) - ps := tree.NewPathSet() + ps := treetypes.NewPathSet() for _, intent := range srcMap { ps.Join(intent.GetPathSet()) } diff --git a/pkg/datastore/types/transaction_intent.go b/pkg/datastore/types/transaction_intent.go index 4927dc24..2d5feb50 100644 --- a/pkg/datastore/types/transaction_intent.go +++ b/pkg/datastore/types/transaction_intent.go @@ -1,14 +1,13 @@ package types import ( - "github.com/sdcio/data-server/pkg/cache" - "github.com/sdcio/data-server/pkg/tree" + treetypes "github.com/sdcio/data-server/pkg/tree/types" ) type TransactionIntent struct { name string // updates is nil if the intent did not exist. - updates tree.UpdateSlice + updates treetypes.UpdateSlice delete bool onlyIntended bool priority int32 @@ -16,7 +15,9 @@ type TransactionIntent struct { func NewTransactionIntent(name string, priority int32) *TransactionIntent { return &TransactionIntent{ - name: name, + name: name, + updates: make(treetypes.UpdateSlice, 0), + priority: priority, } } @@ -28,11 +29,11 @@ func (ti *TransactionIntent) GetPriority() int32 { return ti.priority } -func (ti *TransactionIntent) AddUpdates(u tree.UpdateSlice) { +func (ti *TransactionIntent) AddUpdates(u treetypes.UpdateSlice) { ti.updates = append(ti.updates, u...) } -func (ti *TransactionIntent) GetUpdates() tree.UpdateSlice { +func (ti *TransactionIntent) GetUpdates() treetypes.UpdateSlice { return ti.updates } @@ -48,10 +49,10 @@ func (ti *TransactionIntent) SetDeleteOnlyIntendedFlag() { ti.onlyIntended = true } -func (ti *TransactionIntent) GetPathSet() *tree.PathSet { +func (ti *TransactionIntent) GetPathSet() *treetypes.PathSet { return ti.updates.ToPathSet() } -func (ti *TransactionIntent) AddUpdate(u *cache.Update) { +func (ti *TransactionIntent) AddUpdate(u *treetypes.Update) { ti.updates = append(ti.updates, u) } diff --git a/pkg/schema/remote.go b/pkg/schema/remote.go index 91b9dd19..507e013f 100644 --- a/pkg/schema/remote.go +++ b/pkg/schema/remote.go @@ -56,7 +56,7 @@ func NewRemoteClient(cc *grpc.ClientConn, cacheConfig *config.RemoteSchemaCache) } // rc with cache rc := &remoteClient{ - schemaCache: ttlcache.New[cacheKey, *sdcpb.GetSchemaResponse]( + schemaCache: ttlcache.New( ttlcache.WithTTL[cacheKey, *sdcpb.GetSchemaResponse](cacheConfig.TTL), ttlcache.WithCapacity[cacheKey, *sdcpb.GetSchemaResponse](cacheConfig.Capacity), ), diff --git a/pkg/server/cache.go b/pkg/server/cache.go index 7209dbdb..f0d2face 100644 --- a/pkg/server/cache.go +++ b/pkg/server/cache.go @@ -41,14 +41,6 @@ START: goto START } log.Infof("local cache created") - // case "remote": - // err = s.createRemoteCacheClient(ctx) - // if err != nil { - // log.Errorf("failed to initialize a remote cache client: %v", err) - // time.Sleep(time.Second) - // goto START - // } - // log.Infof("connected to remote cache: %s", s.config.Cache.Address) } } @@ -56,16 +48,8 @@ func (s *Server) createLocalCacheClient(_ context.Context) error { var err error log.Infof("initializing local cache client") s.cacheClient, err = cache.NewLocalCache(&cconfig.CacheConfig{ - MaxCaches: -1, StoreType: s.config.Cache.StoreType, Dir: s.config.Cache.Dir, }) return err } - -// func (s *Server) createRemoteCacheClient(ctx context.Context) error { -// log.Infof("initializing remote cache client") -// var err error -// s.cacheClient, err = cache.NewRemoteCache(ctx, s.config.Cache.Address) -// return err -// } diff --git a/pkg/server/data.go b/pkg/server/data.go deleted file mode 100644 index 5a57a340..00000000 --- a/pkg/server/data.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2024 Nokia -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "strings" - "sync" - "time" - - sdcpb "github.com/sdcio/sdc-protos/sdcpb" - log "github.com/sirupsen/logrus" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/peer" - "google.golang.org/grpc/status" -) - -// data -func (s *Server) GetData(req *sdcpb.GetDataRequest, stream sdcpb.DataServer_GetDataServer) error { - pr, _ := peer.FromContext(stream.Context()) - log.Debugf("received GetData request %v from peer %s", req, pr.Addr.String()) - name := req.GetName() - if name == "" { - return status.Error(codes.InvalidArgument, "missing datastore name") - } - switch req.GetDataType() { - case sdcpb.DataType_STATE: - if req.GetDatastore().GetType() == sdcpb.Type_CANDIDATE { - return status.Error(codes.InvalidArgument, "a candidate datastore does not store state data") - } - } - if len(req.GetPath()) == 0 { - return status.Error(codes.InvalidArgument, "missing path attribute") - } - - s.md.RLock() - defer s.md.RUnlock() - ds, ok := s.datastores[name] - if !ok { - return status.Errorf(codes.InvalidArgument, "unknown datastore %s", name) - } - wg := new(sync.WaitGroup) - wg.Add(1) - nCh := make(chan *sdcpb.GetDataResponse) - go func() { - defer wg.Done() - for { - select { - case <-stream.Context().Done(): - return - case rsp, ok := <-nCh: - if !ok { - return - } - err := stream.Send(rsp) - if err != nil { - if strings.Contains(err.Error(), "context canceled") || strings.Contains(err.Error(), "EOF") { - return - } - log.Errorf("GetData stream send err :%v", err) - } - } - } - }() - err := ds.Get(stream.Context(), req, nCh) - if err != nil { - return err - } - wg.Wait() - return nil -} - -func (s *Server) Subscribe(req *sdcpb.SubscribeRequest, stream sdcpb.DataServer_SubscribeServer) error { - log.Infof("received SubscribeRequest: %v", req) - name := req.GetName() - if name == "" { - return status.Errorf(codes.InvalidArgument, "missing datastore name") - } - - if len(req.GetSubscription()) == 0 { - return status.Errorf(codes.InvalidArgument, "missing subscription list in request") - } - // TODO: set subscribe request defaults - for _, subsc := range req.GetSubscription() { - if subsc.GetSampleInterval() < uint64(time.Second) { - subsc.SampleInterval = uint64(time.Second) - } - } - - s.md.RLock() - ds, ok := s.datastores[name] - s.md.RUnlock() - if !ok { - return status.Errorf(codes.InvalidArgument, "unknown datastore %s", name) - } - return ds.Subscribe(req, stream) -} - -func (s *Server) Watch(req *sdcpb.WatchRequest, stream sdcpb.DataServer_WatchServer) error { - return nil -} diff --git a/pkg/server/datastore.go b/pkg/server/datastore.go index 72b32add..f70c99ac 100644 --- a/pkg/server/datastore.go +++ b/pkg/server/datastore.go @@ -36,11 +36,9 @@ import ( func (s *Server) ListDataStore(ctx context.Context, req *sdcpb.ListDataStoreRequest) (*sdcpb.ListDataStoreResponse, error) { log.Debug("Received ListDataStoreRequest") - s.md.RLock() - defer s.md.RUnlock() - numDs := len(s.datastores) - rs := make([]*sdcpb.GetDataStoreResponse, 0, numDs) - for _, ds := range s.datastores { + datastores := s.datastores.GetDatastoreAll() + rs := make([]*sdcpb.GetDataStoreResponse, 0, len(datastores)) + for _, ds := range datastores { r, err := s.datastoreToRsp(ctx, ds) if err != nil { return nil, err @@ -54,22 +52,20 @@ func (s *Server) ListDataStore(ctx context.Context, req *sdcpb.ListDataStoreRequ func (s *Server) GetDataStore(ctx context.Context, req *sdcpb.GetDataStoreRequest) (*sdcpb.GetDataStoreResponse, error) { log.Debugf("Received GetDataStoreRequest: %v", req) - name := req.GetName() + name := req.GetDatastoreName() if name == "" { return nil, status.Error(codes.InvalidArgument, "missing datastore name attribute") } - s.md.RLock() - defer s.md.RUnlock() - ds, ok := s.datastores[name] - if !ok { - return nil, status.Errorf(codes.InvalidArgument, "unknown datastore %s", name) + ds, err := s.datastores.GetDataStore(req.GetDatastoreName()) + if err != nil { + return nil, err } return s.datastoreToRsp(ctx, ds) } func (s *Server) CreateDataStore(ctx context.Context, req *sdcpb.CreateDataStoreRequest) (*sdcpb.CreateDataStoreResponse, error) { log.Debugf("Received CreateDataStoreRequest: %v", req) - name := req.GetName() + name := req.GetDatastoreName() lName := len(name) if lName == 0 { return nil, status.Error(codes.InvalidArgument, "missing datastore name attribute") @@ -77,149 +73,116 @@ func (s *Server) CreateDataStore(ctx context.Context, req *sdcpb.CreateDataStore if lName > math.MaxUint16 { return nil, status.Error(codes.InvalidArgument, "missing datastore name attribute") } - switch { - // create candidate - case req.GetSchema() == nil && req.GetDatastore() != nil: - s.md.RLock() - defer s.md.RUnlock() - ds, ok := s.datastores[name] - if !ok { - return nil, status.Errorf(codes.InvalidArgument, "unknown datastore %s", name) - } - switch req.GetDatastore().GetType() { - case sdcpb.Type_CANDIDATE: - owner := req.GetDatastore().GetOwner() - lOwner := len(owner) - if lOwner == 0 { - return nil, status.Error(codes.InvalidArgument, "missing owner name attribute") - } - if lOwner > math.MaxUint16 { - return nil, status.Errorf(codes.InvalidArgument, "owner name too long(%d>%d)", lOwner, math.MaxUint16) - } - if strings.HasPrefix(owner, "__") && owner != datastore.DefaultOwner { - return nil, status.Error(codes.InvalidArgument, "owner name cannot start with `__`") - } - err := ds.CreateCandidate(ctx, req.GetDatastore()) - if err != nil { - return nil, status.Errorf(codes.Internal, "%v", err) - } - return &sdcpb.CreateDataStoreResponse{}, nil - default: - return nil, status.Errorf(codes.InvalidArgument, "schema required for MAIN datastore creation") - } - // create main - case req.GetSchema() != nil: - s.md.Lock() - defer s.md.Unlock() - if _, ok := s.datastores[name]; ok { - return nil, status.Errorf(codes.InvalidArgument, "datastore %s already exists", name) - } - sbi := &config.SBI{ - Type: req.GetTarget().GetType(), - Port: req.GetTarget().GetPort(), - Address: req.GetTarget().GetAddress(), - } + if _, err := s.datastores.GetDataStore(name); err == nil { + return nil, status.Errorf(codes.InvalidArgument, "datastore %s already exists", name) + } - switch strings.ToLower(req.GetTarget().GetType()) { - case "netconf": - commitDatastore := "candidate" - switch req.GetTarget().GetNetconfOpts().GetCommitCandidate() { - case sdcpb.CommitCandidate_COMMIT_CANDIDATE: - case sdcpb.CommitCandidate_COMMIT_RUNNING: - commitDatastore = "running" - default: - return nil, fmt.Errorf("unknown commitDatastore: %v", req.GetTarget().GetNetconfOpts().GetCommitCandidate()) - } - sbi.NetconfOptions = &config.SBINetconfOptions{ - IncludeNS: req.GetTarget().GetNetconfOpts().GetIncludeNs(), - OperationWithNamespace: req.GetTarget().GetNetconfOpts().GetOperationWithNs(), - UseOperationRemove: req.GetTarget().GetNetconfOpts().GetUseOperationRemove(), - CommitDatastore: commitDatastore, - } + sbi := &config.SBI{ + Type: req.GetTarget().GetType(), + Port: req.GetTarget().GetPort(), + Address: req.GetTarget().GetAddress(), + } - case "gnmi": - sbi.GnmiOptions = &config.SBIGnmiOptions{ - Encoding: req.GetTarget().GetGnmiOpts().GetEncoding(), - } + switch strings.ToLower(req.GetTarget().GetType()) { + case "netconf": + commitDatastore := "candidate" + switch req.GetTarget().GetNetconfOpts().GetCommitCandidate() { + case sdcpb.CommitCandidate_COMMIT_CANDIDATE: + case sdcpb.CommitCandidate_COMMIT_RUNNING: + commitDatastore = "running" default: - return nil, fmt.Errorf("unknowm protocol type %s", req.GetTarget().GetType()) + return nil, fmt.Errorf("unknown commitDatastore: %v", req.GetTarget().GetNetconfOpts().GetCommitCandidate()) + } + sbi.NetconfOptions = &config.SBINetconfOptions{ + IncludeNS: req.GetTarget().GetNetconfOpts().GetIncludeNs(), + OperationWithNamespace: req.GetTarget().GetNetconfOpts().GetOperationWithNs(), + UseOperationRemove: req.GetTarget().GetNetconfOpts().GetUseOperationRemove(), + CommitDatastore: commitDatastore, } - if req.GetTarget().GetTls() != nil { - sbi.TLS = &config.TLS{ - CA: req.GetTarget().GetTls().GetCa(), - Cert: req.GetTarget().GetTls().GetCert(), - Key: req.GetTarget().GetTls().GetKey(), - SkipVerify: req.GetTarget().GetTls().GetSkipVerify(), - } + case "gnmi": + sbi.GnmiOptions = &config.SBIGnmiOptions{ + Encoding: req.GetTarget().GetGnmiOpts().GetEncoding(), } - if req.GetTarget().GetCredentials() != nil { - sbi.Credentials = &config.Creds{ - Username: req.GetTarget().GetCredentials().GetUsername(), - Password: req.GetTarget().GetCredentials().GetPassword(), - Token: req.GetTarget().GetCredentials().GetToken(), - } + default: + return nil, fmt.Errorf("unknowm protocol type %s", req.GetTarget().GetType()) + } + + if req.GetTarget().GetTls() != nil { + sbi.TLS = &config.TLS{ + CA: req.GetTarget().GetTls().GetCa(), + Cert: req.GetTarget().GetTls().GetCert(), + Key: req.GetTarget().GetTls().GetKey(), + SkipVerify: req.GetTarget().GetTls().GetSkipVerify(), + } + } + if req.GetTarget().GetCredentials() != nil { + sbi.Credentials = &config.Creds{ + Username: req.GetTarget().GetCredentials().GetUsername(), + Password: req.GetTarget().GetCredentials().GetPassword(), + Token: req.GetTarget().GetCredentials().GetToken(), } + } - dsConfig := &config.DatastoreConfig{ - Name: name, - Schema: &config.SchemaConfig{ - Name: req.GetSchema().GetName(), - Vendor: req.GetSchema().GetVendor(), - Version: req.GetSchema().GetVersion(), - }, - SBI: sbi, - Validation: s.config.Validation, + dsConfig := &config.DatastoreConfig{ + Name: name, + Schema: &config.SchemaConfig{ + Name: req.GetSchema().GetName(), + Vendor: req.GetSchema().GetVendor(), + Version: req.GetSchema().GetVersion(), + }, + SBI: sbi, + } + if req.GetSync() != nil { + dsConfig.Sync = &config.Sync{ + Validate: req.GetSync().GetValidate(), + Buffer: req.GetSync().GetBuffer(), + WriteWorkers: req.GetSync().GetWriteWorkers(), + Config: make([]*config.SyncProtocol, 0, len(req.GetSync().GetConfig())), } - if req.GetSync() != nil { - dsConfig.Sync = &config.Sync{ - Validate: req.GetSync().GetValidate(), - Buffer: req.GetSync().GetBuffer(), - WriteWorkers: req.GetSync().GetWriteWorkers(), - Config: make([]*config.SyncProtocol, 0, len(req.GetSync().GetConfig())), + for _, pSync := range req.GetSync().GetConfig() { + gnSyncConfig := &config.SyncProtocol{ + Protocol: pSync.GetTarget().GetType(), + Name: pSync.GetName(), + Paths: pSync.GetPath(), + Interval: time.Duration(pSync.GetInterval()), } - for _, pSync := range req.GetSync().GetConfig() { - gnSyncConfig := &config.SyncProtocol{ - Protocol: pSync.GetTarget().GetType(), - Name: pSync.GetName(), - Paths: pSync.GetPath(), - Interval: time.Duration(pSync.GetInterval()), - } - switch pSync.GetTarget().GetType() { - case "gnmi": - gnSyncConfig.Mode = "on-change" - switch pSync.GetMode() { - case sdcpb.SyncMode_SM_ON_CHANGE: - case sdcpb.SyncMode_SM_SAMPLE: - gnSyncConfig.Mode = "sample" - case sdcpb.SyncMode_SM_ONCE: - gnSyncConfig.Mode = "once" - case sdcpb.SyncMode_SM_GET: - gnSyncConfig.Mode = "get" - } - gnSyncConfig.Encoding = pSync.GetTarget().GetGnmiOpts().GetEncoding() - case "netconf": - default: - return nil, status.Errorf(codes.InvalidArgument, "unknown sync protocol: %q", pSync.GetTarget().GetType()) + switch strings.ToLower(pSync.GetTarget().GetType()) { + case "gnmi": + gnSyncConfig.Mode = "on-change" + switch pSync.GetMode() { + case sdcpb.SyncMode_SM_ON_CHANGE: + case sdcpb.SyncMode_SM_SAMPLE: + gnSyncConfig.Mode = "sample" + case sdcpb.SyncMode_SM_ONCE: + gnSyncConfig.Mode = "once" + case sdcpb.SyncMode_SM_GET: + gnSyncConfig.Mode = "get" } - dsConfig.Sync.Config = append(dsConfig.Sync.Config, gnSyncConfig) + gnSyncConfig.Encoding = pSync.GetTarget().GetGnmiOpts().GetEncoding() + case "netconf": + default: + return nil, status.Errorf(codes.InvalidArgument, "unknown sync protocol: %q", pSync.GetTarget().GetType()) } + dsConfig.Sync.Config = append(dsConfig.Sync.Config, gnSyncConfig) } - err := dsConfig.ValidateSetDefaults() - if err != nil { - return nil, status.Errorf(codes.InvalidArgument, "invalid datastore config: %v", err) - } - s.datastores[req.GetName()] = datastore.New( - s.ctx, - dsConfig, - s.schemaClient, - s.cacheClient, - s.gnmiOpts...) - return &sdcpb.CreateDataStoreResponse{}, nil - default: - return nil, status.Errorf(codes.InvalidArgument, "schema or datastore must be set") } + err := dsConfig.ValidateSetDefaults() + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "invalid datastore config: %v", err) + } + ds, err := datastore.New( + s.ctx, + dsConfig, + s.schemaClient, + s.cacheClient, + s.gnmiOpts...) + if err != nil { + return nil, err + } + s.datastores.AddDatastore(ds) + return &sdcpb.CreateDataStoreResponse{}, nil } func (s *Server) DeleteDataStore(ctx context.Context, req *sdcpb.DeleteDataStoreRequest) (*sdcpb.DeleteDataStoreResponse, error) { @@ -228,60 +191,20 @@ func (s *Server) DeleteDataStore(ctx context.Context, req *sdcpb.DeleteDataStore if name == "" { return nil, status.Error(codes.InvalidArgument, "missing datastore name attribute") } - s.md.Lock() - defer s.md.Unlock() - ds, ok := s.datastores[name] - if !ok { - return nil, status.Errorf(codes.InvalidArgument, "unknown datastore %s", name) - } - switch { - case req.GetDatastore() == nil: - err := ds.Stop() - if err != nil { - log.Errorf("failed to stop datastore %s: %v", name, err) - } - err = ds.DeleteCache(ctx) - if err != nil { - log.Errorf("failed to delete the datastore %s cache: %v", name, err) - } - delete(s.datastores, name) - log.Infof("deleted datastore %s", name) - return &sdcpb.DeleteDataStoreResponse{}, nil - default: - switch req.GetDatastore().GetType() { - case *sdcpb.Type_CANDIDATE.Enum(): - ds.DeleteCandidate(ctx, req.GetDatastore().GetName()) - log.Infof("datastore %s deleted candidate %s", name, req.GetDatastore().GetName()) - case *sdcpb.Type_MAIN.Enum(): - err := ds.Stop() - if err != nil { - log.Errorf("failed to stop datastore %s: %v", name, err) - } - delete(s.datastores, name) - log.Infof("deleted datastore %s", name) - } - return &sdcpb.DeleteDataStoreResponse{}, nil + ds, err := s.datastores.GetDataStore(name) + if err != nil { + return nil, err } -} -func (s *Server) Discard(ctx context.Context, req *sdcpb.DiscardRequest) (*sdcpb.DiscardResponse, error) { - log.Debugf("Received DiscardDataStoreRequest: %v", req) - name := req.GetName() - if name == "" { - return nil, status.Error(codes.InvalidArgument, "missing name attribute") - } - s.md.RLock() - defer s.md.RUnlock() - ds, ok := s.datastores[name] - if !ok { - return nil, status.Errorf(codes.InvalidArgument, "unknown datastore %s", name) - } - err := ds.Discard(ctx, req) + err = ds.Stop() if err != nil { - return nil, status.Errorf(codes.Internal, "%v", err) + log.Errorf("failed to stop datastore %s: %v", name, err) } - return &sdcpb.DiscardResponse{}, nil + s.datastores.DeleteDatastore(ctx, name) + log.Infof("deleted datastore %s", name) + + return &sdcpb.DeleteDataStoreResponse{}, nil } func (s *Server) WatchDeviations(req *sdcpb.WatchDeviationRequest, stream sdcpb.DataServer_WatchDeviationsServer) error { @@ -294,38 +217,35 @@ func (s *Server) WatchDeviations(req *sdcpb.WatchDeviationRequest, stream sdcpb. if req.GetName() == nil { return status.Errorf(codes.InvalidArgument, "missing datastore name") } - s.md.RLock() - ds, ok := s.datastores[req.GetName()[0]] - s.md.RUnlock() - if !ok { - return status.Errorf(codes.InvalidArgument, "unknown datastore name: %s", req.GetName()[0]) + + ds, err := s.datastores.GetDataStore(req.GetName()[0]) + if err != nil { + log.Error(err) + return status.Errorf(codes.NotFound, "unknown datastore") + } + + err = ds.WatchDeviations(req, stream) + if err != nil { + log.Error(err) } - _ = ds.WatchDeviations(req, stream) <-stream.Context().Done() ds.StopDeviationsWatch(peerInfo.Addr.String()) return nil } func (s *Server) datastoreToRsp(ctx context.Context, ds *datastore.Datastore) (*sdcpb.GetDataStoreResponse, error) { - cands, err := ds.Candidates(ctx) - if err != nil { - return nil, err - } + var err error rsp := &sdcpb.GetDataStoreResponse{ - Name: ds.Config().Name, - Datastore: make([]*sdcpb.DataStore, 0, len(cands)+1), + DatastoreName: ds.Config().Name, } - rsp.Datastore = append(rsp.Datastore, - &sdcpb.DataStore{ - Type: *sdcpb.Type_MAIN.Enum().Enum(), - Name: ds.Config().Name, - }, - ) - rsp.Datastore = append(rsp.Datastore, cands...) rsp.Target = &sdcpb.Target{ Type: ds.Config().SBI.Type, Address: ds.Config().SBI.Address, } + rsp.Intents, err = ds.IntentsList(ctx) + if err != nil { + return nil, err + } // map datastore sbi conn state to sdcpb.TargetStatus switch ds.ConnectionState().Status { case target.TargetStatusConnected: diff --git a/pkg/server/intent.go b/pkg/server/intent.go index 14d9b84c..bcef2b52 100644 --- a/pkg/server/intent.go +++ b/pkg/server/intent.go @@ -1,82 +1,119 @@ -// Copyright 2024 Nokia -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - package server import ( "context" + "encoding/json" + "fmt" sdcpb "github.com/sdcio/sdc-protos/sdcpb" - log "github.com/sirupsen/logrus" "google.golang.org/grpc/codes" - "google.golang.org/grpc/peer" "google.golang.org/grpc/status" ) -func (s *Server) GetIntent(ctx context.Context, req *sdcpb.GetIntentRequest) (*sdcpb.GetIntentResponse, error) { - pr, _ := peer.FromContext(ctx) - log.Debugf("received GetIntent request %v from peer %s", req, pr.Addr.String()) - - if req.GetName() == "" { +func (s *Server) ListIntent(ctx context.Context, req *sdcpb.ListIntentRequest) (*sdcpb.ListIntentResponse, error) { + if req.GetDatastoreName() == "" { return nil, status.Error(codes.InvalidArgument, "missing datastore name") } - if req.GetIntent() == "" { - return nil, status.Error(codes.InvalidArgument, "missing intent name") + + ds, err := s.datastores.getDataStore(req.GetDatastoreName()) + if err != nil { + return nil, status.Error(codes.NotFound, err.Error()) } - if req.GetPriority() == 0 { - return nil, status.Error(codes.InvalidArgument, "missing intent priority") + resp := &sdcpb.ListIntentResponse{ + DatastoreName: req.DatastoreName, } - ds, err := s.getDataStore(req.Name) + + intentNames, err := ds.IntentsList(ctx) if err != nil { - return nil, status.Error(codes.NotFound, err.Error()) + return nil, err } - return ds.GetIntent(ctx, req) -} -// func (s *Server) SetIntent(ctx context.Context, req *sdcpb.SetIntentRequest) (*sdcpb.SetIntentResponse, error) { -// pr, _ := peer.FromContext(ctx) -// log.Debugf("received SetIntent request %v from peer %s", req, pr.Addr.String()) - -// if req.GetName() == "" { -// return nil, status.Error(codes.InvalidArgument, "missing datastore name") -// } -// if req.GetIntent() == "" { -// return nil, status.Error(codes.InvalidArgument, "missing intent name") -// } -// if len(req.GetUpdate()) == 0 && !req.GetDelete() { -// return nil, status.Error(codes.InvalidArgument, "updates or a delete flag must be set") -// } -// if len(req.GetUpdate()) != 0 && req.GetDelete() { -// return nil, status.Error(codes.InvalidArgument, "both updates and the delete flag cannot be set at the same time") -// } -// ds, err := s.getDataStore(req.Name) -// if err != nil { -// return nil, status.Error(codes.NotFound, err.Error()) -// } -// return ds.SetIntent(ctx, req) -// } + resp.Intent = intentNames -func (s *Server) ListIntent(ctx context.Context, req *sdcpb.ListIntentRequest) (*sdcpb.ListIntentResponse, error) { - pr, _ := peer.FromContext(ctx) - log.Debugf("received ListIntent request %v from peer %s", req, pr.Addr.String()) + return resp, nil + +} - if req.GetName() == "" { +func (s *Server) GetIntent(ctx context.Context, req *sdcpb.GetIntentRequest) (*sdcpb.GetIntentResponse, error) { + if req.GetDatastoreName() == "" { return nil, status.Error(codes.InvalidArgument, "missing datastore name") } - ds, err := s.getDataStore(req.Name) + + if req.GetIntent() == "" { + return nil, status.Error(codes.InvalidArgument, "missing intent name") + } + + // retrieve the referenced datastore + ds, err := s.datastores.getDataStore(req.GetDatastoreName()) if err != nil { return nil, status.Error(codes.NotFound, err.Error()) } - return ds.ListIntent(ctx, req) + + rsp, err := ds.GetIntent(ctx, req.GetIntent()) + if err != nil { + return nil, err + } + + switch req.GetFormat() { + case sdcpb.Format_Intent_Format_JSON, sdcpb.Format_Intent_Format_JSON_IETF: + var j any + switch req.GetFormat() { + case sdcpb.Format_Intent_Format_JSON: + j, err = rsp.ToJson() + if err != nil { + return nil, err + } + case sdcpb.Format_Intent_Format_JSON_IETF: + j, err = rsp.ToJsonIETF() + if err != nil { + return nil, err + } + } + b, err := json.Marshal(j) + if err != nil { + return nil, err + } + return &sdcpb.GetIntentResponse{ + DatastoreName: req.GetIntent(), + Format: req.GetFormat(), + Intent: &sdcpb.GetIntentResponse_Blob{ + Blob: b, + }, + }, nil + + case sdcpb.Format_Intent_Format_XML: + doc, err := rsp.ToXML() + if err != nil { + return nil, err + } + xml, err := doc.WriteToBytes() + if err != nil { + return nil, err + } + + return &sdcpb.GetIntentResponse{ + DatastoreName: req.GetIntent(), + Format: req.GetFormat(), + Intent: &sdcpb.GetIntentResponse_Blob{ + Blob: xml, + }, + }, nil + case sdcpb.Format_Intent_Format_PROTO: + upds, err := rsp.ToProtoUpdates(ctx) + if err != nil { + return nil, err + } + + return &sdcpb.GetIntentResponse{ + DatastoreName: req.GetIntent(), + Format: req.GetFormat(), + Intent: &sdcpb.GetIntentResponse_Proto{ + Proto: &sdcpb.Intent{ + Intent: req.GetIntent(), + Update: upds, + }, + }, + }, nil + } + return nil, fmt.Errorf("unknown format") } diff --git a/pkg/server/schema.go b/pkg/server/schema.go index 67580c82..e2af82b0 100644 --- a/pkg/server/schema.go +++ b/pkg/server/schema.go @@ -49,10 +49,10 @@ func (s *Server) createLocalSchemaStore(ctx context.Context) { var store schemaStore.Store switch s.config.SchemaStore.Type { case schemaConfig.StoreTypeMemory: - store = schemaMemoryStore.New() + store = schemaMemoryStore.New(s.config.SchemaStore.UploadPath) case schemaConfig.StoreTypePersistent: var err error - store, err = schemaPersistentStore.New(ctx, s.config.SchemaStore.Path, s.config.SchemaStore.Cache) + store, err = schemaPersistentStore.New(ctx, s.config.SchemaStore.UploadPath, s.config.SchemaStore.Path, s.config.SchemaStore.Cache) if err != nil { log.Errorf("failed to create a persistent schema store: %v", err) os.Exit(1) diff --git a/pkg/server/server.go b/pkg/server/server.go index f884e13f..5fc07f3c 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -53,9 +53,6 @@ type Server struct { ctx context.Context cfn context.CancelFunc - md *sync.RWMutex - datastores map[string]*datastore.Datastore // datastore group with sbi - srv *grpc.Server sdcpb.UnimplementedDataServerServer sdcpb.UnimplementedSchemaServerServer @@ -63,7 +60,7 @@ type Server struct { router *mux.Router reg *prometheus.Registry - // remoteSchemaClient sdcpb.SchemaServerClient + datastores *DatastoreMap schemaClient schema.Client cacheClient cache.Client @@ -71,6 +68,71 @@ type Server struct { gnmiOpts []grpc.DialOption } +type DatastoreMap struct { + md *sync.RWMutex + datastores map[string]*datastore.Datastore // datastore group with sbi +} + +func NewDatastoreMap() *DatastoreMap { + return &DatastoreMap{ + md: &sync.RWMutex{}, + datastores: map[string]*datastore.Datastore{}, + } +} +func (d *DatastoreMap) StopAll() { + for _, ds := range d.datastores { + ds.Stop() + } +} + +func (d *DatastoreMap) AddDatastore(ds *datastore.Datastore) error { + d.md.Lock() + defer d.md.Unlock() + if existingDs, _ := d.getDataStore(ds.Name()); existingDs != nil { + return fmt.Errorf("datastore %s already exists", ds.Name()) + } + + d.datastores[ds.Name()] = ds + return nil +} + +func (d *DatastoreMap) DeleteDatastore(ctx context.Context, name string) error { + d.md.Lock() + defer d.md.Unlock() + ds, err := d.getDataStore(name) + if err != nil { + return err + } + ds.Delete(ctx) + delete(d.datastores, name) + return nil +} + +func (d *DatastoreMap) GetDataStore(name string) (*datastore.Datastore, error) { + d.md.RLock() + defer d.md.RUnlock() + return d.getDataStore(name) +} + +func (d *DatastoreMap) GetDatastoreAll() []*datastore.Datastore { + d.md.RLock() + defer d.md.RUnlock() + result := make([]*datastore.Datastore, 0, len(d.datastores)) + for _, x := range d.datastores { + result = append(result, x) + } + return result +} + +// getDataStore expects that the mutex d.md is already held +func (d *DatastoreMap) getDataStore(name string) (*datastore.Datastore, error) { + ds, exists := d.datastores[name] + if !exists { + return nil, fmt.Errorf("unknown datastore %s", name) + } + return ds, nil +} + func New(ctx context.Context, c *config.Config) (*Server, error) { ctx, cancel := context.WithCancel(ctx) var s = &Server{ @@ -78,8 +140,7 @@ func New(ctx context.Context, c *config.Config) (*Server, error) { ctx: ctx, cfn: cancel, - md: &sync.RWMutex{}, - datastores: make(map[string]*datastore.Datastore), + datastores: NewDatastoreMap(), router: mux.NewRouter(), reg: prometheus.NewRegistry(), @@ -159,16 +220,6 @@ func (s *Server) Serve(ctx context.Context) error { return nil } -func (s *Server) getDataStore(name string) (*datastore.Datastore, error) { - s.md.Lock() - defer s.md.Unlock() - ds, exists := s.datastores[name] - if !exists { - return nil, fmt.Errorf("unknown datastore %s", name) - } - return ds, nil -} - func (s *Server) ServeHTTP() { s.router.Handle("/metrics", promhttp.HandlerFor(s.reg, promhttp.HandlerOpts{})) s.reg.MustRegister(collectors.NewGoCollector()) @@ -187,9 +238,7 @@ func (s *Server) ServeHTTP() { func (s *Server) Stop() { s.srv.Stop() - for _, ds := range s.datastores { - ds.Stop() - } + s.datastores.StopAll() s.cfn() } @@ -228,11 +277,9 @@ func (s *Server) createInitialDatastores(ctx context.Context) { log.Debugf("creating datastore %s", dsCfg.Name) go func(dsCfg *config.DatastoreConfig) { defer wg.Done() - dsCfg.Validation = s.config.Validation - ds := datastore.New(ctx, dsCfg, s.schemaClient, s.cacheClient, s.gnmiOpts...) - s.md.Lock() - defer s.md.Unlock() - s.datastores[dsCfg.Name] = ds + // TODO: handle error + ds, _ := datastore.New(ctx, dsCfg, s.schemaClient, s.cacheClient, s.gnmiOpts...) + s.datastores.AddDatastore(ds) }(dsCfg) } wg.Wait() diff --git a/pkg/server/transaction.go b/pkg/server/transaction.go index 3ecef288..41a89307 100644 --- a/pkg/server/transaction.go +++ b/pkg/server/transaction.go @@ -32,7 +32,7 @@ func (s *Server) TransactionSet(ctx context.Context, req *sdcpb.TransactionSetRe } // retrieve the referenced datastore - ds, err := s.getDataStore(req.DatastoreName) + ds, err := s.datastores.getDataStore(req.DatastoreName) if err != nil { return nil, status.Error(codes.NotFound, err.Error()) } @@ -76,7 +76,7 @@ func (s *Server) TransactionConfirm(ctx context.Context, req *sdcpb.TransactionC return nil, status.Error(codes.InvalidArgument, "missing datastore name") } - ds, err := s.getDataStore(req.DatastoreName) + ds, err := s.datastores.GetDataStore(req.DatastoreName) if err != nil { return nil, status.Error(codes.NotFound, err.Error()) } @@ -93,7 +93,7 @@ func (s *Server) TransactionCancel(ctx context.Context, req *sdcpb.TransactionCa return nil, status.Error(codes.InvalidArgument, "missing datastore name") } - ds, err := s.getDataStore(req.DatastoreName) + ds, err := s.datastores.GetDataStore(req.DatastoreName) if err != nil { return nil, status.Error(codes.NotFound, err.Error()) } diff --git a/pkg/tree/cache_update_filter.go b/pkg/tree/cache_update_filter.go deleted file mode 100644 index 393f2da4..00000000 --- a/pkg/tree/cache_update_filter.go +++ /dev/null @@ -1,23 +0,0 @@ -package tree - -import "github.com/sdcio/data-server/pkg/cache" - -type CacheUpdateFilter func(u *cache.Update) bool - -func CacheUpdateFilterExcludeOwner(owner string) func(u *cache.Update) bool { - return func(u *cache.Update) bool { - return u.Owner() != owner - } -} - -// ApplyCacheUpdateFilters takes a bunch of CacheUpdateFilters applies them in an AND fashion -// and returns the result. -func ApplyCacheUpdateFilters(u *cache.Update, fs []CacheUpdateFilter) bool { - for _, f := range fs { - b := f(u) - if !b { - return false - } - } - return true -} diff --git a/pkg/tree/childMap.go b/pkg/tree/childMap.go new file mode 100644 index 00000000..841636da --- /dev/null +++ b/pkg/tree/childMap.go @@ -0,0 +1,102 @@ +package tree + +import ( + "iter" + "slices" + "sync" +) + +type childMap struct { + c map[string]Entry + mu sync.RWMutex +} + +func newChildMap() *childMap { + return &childMap{ + c: map[string]Entry{}, + } +} + +func (c *childMap) Items() iter.Seq2[string, Entry] { + return func(yield func(string, 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 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 := make([]string, 0, len(c.c)) + for name := range c.c { + childNames = append(childNames, name) + } + slices.Sort(childNames) + + result := make([]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]Entry { + c.mu.RLock() + defer c.mu.RUnlock() + + result := map[string]Entry{} + 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/choice_case_resolver.go b/pkg/tree/choice_case_resolver.go index 7f990cf8..98054327 100644 --- a/pkg/tree/choice_case_resolver.go +++ b/pkg/tree/choice_case_resolver.go @@ -2,20 +2,28 @@ package tree import ( "math" - "slices" ) -type choiceCasesResolvers map[string]*choiceCasesResolver +type choiceResolvers map[string]*choiceResolver // AddChoice adds / registers a new Choice to the choiceCasesResolver -func (c choiceCasesResolvers) AddChoice(name string) *choiceCasesResolver { - r := newChoiceCasesResolver() +func (c choiceResolvers) AddChoice(name string) *choiceResolver { + r := newChoiceResolver() c[name] = r return r } -func (c choiceCasesResolvers) deepCopy() choiceCasesResolvers { - result := choiceCasesResolvers{} +// GetDeletes returns the names of the elements that need to be deleted. +func (c choiceResolvers) GetDeletes() []string { + result := []string{} + for _, cases := range c { + result = append(result, cases.GetDeletes()...) + } + return result +} + +func (c choiceResolvers) deepCopy() choiceResolvers { + result := choiceResolvers{} for k, v := range c { result[k] = v.deepCopy() @@ -27,7 +35,7 @@ func (c choiceCasesResolvers) deepCopy() choiceCasesResolvers { // GetSkipElements returns the list of all choices elements that are not highes priority. // The resulting slice is used to skip these elements. -func (c choiceCasesResolvers) GetSkipElements() []string { +func (c choiceResolvers) GetSkipElements() []string { result := []string{} for _, x := range c { result = append(result, x.GetSkipElements()...) @@ -35,72 +43,129 @@ func (c choiceCasesResolvers) GetSkipElements() []string { return result } -func (c choiceCasesResolvers) shouldDelete() bool { - if len(c) == 0 { - return false - } - return !c.remainsToExist() +// choiceResolver is a helper used to efficiently store the priority values of certain branches and their association to the cases is a choice. +// All with the goal of composing a list of elements that do not belong to the prioritised case, for exclusion on tree traversal time. +type choiceResolver struct { + cases map[string]*choicesCase + elementToCaseMapping map[string]*choicesCase + elements map[string]*caseElement } -func (c choiceCasesResolvers) remainsToExist() bool { - for _, x := range c { - if x.getBestCaseName() != "" { - return true +func (c *choiceResolver) deepCopy() *choiceResolver { + result := &choiceResolver{ + cases: map[string]*choicesCase{}, + elementToCaseMapping: map[string]*choicesCase{}, + elements: map[string]*caseElement{}, + } + + for k, v := range c.cases { + result.cases[k] = v.deepCopy() + } + + for _, cas := range result.cases { + for _, elem := range cas.elements { + result.elementToCaseMapping[elem.name] = cas + result.elements[elem.name] = elem } } - return false -} - -// GetChoiceElements returns a list of elements that belong to the same choice -// as the given element. This is used to query the cache for all elements of all cases for the -// choice. -func (c choiceCasesResolvers) GetChoiceElementNeighbors(elemName string) []string { - var result []string - // iterate through the different choices that might exist - for _, choice := range c { - // get all the elements references in a certain contains - sl := choice.GetElementNames() - // check for the given element, if it belongs to the actual choice - var idx int - if idx = slices.Index(sl, elemName); idx == -1 { - // if not continue with next choice + + return result +} + +// newChoiceResolver returns a ready to use choiceCasesResolver. +func newChoiceResolver() *choiceResolver { + return &choiceResolver{ + cases: map[string]*choicesCase{}, // case name -> case data + elementToCaseMapping: map[string]*choicesCase{}, // element name -> case name + elements: map[string]*caseElement{}, //element name -> element data + } +} + +// AddCase adds / registers a case with its name and the element names that belong to the case +func (c *choiceResolver) AddCase(name string, elements []string) *choicesCase { + cse := newChoicesCase(name) + c.cases[name] = cse + + for _, e := range elements { + cce := newCaseElement(e) + c.elementToCaseMapping[e] = cse + cse.elements[e] = cce + c.elements[e] = cce + } + return cse +} + +// SetValue Sets the priority value that the given elements with its entire branch has calculated +func (c *choiceResolver) SetValue(elemName string, highestWODel int32, highestDelete int32, highestWONew int32, isDeleted bool) { + elem := c.elements[elemName] + elem.highestWODeleted = highestWODel + elem.highestWONew = highestWONew +} + +// GetSkipElements returns the names of all the elements that belong to +// cases that have not the best priority +func (c *choiceResolver) GetSkipElements() []string { + result := make([]string, 0, len(c.elementToCaseMapping)) + + bestCase := c.GetBestCaseNow() + for elem, cas := range c.elementToCaseMapping { + if cas == bestCase { continue } - // if it belongs to it, return the list of all elements belonging to the - // choice without the provided element itself. - return append(sl[:idx], sl[idx+1:]...) - + result = append(result, elem) } return result } -// choiceCasesResolver is a helper used to efficiently store the priority values of certain branches and their association to the cases is a choice. -// All with the goal of composing a list of elements that do not belong to the prioritised case, for exclusion on tree traversal time. -type choiceCasesResolver struct { - cases map[string]*choicesCase - elementToCaseMapping map[string]string -} +func (c *choiceResolver) GetBestCaseNow() *choicesCase { + // best case now + var highestCaseWODeleted *choicesCase + highesPrecedenceWODeletedValue := int32(math.MaxInt32) -func (c *choiceCasesResolver) deepCopy() *choiceCasesResolver { - result := &choiceCasesResolver{ - cases: map[string]*choicesCase{}, - elementToCaseMapping: map[string]string{}, + for _, cas := range c.cases { + actualCaseWODeletedValue := cas.getHighestPrecedenceWODeleted() + if actualCaseWODeletedValue < highesPrecedenceWODeletedValue { + highesPrecedenceWODeletedValue = actualCaseWODeletedValue + highestCaseWODeleted = cas + } } + return highestCaseWODeleted +} - for k, v := range c.cases { - result.cases[k] = v.deepCopy() +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 +} - for k, v := range c.elementToCaseMapping { - result.elementToCaseMapping[k] = v +func (c *choiceResolver) GetDeletes() []string { + result := []string{} + // best case now + highestCaseNow := c.GetBestCaseNow() + // best case before + highestCaseBefore := c.GetBestCaseBefore() + + // if best case before (highestCaseWONew) != best case now (highestCaseWODeleted) + if highestCaseBefore != nil && highestCaseBefore != highestCaseNow { + // remove best case before + result = append(result, highestCaseBefore.GetElementNames()...) } return result } // GetElementNames retrieve all the Element names involved in the Choice -func (c *choiceCasesResolver) GetElementNames() []string { - result := make([]string, 0, len(c.cases)) +func (c *choiceResolver) GetElementNames() []string { + result := make([]string, 0, len(c.elementToCaseMapping)) for elemName := range c.elementToCaseMapping { result = append(result, elemName) } @@ -110,127 +175,70 @@ func (c *choiceCasesResolver) GetElementNames() []string { // choicesCase is the representation of a case in the choiceCasesResolver. type choicesCase struct { name string - elements map[string]*choicesCaseElement + elements map[string]*caseElement } -func (c *choicesCase) deepCopy() *choicesCase { - result := &choicesCase{ - name: c.name, +func newChoicesCase(name string) *choicesCase { + return &choicesCase{ + name: name, + elements: map[string]*caseElement{}, } - for k, v := range c.elements { - result.elements[k] = v.deepCopy() +} + +func (c *choicesCase) GetElementNames() []string { + result := make([]string, 0, len(c.elements)) + for name := range c.elements { + result = append(result, name) } return result } -func (c *choicesCase) GetLowestPriorityValue() int32 { +func (c *choicesCase) getHighestPrecedenceWONew() int32 { result := int32(math.MaxInt32) - for _, cas := range c.elements { - if cas.value < result { - result = cas.value + for _, elem := range c.elements { + if elem.highestWONew < result { + result = elem.highestWONew } } return result } -func (c *choicesCase) GetLowestPriorityValueOld() int32 { +func (c *choicesCase) getHighestPrecedenceWODeleted() int32 { result := int32(math.MaxInt32) - for _, cas := range c.elements { - if !cas.new && cas.value < result { - result = cas.value + for _, elem := range c.elements { + if elem.highestWODeleted < result { + result = elem.highestWODeleted } } return result } -type choicesCaseElement struct { - name string - value int32 - new bool -} - -func (c *choicesCaseElement) deepCopy() *choicesCaseElement { - return &choicesCaseElement{ - name: c.name, - value: c.value, - new: c.new, - } -} - -// newChoiceCasesResolver returns a ready to use choiceCasesResolver. -func newChoiceCasesResolver() *choiceCasesResolver { - return &choiceCasesResolver{ - cases: map[string]*choicesCase{}, // case name -> case data - elementToCaseMapping: map[string]string{}, // element name -> case name - } -} - -// AddCase adds / registers a case with its name and the element names that belong to the case -func (c *choiceCasesResolver) AddCase(name string, elements []string) *choicesCase { - c.cases[name] = &choicesCase{ - name: name, - elements: map[string]*choicesCaseElement{}, +func (c *choicesCase) deepCopy() *choicesCase { + result := &choicesCase{ + name: c.name, } - for _, e := range elements { - c.elementToCaseMapping[e] = name - c.cases[name].elements[e] = &choicesCaseElement{ - name: e, - value: int32(math.MaxInt32), - } + for k, v := range c.elements { + result.elements[k] = v.deepCopy() } - return c.cases[name] + return result } -// SetValue Sets the priority value that the given elements with its entire branch has calculated -func (c *choiceCasesResolver) SetValue(elemName string, v int32, new bool) { - // math.MaxInt32 indicates that the branch is not populated, - // so we skip adding it - if v == math.MaxInt32 { - return - } - actualCase := c.elementToCaseMapping[elemName] - c.cases[actualCase].elements[elemName].value = v - c.cases[actualCase].elements[elemName].new = new -} - -// GetBestCaseName returns the name of the case, that has the highes priority -func (c *choiceCasesResolver) getBestCaseName() string { - var bestCaseName string - bestCasePrio := int32(math.MaxInt32) - for caseName, cas := range c.cases { - if cas.GetLowestPriorityValue() <= bestCasePrio { - bestCaseName = caseName - bestCasePrio = cas.GetLowestPriorityValue() - } - } - return bestCaseName +type caseElement struct { + name string + highestWODeleted int32 + highestWONew int32 } -func (c *choiceCasesResolver) getOldBestCaseName() string { - var bestCaseName string - bestCasePrio := int32(math.MaxInt32) - for caseName, cas := range c.cases { - lowestPrioOld := cas.GetLowestPriorityValueOld() - if lowestPrioOld < bestCasePrio { - bestCaseName = caseName - bestCasePrio = lowestPrioOld - } +func newCaseElement(name string) *caseElement { + return &caseElement{ + name: name, } - return bestCaseName } -// GetSkipElements returns the names of all the elements that belong to -// cases that have not the best priority -func (c *choiceCasesResolver) GetSkipElements() []string { - result := make([]string, 0, len(c.elementToCaseMapping)) - - bestCase := c.getBestCaseName() - - for elem, cas := range c.elementToCaseMapping { - if cas == bestCase { - continue - } - result = append(result, elem) +func (c *caseElement) deepCopy() *caseElement { + return &caseElement{ + name: c.name, + highestWODeleted: c.highestWODeleted, + highestWONew: c.highestWONew, } - return result } diff --git a/pkg/utils/default_value.go b/pkg/tree/default_value.go similarity index 78% rename from pkg/utils/default_value.go rename to pkg/tree/default_value.go index eacacc4c..4fdb6330 100644 --- a/pkg/utils/default_value.go +++ b/pkg/tree/default_value.go @@ -1,12 +1,12 @@ -package utils +package tree import ( "fmt" "strings" - "github.com/sdcio/data-server/pkg/cache" + "github.com/sdcio/data-server/pkg/tree/types" + "github.com/sdcio/data-server/pkg/utils" sdcpb "github.com/sdcio/sdc-protos/sdcpb" - "google.golang.org/protobuf/proto" ) func DefaultValueExists(schema *sdcpb.SchemaElem) bool { @@ -19,7 +19,7 @@ func DefaultValueExists(schema *sdcpb.SchemaElem) bool { return false } -func DefaultValueRetrieve(schema *sdcpb.SchemaElem, path []string, prio int32, intent string) (*cache.Update, error) { +func DefaultValueRetrieve(schema *sdcpb.SchemaElem, path []string, prio int32, intent string) (*types.Update, error) { var tv *sdcpb.TypedValue var err error switch schem := schema.GetSchema().(type) { @@ -28,7 +28,7 @@ func DefaultValueRetrieve(schema *sdcpb.SchemaElem, path []string, prio int32, i if defaultVal == "" { return nil, fmt.Errorf("no defaults defined for schema path: %s", strings.Join(path, "->")) } - tv, err = Convert(defaultVal, schem.Field.GetType()) + tv, err = utils.Convert(defaultVal, schem.Field.GetType()) if err != nil { return nil, err } @@ -39,7 +39,7 @@ func DefaultValueRetrieve(schema *sdcpb.SchemaElem, path []string, prio int32, i } tvlist := make([]*sdcpb.TypedValue, 0, len(listDefaults)) for _, dv := range schem.Leaflist.GetDefaults() { - tvelem, err := Convert(dv, schem.Leaflist.GetType()) + tvelem, err := utils.Convert(dv, schem.Leaflist.GetType()) if err != nil { return nil, fmt.Errorf("error converting default to typed value for %s, type: %s ; value: %s; err: %v", strings.Join(path, " -> "), schem.Leaflist.GetType().GetTypeName(), dv, err) } @@ -56,11 +56,5 @@ func DefaultValueRetrieve(schema *sdcpb.SchemaElem, path []string, prio int32, i return nil, fmt.Errorf("no defaults defined for schema path: %s", strings.Join(path, "->")) } - // convert value to []byte for cache insertion - val, err := proto.Marshal(tv) - if err != nil { - return nil, err - } - - return cache.NewUpdate(path, val, prio, intent, 0), nil + return types.NewUpdate(path, tv, prio, intent, 0), nil } diff --git a/pkg/tree/entry.go b/pkg/tree/entry.go index 42bf6752..fb7f994f 100644 --- a/pkg/tree/entry.go +++ b/pkg/tree/entry.go @@ -5,10 +5,10 @@ import ( "math" "github.com/beevik/etree" - "github.com/sdcio/data-server/pkg/cache" "github.com/sdcio/data-server/pkg/config" "github.com/sdcio/data-server/pkg/tree/importer" - "github.com/sdcio/data-server/pkg/types" + "github.com/sdcio/data-server/pkg/tree/tree_persist" + "github.com/sdcio/data-server/pkg/tree/types" sdcpb "github.com/sdcio/sdc-protos/sdcpb" ) @@ -46,28 +46,34 @@ func newEntry(ctx context.Context, parent Entry, pathElemName string, tc *TreeCo // Entry is the primary Element of the Tree. type Entry interface { // Path returns the Path as PathSlice - Path() PathSlice + Path() types.PathSlice // PathName returns the last Path element, the name of the Entry PathName() string + // GetLevel returns the depth of the Entry in the tree + GetLevel() int // addChild Add a child entry addChild(context.Context, Entry) error - // AddCacheUpdateRecursive Add the given cache.Update to the tree - AddCacheUpdateRecursive(ctx context.Context, u *cache.Update, flags *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 types.PathSlice) (Entry, error) + // AddUpdateRecursive Add the given cache.Update to the tree + AddUpdateRecursive(ctx context.Context, 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) LeafVariantSlice + GetHighestPrecedence(result LeafVariantSlice, onlyNewOrUpdated bool, includeDefaults 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) []*LeafEntry + 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) + 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 []DeleteEntry, aggregatePaths bool) ([]DeleteEntry, error) + GetDeletes(entries []types.DeleteEntry, aggregatePaths bool) ([]types.DeleteEntry, error) // Walk takes the EntryVisitor and applies it to every Entry in the tree Walk(f EntryVisitor) error // Validate kicks off validation @@ -76,20 +82,20 @@ type Entry interface { validateMandatory(ctx context.Context, resultChan chan<- *types.ValidationResultEntry) // 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, attribute 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() 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 IsRoot() bool // FinishInsertionPhase indicates, that the insertion of Entries into the tree is over // Hence calculations for e.g. choice/case can be performed. - FinishInsertionPhase(ctx context.Context) + FinishInsertionPhase(ctx context.Context) error // GetParent returns the parent entry GetParent() Entry // Navigate navigates the tree according to the given path and returns the referenced entry or nil if it does not exist. - Navigate(ctx context.Context, path []string, isRootPath bool) (Entry, error) + Navigate(ctx context.Context, path []string, isRootPath bool, dotdotSkipKeys bool) (Entry, error) NavigateSdcpbPath(ctx context.Context, path []*sdcpb.PathElem, isRootPath bool) (Entry, error) // NavigateLeafRef follows the leafref and returns the referenced entry NavigateLeafRef(ctx context.Context) ([]Entry, error) @@ -138,7 +144,14 @@ type Entry interface { 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) // ImportConfig allows importing config data received from e.g. the device in different formats (json, xml) to be imported into the tree. - ImportConfig(ctx context.Context, t importer.ImportConfigAdapter, intentName string, intentPrio int32) error + ImportConfig(ctx context.Context, importer importer.ImportConfigAdapter, intentName string, intentPrio int32, flags *types.UpdateInsertFlags) error + TreeExport(owner string) ([]*tree_persist.TreeElement, error) + DeleteSubtree(relativePath types.PathSlice, owner string) (remainsToExist bool, err error) + GetDeviations(ch chan<- *types.DeviationEntry, activeCase bool) + // 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 string) ([]Entry, error) } type EntryVisitor func(s *sharedEntryAttributes) error diff --git a/pkg/tree/entry_test.go b/pkg/tree/entry_test.go index df105e0d..ce3ba82c 100644 --- a/pkg/tree/entry_test.go +++ b/pkg/tree/entry_test.go @@ -8,37 +8,32 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/sdcio/data-server/pkg/cache" "github.com/sdcio/data-server/pkg/config" + "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" - "google.golang.org/protobuf/proto" ) var ( - flagsNew *UpdateInsertFlags - flagsExisting *UpdateInsertFlags - validationConfig *config.Validation + flagsNew *types.UpdateInsertFlags + flagsExisting *types.UpdateInsertFlags + validationConfig = &config.Validation{DisableConcurrency: true} ) func init() { - flagsNew = NewUpdateInsertFlags() + flagsNew = types.NewUpdateInsertFlags() flagsNew.SetNewFlag() - flagsExisting = NewUpdateInsertFlags() - validationConfig = &config.Validation{DisableConcurrency: true} + flagsExisting = types.NewUpdateInsertFlags() } func Test_Entry(t *testing.T) { - desc, err := proto.Marshal(&sdcpb.TypedValue{Value: &sdcpb.TypedValue_StringVal{StringVal: "MyDescription"}}) - if err != nil { - t.Error(err) - } + desc := testhelper.GetStringTvProto("MyDescription") - u1 := cache.NewUpdate([]string{"interface", "ethernet-0/0", "subinterface", "9", "description"}, desc, int32(100), "me", int64(9999999)) - u2 := cache.NewUpdate([]string{"interface", "ethernet-0/0", "subinterface", "10", "description"}, desc, int32(99), "me", int64(444)) - u3 := cache.NewUpdate([]string{"interface", "ethernet-0/0", "subinterface", "10", "description"}, desc, int32(98), "me", int64(88)) + u1 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "9", "description"}, desc, int32(100), "me", int64(9999999)) + u2 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "10", "description"}, desc, int32(99), "me", int64(444)) + u3 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "10", "description"}, desc, int32(98), "me", int64(88)) mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() @@ -50,21 +45,24 @@ func Test_Entry(t *testing.T) { ctx := context.TODO() - tc := NewTreeContext(NewTreeCacheClient("dev1", nil), scb, "foo") + tc := NewTreeContext(scb, "foo") root, err := NewTreeRoot(ctx, tc) if err != nil { t.Error(err) } - for _, u := range []*cache.Update{u1, u2, u3} { - _, err = root.AddCacheUpdateRecursive(ctx, u, flagsNew) + for _, u := range []*types.Update{u1, u2, u3} { + _, err = root.AddUpdateRecursive(ctx, u, flagsNew) if err != nil { t.Error(err) } } - root.FinishInsertionPhase(ctx) + err = root.FinishInsertionPhase(ctx) + if err != nil { + t.Error(err) + } r := []string{} r = root.StringIndent(r) @@ -72,9 +70,9 @@ func Test_Entry(t *testing.T) { } func Test_Entry_One(t *testing.T) { - desc1 := testhelper.GetStringTvProto(t, "DescriptionOne") - desc2 := testhelper.GetStringTvProto(t, "DescriptionTwo") - desc3 := testhelper.GetStringTvProto(t, "DescriptionThree") + desc1 := testhelper.GetStringTvProto("DescriptionOne") + desc2 := testhelper.GetStringTvProto("DescriptionTwo") + desc3 := testhelper.GetStringTvProto("DescriptionThree") prio100 := int32(100) prio50 := int32(50) @@ -84,9 +82,14 @@ func Test_Entry_One(t *testing.T) { ts1 := int64(9999999) - u1 := cache.NewUpdate([]string{"interface", "ethernet-0/0", "subinterface", "9", "description"}, desc1, prio100, owner1, ts1) - u2 := cache.NewUpdate([]string{"interface", "ethernet-0/0", "subinterface", "10", "description"}, desc2, prio100, owner1, ts1) - u3 := cache.NewUpdate([]string{"interface", "ethernet-0/0", "subinterface", "10", "description"}, desc3, prio50, owner2, ts1) + u0o1 := types.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto("ethernet-1/1"), prio100, owner1, ts1) + u0o2 := types.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto("ethernet-1/1"), prio50, owner2, ts1) + u1 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "9", "description"}, desc1, prio100, owner1, ts1) + u1_1 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "9", "index"}, testhelper.GetUIntTvProto(9), prio100, owner1, ts1) + u2 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "10", "description"}, desc2, prio100, owner1, ts1) + u2_1 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "10", "index"}, testhelper.GetUIntTvProto(10), prio100, owner1, ts1) + u3 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "10", "description"}, desc3, prio50, owner2, ts1) + u3_1 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "10", "index"}, testhelper.GetUIntTvProto(10), prio50, owner2, ts1) mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() @@ -98,7 +101,7 @@ func Test_Entry_One(t *testing.T) { ctx := context.TODO() - tc := NewTreeContext(NewTreeCacheClient("dev1", nil), scb, "foo") + tc := NewTreeContext(scb, "foo") root, err := NewTreeRoot(ctx, tc) if err != nil { @@ -106,34 +109,37 @@ func Test_Entry_One(t *testing.T) { } // start test - for _, u := range []*cache.Update{u1, u2, u3} { - _, err := root.AddCacheUpdateRecursive(ctx, u, flagsNew) + for _, u := range []*types.Update{u0o1, u1, u1_1, u2, u2_1, u3, u3_1} { + _, err := root.AddUpdateRecursive(ctx, u, flagsNew) if err != nil { t.Error(err) } } - root.FinishInsertionPhase(ctx) + err = root.FinishInsertionPhase(ctx) + if err != nil { + t.Error(err) + } // log the tree t.Log(root.String()) - t.Run("Test 1 - expect 2 entry for owner1", func(t *testing.T) { + t.Run("Test 1 - expected entries for owner1", func(t *testing.T) { o1Le := []*LeafEntry{} o1Le = root.GetByOwner(owner1, o1Le) - o1 := LeafEntriesToCacheUpdates(o1Le) + o1 := LeafEntriesToUpdates(o1Le) // diff the result with the expected - if diff := testhelper.DiffCacheUpdates([]*cache.Update{u2, u1}, o1); diff != "" { + if diff := testhelper.DiffUpdates([]*types.Update{u0o1, u2, u2_1, u1, u1_1}, o1); diff != "" { t.Errorf("root.GetByOwner(owner1) mismatch (-want +got):\n%s", diff) } }) - t.Run("Test 2 - expect 1 entry for owner2", func(t *testing.T) { + t.Run("Test 2 - expected entries for owner2", func(t *testing.T) { o2Le := []*LeafEntry{} o2Le = root.GetByOwner(owner2, o2Le) - o2 := LeafEntriesToCacheUpdates(o2Le) + o2 := LeafEntriesToUpdates(o2Le) // diff the result with the expected - if diff := testhelper.DiffCacheUpdates([]*cache.Update{u3}, o2); diff != "" { + if diff := testhelper.DiffUpdates([]*types.Update{u0o2, u3_1, u3}, o2); diff != "" { t.Errorf("root.GetByOwner(owner2) mismatch (-want +got):\n%s", diff) } }) @@ -142,7 +148,7 @@ func Test_Entry_One(t *testing.T) { highprec := root.GetHighestPrecedence(true) // diff the result with the expected - if diff := testhelper.DiffCacheUpdates([]*cache.Update{u1, u3}, highprec.ToCacheUpdateSlice()); diff != "" { + if diff := testhelper.DiffUpdates([]*types.Update{u0o2, u1, u1_1, u3, u3_1}, highprec.ToUpdateSlice()); diff != "" { t.Errorf("root.GetHighesPrio() mismatch (-want +got):\n%s", diff) } }) @@ -151,11 +157,13 @@ func Test_Entry_One(t *testing.T) { // Test_Entry_Two adding a new Update with same owner and priority but updating the value func Test_Entry_Two(t *testing.T) { - desc3 := testhelper.GetStringTvProto(t, "DescriptionThree") + desc3 := testhelper.GetStringTvProto("DescriptionThree") prio50 := int32(50) owner1 := "OwnerOne" ts1 := int64(9999999) - u1 := cache.NewUpdate([]string{"interface", "ethernet-0/0", "subinterface", "10", "description"}, desc3, prio50, owner1, ts1) + u0 := types.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto("ethernet-1/1"), prio50, owner1, ts1) + u1 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "10", "description"}, desc3, prio50, owner1, ts1) + u1_1 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "10", "index"}, testhelper.GetUIntTvProto(10), prio50, owner1, ts1) mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() @@ -167,7 +175,7 @@ func Test_Entry_Two(t *testing.T) { ctx := context.TODO() - tc := NewTreeContext(NewTreeCacheClient("dev1", nil), scb, "foo") + tc := NewTreeContext(scb, "foo") root, err := NewTreeRoot(ctx, tc) if err != nil { @@ -175,53 +183,61 @@ func Test_Entry_Two(t *testing.T) { } // start test add "existing" data - for _, u := range []*cache.Update{u1} { - _, err := root.AddCacheUpdateRecursive(ctx, u, flagsExisting) + for _, u := range []*types.Update{u1} { + _, err := root.AddUpdateRecursive(ctx, u, flagsExisting) if err != nil { t.Error(err) } } // add incomming set intent reques data - overwriteDesc := testhelper.GetStringTvProto(t, "Owerwrite Description") + overwriteDesc := testhelper.GetStringTvProto("Owerwrite Description") // adding a new Update with same owner and priority with different value - n1 := cache.NewUpdate([]string{"interface", "ethernet-0/0", "subinterface", "10", "description"}, overwriteDesc, prio50, owner1, ts1) + n1 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "10", "description"}, overwriteDesc, prio50, owner1, ts1) - for _, u := range []*cache.Update{n1} { - _, err = root.AddCacheUpdateRecursive(ctx, u, flagsNew) + for _, u := range []*types.Update{n1} { + _, err = root.AddUpdateRecursive(ctx, u, flagsNew) if err != nil { t.Error(err) } } - root.FinishInsertionPhase(ctx) + err = root.FinishInsertionPhase(ctx) + if err != nil { + t.Error(err) + } // log the tree t.Log(root.String()) highprec := root.GetHighestPrecedence(true) // diff the result with the expected - if diff := testhelper.DiffCacheUpdates([]*cache.Update{n1}, highprec.ToCacheUpdateSlice()); diff != "" { + if diff := testhelper.DiffUpdates([]*types.Update{n1, u0, u1_1}, highprec.ToUpdateSlice()); diff != "" { t.Errorf("root.GetHighesPrio() mismatch (-want +got):\n%s", diff) } } // Test_Entry_Three Checks that an Intent update is processed properly func Test_Entry_Three(t *testing.T) { - desc3 := testhelper.GetStringTvProto(t, "DescriptionThree") + desc3 := testhelper.GetStringTvProto("DescriptionThree") prio50 := int32(50) owner1 := "OwnerOne" ts1 := int64(9999999) - u1 := cache.NewUpdate([]string{"interface", "ethernet-0/0", "subinterface", "10", "description"}, desc3, prio50, owner1, ts1) - u2 := cache.NewUpdate([]string{"interface", "ethernet-0/0", "subinterface", "11", "description"}, desc3, prio50, owner1, ts1) - u3 := cache.NewUpdate([]string{"interface", "ethernet-0/0", "subinterface", "12", "description"}, desc3, prio50, owner1, ts1) - u4 := cache.NewUpdate([]string{"interface", "ethernet-0/0", "subinterface", "13", "description"}, desc3, prio50, owner1, ts1) - - u1r := cache.NewUpdate([]string{"interface", "ethernet-0/0", "subinterface", "10", "description"}, desc3, RunningValuesPrio, RunningIntentName, ts1) - u2r := cache.NewUpdate([]string{"interface", "ethernet-0/0", "subinterface", "11", "description"}, desc3, RunningValuesPrio, RunningIntentName, ts1) - u3r := cache.NewUpdate([]string{"interface", "ethernet-0/0", "subinterface", "12", "description"}, desc3, RunningValuesPrio, RunningIntentName, ts1) - u4r := cache.NewUpdate([]string{"interface", "ethernet-0/0", "subinterface", "13", "description"}, desc3, RunningValuesPrio, RunningIntentName, ts1) + u0 := types.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto("ethernet-1/1"), prio50, owner1, ts1) + u1 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "10", "description"}, desc3, prio50, owner1, ts1) + u1_1 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "10", "index"}, testhelper.GetUIntTvProto(10), prio50, owner1, ts1) + u2 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "11", "description"}, desc3, prio50, owner1, ts1) + u2_1 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "11", "index"}, testhelper.GetUIntTvProto(11), prio50, owner1, ts1) + u3 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "12", "description"}, desc3, prio50, owner1, ts1) + u3_1 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "12", "index"}, testhelper.GetUIntTvProto(12), prio50, owner1, ts1) + u4 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "13", "description"}, desc3, prio50, owner1, ts1) + u4_1 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "13", "index"}, testhelper.GetUIntTvProto(13), prio50, owner1, ts1) + + u1r := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "10", "description"}, desc3, RunningValuesPrio, RunningIntentName, ts1) + u2r := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "11", "description"}, desc3, RunningValuesPrio, RunningIntentName, ts1) + u3r := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "12", "description"}, desc3, RunningValuesPrio, RunningIntentName, ts1) + u4r := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "13", "description"}, desc3, RunningValuesPrio, RunningIntentName, ts1) mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() @@ -233,7 +249,7 @@ func Test_Entry_Three(t *testing.T) { ctx := context.TODO() - tc := NewTreeContext(NewTreeCacheClient("dev1", nil), scb, "foo") + tc := NewTreeContext(scb, "foo") root, err := NewTreeRoot(ctx, tc) if err != nil { @@ -241,22 +257,25 @@ func Test_Entry_Three(t *testing.T) { } // start test add "existing" data - for _, u := range []*cache.Update{u1, u2, u3, u4} { - _, err := root.AddCacheUpdateRecursive(ctx, u, flagsExisting) + for _, u := range []*types.Update{u1, u2, u3, u4} { + _, err := root.AddUpdateRecursive(ctx, u, flagsExisting) if err != nil { t.Error(err) } } // start test add "existing" data as running - for _, u := range []*cache.Update{u1r, u2r, u3r, u4r} { - _, err := root.AddCacheUpdateRecursive(ctx, u, flagsExisting) + for _, u := range []*types.Update{u1r, u2r, u3r, u4r} { + _, err := root.AddUpdateRecursive(ctx, u, flagsExisting) if err != nil { t.Error(err) } } - root.FinishInsertionPhase(ctx) + err = root.FinishInsertionPhase(ctx) + if err != nil { + t.Error(err) + } t.Run("Check the data is present", func(t *testing.T) { @@ -266,7 +285,7 @@ func Test_Entry_Three(t *testing.T) { highpri := root.GetHighestPrecedence(false) // diff the result with the expected - if diff := testhelper.DiffCacheUpdates([]*cache.Update{u1, u2, u3, u4}, highpri.ToCacheUpdateSlice()); diff != "" { + if diff := testhelper.DiffUpdates([]*types.Update{u0, u1, u1_1, u2, u2_1, u3, u3_1, u4, u4_1}, highpri.ToUpdateSlice()); diff != "" { t.Errorf("root.GetHighesPrio() mismatch (-want +got):\n%s", diff) } }) @@ -279,30 +298,33 @@ func Test_Entry_Three(t *testing.T) { highpri := root.GetHighestPrecedence(true) // diff the result with the expected - if diff := testhelper.DiffCacheUpdates([]*cache.Update{}, highpri.ToCacheUpdateSlice()); diff != "" { + if diff := testhelper.DiffUpdates([]*types.Update{}, highpri.ToUpdateSlice()); diff != "" { t.Errorf("root.GetHighesPrio() mismatch (-want +got):\n%s", diff) } }) // indicate that the intent is receiving an update // therefor invalidate all the present entries of the owner / intent - root.markOwnerDelete(owner1, false) + root.MarkOwnerDelete(owner1, false) // add incomming set intent reques data - overwriteDesc := testhelper.GetStringTvProto(t, "Owerwrite Description") + overwriteDesc := testhelper.GetStringTvProto("Owerwrite Description") // adding a new Update with same owner and priority with different value - n1 := cache.NewUpdate([]string{"interface", "ethernet-0/0", "subinterface", "10", "description"}, overwriteDesc, prio50, owner1, ts1) - n2 := cache.NewUpdate([]string{"interface", "ethernet-0/0", "subinterface", "11", "description"}, overwriteDesc, prio50, owner1, ts1) + n1 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "10", "description"}, overwriteDesc, prio50, owner1, ts1) + n2 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "11", "description"}, overwriteDesc, prio50, owner1, ts1) - for _, u := range []*cache.Update{n1, n2} { - _, err := root.AddCacheUpdateRecursive(ctx, u, flagsNew) + for _, u := range []*types.Update{n1, n2} { + _, err := root.AddUpdateRecursive(ctx, u, flagsNew) if err != nil { t.Error(err) } } - root.FinishInsertionPhase(ctx) + err = root.FinishInsertionPhase(ctx) + if err != nil { + t.Error(err) + } // log the tree t.Log(root.String()) @@ -314,10 +336,10 @@ func Test_Entry_Three(t *testing.T) { highPriLe := root.getByOwnerFiltered(owner1, FilterNonDeleted) - highPri := LeafEntriesToCacheUpdates(highPriLe) + highPri := LeafEntriesToUpdates(highPriLe) // diff the result with the expected - if diff := testhelper.DiffCacheUpdates([]*cache.Update{n1, n2}, highPri); diff != "" { + if diff := testhelper.DiffUpdates([]*types.Update{n1, n2}, highPri); diff != "" { t.Errorf("root.GetByOwner() mismatch (-want +got):\n%s", diff) } }) @@ -325,7 +347,7 @@ func Test_Entry_Three(t *testing.T) { t.Run("Check the old entries are gone", func(t *testing.T) { highpri := root.GetHighestPrecedence(true) // diff the result with the expected - if diff := testhelper.DiffCacheUpdates([]*cache.Update{n1, n2}, highpri.ToCacheUpdateSlice()); diff != "" { + if diff := testhelper.DiffUpdates([]*types.Update{n1, n2}, highpri.ToUpdateSlice()); diff != "" { t.Errorf("root.GetHighesPrio() mismatch (-want +got):\n%s", diff) } }) @@ -334,20 +356,28 @@ func Test_Entry_Three(t *testing.T) { // Test_Entry_Four Checks that an Intent update is processed properly with an intent that is shadowed initially. func Test_Entry_Four(t *testing.T) { - desc3 := testhelper.GetStringTvProto(t, "DescriptionThree") + desc3 := testhelper.GetStringTvProto("DescriptionThree") prio50 := int32(50) prio55 := int32(55) owner1 := "OwnerOne" owner2 := "OwnerTwo" ts1 := int64(9999999) - u1o1 := cache.NewUpdate([]string{"interface", "ethernet-0/0", "subinterface", "10", "description"}, desc3, prio50, owner1, ts1) - u2o1 := cache.NewUpdate([]string{"interface", "ethernet-0/0", "subinterface", "11", "description"}, desc3, prio50, owner1, ts1) - u3 := cache.NewUpdate([]string{"interface", "ethernet-0/0", "subinterface", "12", "description"}, desc3, prio50, owner1, ts1) - u4 := cache.NewUpdate([]string{"interface", "ethernet-0/0", "subinterface", "13", "description"}, desc3, prio50, owner1, ts1) - - u1o2 := cache.NewUpdate([]string{"interface", "ethernet-0/0", "subinterface", "10", "description"}, desc3, prio55, owner2, ts1) - u2o2 := cache.NewUpdate([]string{"interface", "ethernet-0/0", "subinterface", "11", "description"}, desc3, prio55, owner2, ts1) + u1o1_0 := types.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto("ethernet-1/1"), prio50, owner1, ts1) + u1o1 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "10", "description"}, desc3, prio50, owner1, ts1) + u1o1_1 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "10", "index"}, testhelper.GetUIntTvProto(10), prio50, owner1, ts1) + u2o1 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "11", "description"}, desc3, prio50, owner1, ts1) + u2o1_1 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "11", "index"}, testhelper.GetUIntTvProto(11), prio50, owner1, ts1) + u3 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "12", "description"}, desc3, prio50, owner1, ts1) + u3_1 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "12", "index"}, testhelper.GetUIntTvProto(12), prio50, owner1, ts1) + u4 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "13", "description"}, desc3, prio50, owner1, ts1) + u4_1 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "13", "index"}, testhelper.GetUIntTvProto(13), prio50, owner1, ts1) + + u1o2_0 := types.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto("ethernet-1/1"), prio55, owner2, ts1) + u1o2 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "10", "description"}, desc3, prio55, owner2, ts1) + u1o2_1 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "10", "index"}, testhelper.GetUIntTvProto(10), prio55, owner2, ts1) + u2o2 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "11", "description"}, desc3, prio55, owner2, ts1) + u2o2_1 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "11", "index"}, testhelper.GetUIntTvProto(11), prio55, owner2, ts1) ctx := context.TODO() @@ -359,7 +389,7 @@ func Test_Entry_Four(t *testing.T) { t.Fatal(err) } - tc := NewTreeContext(NewTreeCacheClient("dev1", nil), scb, "foo") + tc := NewTreeContext(scb, "foo") root, err := NewTreeRoot(ctx, tc) if err != nil { @@ -367,14 +397,17 @@ func Test_Entry_Four(t *testing.T) { } // start test add "existing" data - for _, u := range []*cache.Update{u1o1, u2o1, u3, u4, u1o2, u2o2} { - _, err := root.AddCacheUpdateRecursive(ctx, u, flagsExisting) + for _, u := range []*types.Update{u1o1, u2o1, u1o1_1, u2o1_1, u3, u4, u3_1, u4_1, u1o2, u2o2} { + _, err := root.AddUpdateRecursive(ctx, u, flagsExisting) if err != nil { t.Error(err) } } - root.FinishInsertionPhase(ctx) + err = root.FinishInsertionPhase(ctx) + if err != nil { + t.Error(err) + } t.Run("Check the data is present", func(t *testing.T) { @@ -384,29 +417,35 @@ func Test_Entry_Four(t *testing.T) { highprec := root.GetHighestPrecedence(false) // diff the result with the expected - if diff := testhelper.DiffCacheUpdates([]*cache.Update{u1o1, u2o1, u3, u4}, highprec.ToCacheUpdateSlice()); diff != "" { - t.Errorf("root.GetHighesPrio() mismatch (-want +got):\n%s", diff) + if diff := testhelper.DiffUpdates([]*types.Update{u1o1_0, u1o1, u2o1, u3, u4, u1o1_1, u2o1_1, u3_1, u4_1}, highprec.ToUpdateSlice()); diff != "" { + t.Errorf("root.GetHighestPrecedence() mismatch (-want +got):\n%s", diff) } }) // indicate that the intent is receiving an update // therefor invalidate all the present entries of the owner / intent - root.markOwnerDelete(owner1, false) + root.MarkOwnerDelete(owner1, false) // add incomming set intent reques data - overwriteDesc := testhelper.GetStringTvProto(t, "Owerwrite Description") + overwriteDesc := testhelper.GetStringTvProto("Owerwrite Description") // adding a new Update with same owner and priority with different value - n1 := cache.NewUpdate([]string{"interface", "ethernet-0/1", "subinterface", "10", "description"}, overwriteDesc, prio50, owner1, ts1) - n2 := cache.NewUpdate([]string{"interface", "ethernet-0/1", "subinterface", "11", "description"}, overwriteDesc, prio50, owner1, ts1) - - for _, u := range []*cache.Update{n1, n2} { - _, err := root.AddCacheUpdateRecursive(ctx, u, flagsNew) + //n0 := types.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto(t, "ethernet-1/1"), prio50, owner1, ts1) + // n1_1 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "10", "index"}, testhelper.GetIntTvProto(t, 10), prio50, owner1, ts1) + n1 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "10", "description"}, overwriteDesc, prio50, owner1, ts1) + // n2_1 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "11", "index"}, testhelper.GetIntTvProto(t, 11), prio50, owner1, ts1) + n2 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "11", "description"}, overwriteDesc, prio50, owner1, ts1) + + for _, u := range []*types.Update{n1, n2} { + _, err := root.AddUpdateRecursive(ctx, u, flagsNew) if err != nil { t.Error(err) } } - root.FinishInsertionPhase(ctx) + err = root.FinishInsertionPhase(ctx) + if err != nil { + t.Error(err) + } // log the tree t.Log(root.String()) @@ -418,27 +457,27 @@ func Test_Entry_Four(t *testing.T) { highPriLe := root.getByOwnerFiltered(owner1, FilterNonDeleted) - highPri := LeafEntriesToCacheUpdates(highPriLe) + highPri := LeafEntriesToUpdates(highPriLe) // diff the result with the expected - if diff := testhelper.DiffCacheUpdates([]*cache.Update{n1, n2}, highPri); diff != "" { + if diff := testhelper.DiffUpdates([]*types.Update{n1, n2}, highPri); diff != "" { t.Errorf("root.GetByOwner() mismatch (-want +got):\n%s", diff) } }) t.Run("Check the old entries are gone from highest", func(t *testing.T) { - highpri := root.GetHighestPrecedence(true) + highpri := root.GetHighestPrecedence(false) // diff the result with the expected - if diff := testhelper.DiffCacheUpdates([]*cache.Update{n1, n2, u1o2, u2o2}, highpri.ToCacheUpdateSlice()); diff != "" { - t.Errorf("root.GetHighesPrio() mismatch (-want +got):\n%s", diff) + if diff := testhelper.DiffUpdates([]*types.Update{u1o1_0, n1, n2, u2o1_1, u1o1_1, u3, u3_1, u4, u4_1}, highpri.ToUpdateSlice()); diff != "" { + t.Errorf("root.GetHighestPrecedence() mismatch (-want +got):\n%s", diff) } }) - t.Run("Check the old entries are gone from highest (only New Or Updated)", func(t *testing.T) { + t.Run("Check the old entries are gone from highest (Only New Or Updated)", func(t *testing.T) { highpri := root.GetHighestPrecedence(true) // diff the result with the expected - if diff := testhelper.DiffCacheUpdates([]*cache.Update{u1o2, u2o2, n1, n2}, highpri.ToCacheUpdateSlice()); diff != "" { - t.Errorf("root.GetHighesPrio() mismatch (-want +got):\n%s", diff) + if diff := testhelper.DiffUpdates([]*types.Update{u1o2_0, n1, n2, u2o2_1, u1o2_1}, highpri.ToUpdateSlice()); diff != "" { + t.Errorf("root.GetHighestPrecedence() mismatch (-want +got):\n%s", diff) } }) } @@ -460,13 +499,13 @@ func Test_Validation_Leaflist_Min_Max(t *testing.T) { t.Run("Test Leaflist min- & max- elements - One", func(t *testing.T) { - tc := NewTreeContext(NewTreeCacheClient("dev1", nil), scb, owner1) + tc := NewTreeContext(scb, owner1) root, err := NewTreeRoot(ctx, tc) if err != nil { t.Fatal(err) } - leaflistval := testhelper.GetLeafListTvProto(t, + leaflistval := testhelper.GetLeafListTvProto( []*sdcpb.TypedValue{ { Value: &sdcpb.TypedValue_StringVal{StringVal: "data"}, @@ -474,17 +513,20 @@ func Test_Validation_Leaflist_Min_Max(t *testing.T) { }, ) - u1 := cache.NewUpdate([]string{"leaflist", "entry"}, leaflistval, prio50, owner1, ts1) + u1 := types.NewUpdate([]string{"leaflist", "entry"}, leaflistval, prio50, owner1, ts1) // start test add "existing" data - for _, u := range []*cache.Update{u1} { - _, err := root.AddCacheUpdateRecursive(ctx, u, flagsExisting) + for _, u := range []*types.Update{u1} { + _, err := root.AddUpdateRecursive(ctx, u, flagsExisting) if err != nil { t.Fatal(err) } } - root.FinishInsertionPhase(ctx) + err = root.FinishInsertionPhase(ctx) + if err != nil { + t.Error(err) + } t.Log(root.String()) @@ -501,13 +543,13 @@ func Test_Validation_Leaflist_Min_Max(t *testing.T) { t.Run("Test Leaflist min- & max- elements - Two", func(t *testing.T) { - tc := NewTreeContext(NewTreeCacheClient("dev1", nil), scb, owner1) + tc := NewTreeContext(scb, owner1) root, err := NewTreeRoot(ctx, tc) if err != nil { t.Fatal(err) } - leaflistval := testhelper.GetLeafListTvProto(t, + leaflistval := testhelper.GetLeafListTvProto( []*sdcpb.TypedValue{ { Value: &sdcpb.TypedValue_StringVal{StringVal: "data1"}, @@ -518,11 +560,11 @@ func Test_Validation_Leaflist_Min_Max(t *testing.T) { }, ) - u1 := cache.NewUpdate([]string{"leaflist", "entry"}, leaflistval, prio50, owner1, ts1) + u1 := types.NewUpdate([]string{"leaflist", "entry"}, leaflistval, prio50, owner1, ts1) // start test add "existing" data - for _, u := range []*cache.Update{u1} { - _, err := root.AddCacheUpdateRecursive(ctx, u, flagsExisting) + for _, u := range []*types.Update{u1} { + _, err := root.AddUpdateRecursive(ctx, u, flagsExisting) if err != nil { t.Fatal(err) } @@ -541,13 +583,13 @@ func Test_Validation_Leaflist_Min_Max(t *testing.T) { t.Run("Test Leaflist min- & max- elements - Four", func(t *testing.T) { - tc := NewTreeContext(NewTreeCacheClient("dev1", nil), scb, owner1) + tc := NewTreeContext(scb, owner1) root, err := NewTreeRoot(ctx, tc) if err != nil { t.Fatal(err) } - leaflistval := testhelper.GetLeafListTvProto(t, + leaflistval := testhelper.GetLeafListTvProto( []*sdcpb.TypedValue{ { Value: &sdcpb.TypedValue_StringVal{StringVal: "data1"}, @@ -564,11 +606,11 @@ func Test_Validation_Leaflist_Min_Max(t *testing.T) { }, ) - u1 := cache.NewUpdate([]string{"leaflist", "entry"}, leaflistval, prio50, owner1, ts1) + u1 := types.NewUpdate([]string{"leaflist", "entry"}, leaflistval, prio50, owner1, ts1) // start test add "existing" data - for _, u := range []*cache.Update{u1} { - _, err := root.AddCacheUpdateRecursive(ctx, u, flagsExisting) + for _, u := range []*types.Update{u1} { + _, err := root.AddUpdateRecursive(ctx, u, flagsExisting) if err != nil { t.Fatal(err) } @@ -587,17 +629,17 @@ func Test_Validation_Leaflist_Min_Max(t *testing.T) { } func Test_Entry_Delete_Aggregation(t *testing.T) { - desc3 := testhelper.GetStringTvProto(t, "DescriptionThree") + desc3 := testhelper.GetStringTvProto("DescriptionThree") prio50 := int32(50) owner1 := "OwnerOne" ts1 := int64(9999999) - u1 := cache.NewUpdate([]string{"interface", "ethernet-0/0", "description"}, desc3, prio50, owner1, ts1) - u2 := cache.NewUpdate([]string{"interface", "ethernet-0/0", "name"}, testhelper.GetStringTvProto(t, "ethernet-0/0"), prio50, owner1, ts1) - u3 := cache.NewUpdate([]string{"interface", "ethernet-0/0", "subinterface", "0", "index"}, testhelper.GetStringTvProto(t, "0"), prio50, owner1, ts1) - u4 := cache.NewUpdate([]string{"interface", "ethernet-0/0", "subinterface", "0", "description"}, desc3, prio50, owner1, ts1) - u5 := cache.NewUpdate([]string{"interface", "ethernet-0/0", "subinterface", "1", "index"}, testhelper.GetStringTvProto(t, "1"), prio50, owner1, ts1) - u6 := cache.NewUpdate([]string{"interface", "ethernet-0/0", "subinterface", "1", "description"}, desc3, prio50, owner1, ts1) + u1 := types.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, desc3, prio50, owner1, ts1) + u2 := types.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto("ethernet-1/1"), prio50, owner1, ts1) + u3 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "0", "index"}, testhelper.GetUIntTvProto(0), prio50, owner1, ts1) + u4 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "0", "description"}, desc3, prio50, owner1, ts1) + u5 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "1", "index"}, testhelper.GetUIntTvProto(1), prio50, owner1, ts1) + u6 := types.NewUpdate([]string{"interface", "ethernet-1/1", "subinterface", "1", "description"}, desc3, prio50, owner1, ts1) ctx := context.TODO() mockCtrl := gomock.NewController(t) @@ -608,7 +650,7 @@ func Test_Entry_Delete_Aggregation(t *testing.T) { t.Fatal(err) } - tc := NewTreeContext(NewTreeCacheClient("dev1", nil), scb, "foo") + tc := NewTreeContext(scb, "foo") root, err := NewTreeRoot(ctx, tc) if err != nil { @@ -616,28 +658,33 @@ func Test_Entry_Delete_Aggregation(t *testing.T) { } // start test add "existing" data - for _, u := range []*cache.Update{u1, u2, u3, u4, u5, u6} { - _, err := root.AddCacheUpdateRecursive(ctx, u, flagsExisting) + for _, u := range []*types.Update{u1, u2, u3, u4, u5, u6} { + _, err := root.AddUpdateRecursive(ctx, u, flagsExisting) if err != nil { t.Fatal(err) } } // get ready to add the new intent data - root.markOwnerDelete(owner1, false) + root.MarkOwnerDelete(owner1, false) - u1n := cache.NewUpdate([]string{"interface", "ethernet-0/1", "description"}, desc3, prio50, owner1, ts1) - u2n := cache.NewUpdate([]string{"interface", "ethernet-0/1", "name"}, testhelper.GetStringTvProto(t, "ethernet-0/1"), prio50, owner1, ts1) + u1n := types.NewUpdate([]string{"interface", "ethernet-1/1", "description"}, desc3, prio50, owner1, ts1) + u2n := types.NewUpdate([]string{"interface", "ethernet-1/1", "name"}, testhelper.GetStringTvProto("ethernet-1/1"), prio50, owner1, ts1) // start test add "new" / request data - for _, u := range []*cache.Update{u1n, u2n} { - _, err := root.AddCacheUpdateRecursive(ctx, u, flagsNew) + for _, u := range []*types.Update{u1n, u2n} { + _, err := root.AddUpdateRecursive(ctx, u, flagsNew) if err != nil { t.Fatal(err) } } - root.FinishInsertionPhase(ctx) + err = root.FinishInsertionPhase(ctx) + if err != nil { + t.Error(err) + } + + t.Log(root.String()) // retrieve the Deletes deletesSlices, err := root.GetDeletes(true) @@ -653,7 +700,8 @@ func Test_Entry_Delete_Aggregation(t *testing.T) { // define the expected result expects := []string{ - "interface/ethernet-0/0", + "interface/ethernet-1/1/subinterface/0", + "interface/ethernet-1/1/subinterface/1", } // sort both slices for equality check slices.Sort(deletes) @@ -678,8 +726,8 @@ func TestLeafVariants_GetHighesPrio(t *testing.T) { func(t *testing.T) { lv := newLeafVariants(&TreeContext{}) - lv.Add(NewLeafEntry(cache.NewUpdate(path, nil, 2, owner1, ts), flagsExisting, nil)) - lv.Add(NewLeafEntry(cache.NewUpdate(path, nil, 1, owner2, ts), flagsExisting, nil)) + lv.Add(NewLeafEntry(types.NewUpdate(path, nil, 2, owner1, ts), flagsExisting, nil)) + lv.Add(NewLeafEntry(types.NewUpdate(path, nil, 1, owner2, ts), flagsExisting, nil)) lv.les[1].MarkDelete(false) le := lv.GetHighestPrecedence(true, false) @@ -696,7 +744,7 @@ func TestLeafVariants_GetHighesPrio(t *testing.T) { t.Run("Single entry thats also marked for deletion", func(t *testing.T) { lv := newLeafVariants(&TreeContext{}) - lv.Add(NewLeafEntry(cache.NewUpdate(path, nil, 1, owner1, ts), flagsExisting, nil)) + lv.Add(NewLeafEntry(types.NewUpdate(path, nil, 1, owner1, ts), flagsExisting, nil)) lv.les[0].MarkDelete(false) le := lv.GetHighestPrecedence(true, false) @@ -712,9 +760,9 @@ func TestLeafVariants_GetHighesPrio(t *testing.T) { t.Run("New Low Prio IsUpdate OnlyChanged True", func(t *testing.T) { lv := newLeafVariants(&TreeContext{}) - lv.Add(NewLeafEntry(cache.NewUpdate(path, nil, 5, owner1, ts), flagsExisting, nil)) - lv.Add(NewLeafEntry(cache.NewUpdate(path, nil, 6, owner2, ts), flagsNew, nil)) - lv.Add(NewLeafEntry(cache.NewUpdate(path, nil, RunningValuesPrio, RunningIntentName, ts), flagsExisting, nil)) + lv.Add(NewLeafEntry(types.NewUpdate(path, nil, 5, owner1, ts), flagsExisting, nil)) + lv.Add(NewLeafEntry(types.NewUpdate(path, nil, 6, owner2, ts), flagsNew, nil)) + lv.Add(NewLeafEntry(types.NewUpdate(path, nil, RunningValuesPrio, RunningIntentName, ts), flagsExisting, nil)) le := lv.GetHighestPrecedence(true, false) @@ -729,8 +777,8 @@ func TestLeafVariants_GetHighesPrio(t *testing.T) { t.Run("New Low Prio IsUpdate OnlyChanged False", func(t *testing.T) { lv := newLeafVariants(&TreeContext{}) - lv.Add(NewLeafEntry(cache.NewUpdate(path, nil, 5, owner1, ts), flagsExisting, nil)) - lv.Add(NewLeafEntry(cache.NewUpdate(path, nil, 6, owner1, ts), flagsNew, nil)) + lv.Add(NewLeafEntry(types.NewUpdate(path, nil, 5, owner1, ts), flagsExisting, nil)) + lv.Add(NewLeafEntry(types.NewUpdate(path, nil, 6, owner1, ts), flagsNew, nil)) le := lv.GetHighestPrecedence(false, false) @@ -746,9 +794,9 @@ func TestLeafVariants_GetHighesPrio(t *testing.T) { func(t *testing.T) { lv := newLeafVariants(&TreeContext{}) - lv.Add(NewLeafEntry(cache.NewUpdate(path, nil, 5, owner1, ts), flagsExisting, nil)) - lv.Add(NewLeafEntry(cache.NewUpdate(path, nil, 6, owner2, ts), flagsNew, nil)) - lv.Add(NewLeafEntry(cache.NewUpdate(path, nil, RunningValuesPrio, RunningIntentName, ts), flagsExisting, nil)) + lv.Add(NewLeafEntry(types.NewUpdate(path, nil, 5, owner1, ts), flagsExisting, nil)) + lv.Add(NewLeafEntry(types.NewUpdate(path, nil, 6, owner2, ts), flagsNew, nil)) + lv.Add(NewLeafEntry(types.NewUpdate(path, nil, RunningValuesPrio, RunningIntentName, ts), flagsExisting, nil)) le := lv.GetHighestPrecedence(true, false) @@ -764,8 +812,8 @@ func TestLeafVariants_GetHighesPrio(t *testing.T) { func(t *testing.T) { lv := newLeafVariants(&TreeContext{}) - lv.Add(NewLeafEntry(cache.NewUpdate(path, nil, 5, owner1, ts), flagsExisting, nil)) - lv.Add(NewLeafEntry(cache.NewUpdate(path, nil, 6, owner2, ts), flagsNew, nil)) + lv.Add(NewLeafEntry(types.NewUpdate(path, nil, 5, owner1, ts), flagsExisting, nil)) + lv.Add(NewLeafEntry(types.NewUpdate(path, nil, 6, owner2, ts), flagsNew, nil)) le := lv.GetHighestPrecedence(true, false) @@ -778,8 +826,8 @@ func TestLeafVariants_GetHighesPrio(t *testing.T) { t.Run("New Low Prio IsNew OnlyChanged == False", func(t *testing.T) { lv := newLeafVariants(&TreeContext{}) - lv.Add(NewLeafEntry(cache.NewUpdate(path, nil, 5, owner1, ts), flagsExisting, nil)) - lv.Add(NewLeafEntry(cache.NewUpdate(path, nil, 6, owner2, ts), flagsNew, nil)) + lv.Add(NewLeafEntry(types.NewUpdate(path, nil, 5, owner1, ts), flagsExisting, nil)) + lv.Add(NewLeafEntry(types.NewUpdate(path, nil, 6, owner2, ts), flagsNew, nil)) le := lv.GetHighestPrecedence(false, false) @@ -806,9 +854,9 @@ func TestLeafVariants_GetHighesPrio(t *testing.T) { t.Run("secondhighes populated if highes was first", func(t *testing.T) { lv := newLeafVariants(&TreeContext{}) - lv.Add(NewLeafEntry(cache.NewUpdate(path, nil, 1, owner1, ts), flagsExisting, nil)) + lv.Add(NewLeafEntry(types.NewUpdate(path, nil, 1, owner1, ts), flagsExisting, nil)) lv.les[0].MarkDelete(false) - lv.Add(NewLeafEntry(cache.NewUpdate(path, nil, 2, owner2, ts), flagsExisting, nil)) + lv.Add(NewLeafEntry(types.NewUpdate(path, nil, 2, owner2, ts), flagsExisting, nil)) le := lv.GetHighestPrecedence(true, false) @@ -863,7 +911,7 @@ func Test_Schema_Population(t *testing.T) { t.Fatal(err) } - tc := NewTreeContext(NewTreeCacheClient("dev1", nil), scb, "foo") + tc := NewTreeContext(scb, "foo") root, err := NewTreeRoot(ctx, tc) if err != nil { @@ -876,11 +924,11 @@ func Test_Schema_Population(t *testing.T) { } expectNotNil(t, interf.schema, "/interface schema") - e00, err := newSharedEntryAttributes(ctx, interf, "ethernet-0/0", tc) + e00, err := newSharedEntryAttributes(ctx, interf, "ethernet-1/1", tc) if err != nil { t.Error(err) } - expectNil(t, e00.schema, "/interface/ethernet-0/0 schema") + expectNil(t, e00.schema, "/interface/ethernet-1/1 schema") dk, err := newSharedEntryAttributes(ctx, root.sharedEntryAttributes, "doublekey", tc) if err != nil { @@ -918,7 +966,7 @@ func Test_sharedEntryAttributes_SdcpbPath(t *testing.T) { t.Fatal(err) } - tc := NewTreeContext(NewTreeCacheClient("dev1", nil), scb, "foo") + tc := NewTreeContext(scb, "foo") root, err := NewTreeRoot(ctx, tc) if err != nil { @@ -930,7 +978,7 @@ func Test_sharedEntryAttributes_SdcpbPath(t *testing.T) { t.Error(err) } - e00, err := newSharedEntryAttributes(ctx, interf, "ethernet-0/0", tc) + e00, err := newSharedEntryAttributes(ctx, interf, "ethernet-1/1", tc) if err != nil { t.Error(err) } @@ -971,7 +1019,7 @@ func Test_sharedEntryAttributes_SdcpbPath(t *testing.T) { { Name: "interface", Key: map[string]string{ - "name": "ethernet-0/0", + "name": "ethernet-1/1", }, }, }, @@ -992,7 +1040,7 @@ func Test_sharedEntryAttributes_SdcpbPath(t *testing.T) { { Name: "interface", Key: map[string]string{ - "name": "ethernet-0/0", + "name": "ethernet-1/1", }, }, { @@ -1045,7 +1093,7 @@ func Test_sharedEntryAttributes_getKeyName(t *testing.T) { t.Fatal(err) } - tc := NewTreeContext(NewTreeCacheClient("dev1", nil), scb, "foo") + tc := NewTreeContext(scb, "foo") root, err := NewTreeRoot(ctx, tc) if err != nil { @@ -1057,7 +1105,7 @@ func Test_sharedEntryAttributes_getKeyName(t *testing.T) { t.Error(err) } - e00, err := newSharedEntryAttributes(ctx, interf, "ethernet-0/0", tc) + e00, err := newSharedEntryAttributes(ctx, interf, "ethernet-1/1", tc) if err != nil { t.Error(err) } @@ -1142,24 +1190,27 @@ func Test_Validation_String_Pattern(t *testing.T) { t.Run("Test_Validation_String_Pattern - One", func(t *testing.T) { - tc := NewTreeContext(NewTreeCacheClient("dev1", nil), scb, owner1) + tc := NewTreeContext(scb, owner1) root, err := NewTreeRoot(ctx, tc) if err != nil { t.Fatal(err) } - leafval := testhelper.GetStringTvProto(t, "data123") + leafval := testhelper.GetStringTvProto("data123") - u1 := cache.NewUpdate([]string{"patterntest"}, leafval, prio50, owner1, ts1) + u1 := types.NewUpdate([]string{"patterntest"}, leafval, prio50, owner1, ts1) - for _, u := range []*cache.Update{u1} { - _, err := root.AddCacheUpdateRecursive(ctx, u, flagsNew) + for _, u := range []*types.Update{u1} { + _, err := root.AddUpdateRecursive(ctx, u, flagsNew) if err != nil { t.Fatal(err) } } - root.FinishInsertionPhase(ctx) + err = root.FinishInsertionPhase(ctx) + if err != nil { + t.Error(err) + } validationResult := root.Validate(context.TODO(), validationConfig) @@ -1171,29 +1222,32 @@ func Test_Validation_String_Pattern(t *testing.T) { } }, ) - flagsNew := NewUpdateInsertFlags() + flagsNew := types.NewUpdateInsertFlags() flagsNew.SetNewFlag() t.Run("Test_Validation_String_Pattern - Two", func(t *testing.T) { - tc := NewTreeContext(NewTreeCacheClient("dev1", nil), scb, owner1) + tc := NewTreeContext(scb, owner1) root, err := NewTreeRoot(ctx, tc) if err != nil { t.Fatal(err) } - leafval := testhelper.GetStringTvProto(t, "hallo F") + leafval := testhelper.GetStringTvProto("hallo F") - u1 := cache.NewUpdate([]string{"patterntest"}, leafval, prio50, owner1, ts1) + u1 := types.NewUpdate([]string{"patterntest"}, leafval, prio50, owner1, ts1) - for _, u := range []*cache.Update{u1} { - _, err := root.AddCacheUpdateRecursive(ctx, u, flagsNew) + for _, u := range []*types.Update{u1} { + _, err := root.AddUpdateRecursive(ctx, u, flagsNew) if err != nil { t.Fatal(err) } } - root.FinishInsertionPhase(ctx) + err = root.FinishInsertionPhase(ctx) + if err != nil { + t.Error(err) + } validationResult := root.Validate(context.TODO(), validationConfig) @@ -1216,10 +1270,10 @@ func Test_Validation_String_Pattern(t *testing.T) { // leafval := testhelper.GetStringTvProto(t, "hallo DU") - // u1 := cache.NewUpdate([]string{"patterntest"}, leafval, prio50, owner1, ts1) + // u1 := types.NewUpdate([]string{"patterntest"}, leafval, prio50, owner1, ts1) - // for _, u := range []*cache.Update{u1} { - // err := root.AddCacheUpdateRecursive(ctx, u, true) + // for _, u := range []*types.Update{u1} { + // err := root.AddUpdateRecursive(ctx, u, true) // if err != nil { // t.Fatal(err) // } @@ -1264,29 +1318,32 @@ func Test_Validation_Deref(t *testing.T) { t.Fatal(err) } - flagsNew := NewUpdateInsertFlags() + flagsNew := types.NewUpdateInsertFlags() flagsNew.SetNewFlag() t.Run("Test_Validation_String_Pattern - One", func(t *testing.T) { - tc := NewTreeContext(NewTreeCacheClient("dev1", nil), scb, owner1) + tc := NewTreeContext(scb, owner1) root, err := NewTreeRoot(ctx, tc) if err != nil { t.Fatal(err) } - leafval := testhelper.GetStringTvProto(t, "data123") + leafval := testhelper.GetStringTvProto("data123") - u1 := cache.NewUpdate([]string{"patterntest"}, leafval, prio50, owner1, ts1) + u1 := types.NewUpdate([]string{"patterntest"}, leafval, prio50, owner1, ts1) - for _, u := range []*cache.Update{u1} { - _, err := root.AddCacheUpdateRecursive(ctx, u, flagsNew) + for _, u := range []*types.Update{u1} { + _, err := root.AddUpdateRecursive(ctx, u, flagsNew) if err != nil { t.Fatal(err) } } - root.FinishInsertionPhase(ctx) + err = root.FinishInsertionPhase(ctx) + if err != nil { + t.Error(err) + } validationResult := root.Validate(context.TODO(), validationConfig) diff --git a/pkg/tree/importer/json/json_tree_importer.go b/pkg/tree/importer/json/json_tree_importer.go index a11969f6..61aaf254 100644 --- a/pkg/tree/importer/json/json_tree_importer.go +++ b/pkg/tree/importer/json/json_tree_importer.go @@ -82,3 +82,6 @@ func (j *JsonTreeImporter) GetTVValue(slt *sdcpb.SchemaLeafType) (*sdcpb.TypedVa func (j *JsonTreeImporter) GetName() string { return j.name } + +// Function to ensure JsonTreeImporter implements ImportConfigAdapter (optional) +var _ importer.ImportConfigAdapter = (*JsonTreeImporter)(nil) diff --git a/pkg/tree/importer/json/json_tree_importer_test.go b/pkg/tree/importer/json/json_tree_importer_test.go index d5686ded..16a36b07 100644 --- a/pkg/tree/importer/json/json_tree_importer_test.go +++ b/pkg/tree/importer/json/json_tree_importer_test.go @@ -6,8 +6,8 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/sdcio/data-server/mocks/mockcacheclient" "github.com/sdcio/data-server/pkg/tree" + "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils/testhelper" "go.uber.org/mock/gomock" ) @@ -22,101 +22,108 @@ func TestJsonTreeImporter(t *testing.T) { { name: "JSON", input: ` - { - "choices": { - "case1": { - "case-elem": { - "elem": "foocaseval" - } - } - }, - "interface": [ - { - "admin-state": "enable", - "description": "Foo", - "name": "ethernet-1/2", - "subinterface": [ - { - "description": "Subinterface 5", - "index": 5, - "type": "routed" - } - ] - } - ], - "leaflist": { - "entry": [ - "foo", - "bar" - ] - }, - "network-instance": [ - { - "admin-state": "enable", - "description": "Other NI", - "name": "other", - "type": "ip-vrf", - "protocol":{ - "bgp": {} - } - } - ], - "patterntest": "hallo DU", - "emptyconf": {} - }`, + { + "choices": { + "case1": { + "case-elem": { + "elem": "foocaseval" + } + } + }, + "interface": [ + { + "admin-state": "enable", + "description": "Foo", + "name": "ethernet-1/2", + "subinterface": [ + { + "description": "Subinterface 5", + "index": 5, + "type": "routed" + } + ] + }, + { + "admin-state": "disable", + "description": "Bar", + "name": "ethernet-1/3", + "subinterface": [ + { + "description": "Subinterface 7", + "index": 7, + "type": "routed" + } + ] + } + ], + "leaflist": { + "entry": [ + "foo", + "bar" + ] + }, + "network-instance": [ + { + "admin-state": "enable", + "description": "Other NI", + "name": "other", + "type": "ip-vrf", + "protocol":{ + "bgp": {} + } + } + ], + "patterntest": "hallo DU", + "emptyconf": {} + }`, }, { name: "JSON_IETF", ietf: true, input: `{ - "sdcio_model:patterntest": "foo", - "sdcio_model_choice:choices": { - "case1": { - "case-elem": { - "elem": "foocaseval" - } - } - }, - "sdcio_model_if:interface": [ - { - "admin-state": "enable", - "description": "Foo", - "name": "ethernet-1/1", - "subinterface": [ - { - "description": "Subinterface 0", - "index": 0, - "type": "sdcio_model_common:routed" - } - ] - } - ], - "sdcio_model_leaflist:leaflist": { - "entry": [ - "foo", - "bar" - ] - }, - "sdcio_model_ni:network-instance": [ - { - "admin-state": "disable", - "description": "Default NI", - "name": "default", - "type": "sdcio_model_ni:default" - } - ] - }`, + "sdcio_model:patterntest": "foo", + "sdcio_model_choice:choices": { + "case1": { + "case-elem": { + "elem": "foocaseval" + } + } + }, + "sdcio_model_if:interface": [ + { + "admin-state": "enable", + "description": "Foo", + "name": "ethernet-1/1", + "subinterface": [ + { + "description": "Subinterface 0", + "index": 0, + "type": "sdcio_model_common:routed" + } + ] + } + ], + "sdcio_model_leaflist:leaflist": { + "entry": [ + "foo", + "bar" + ] + }, + "sdcio_model_ni:network-instance": [ + { + "admin-state": "disable", + "description": "Default NI", + "name": "default", + "type": "sdcio_model_ni:default" + } + ] + }`, }, } // create a gomock controller controller := gomock.NewController(t) - // create a cache client mock - cacheClient := mockcacheclient.NewMockClient(controller) - testhelper.ConfigureCacheClientMock(t, cacheClient, nil, nil, nil, nil) - - dsName := "dev1" scb, err := testhelper.GetSchemaClientBound(t, controller) if err != nil { t.Error(err) @@ -125,7 +132,7 @@ func TestJsonTreeImporter(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - tc := tree.NewTreeContext(tree.NewTreeCacheClient(dsName, cacheClient), scb, "test") + tc := tree.NewTreeContext(scb, "intent1") root, err := tree.NewTreeRoot(ctx, tc) if err != nil { t.Error(err) @@ -139,12 +146,15 @@ func TestJsonTreeImporter(t *testing.T) { t.Fatalf("error parsing json document: %v", err) } jti := NewJsonTreeImporter(j) - err = root.ImportConfig(ctx, jti, tree.RunningIntentName, tree.RunningValuesPrio) + err = root.ImportConfig(ctx, nil, jti, "owner1", 5, types.NewUpdateInsertFlags()) if err != nil { t.Fatal(err) } - root.FinishInsertionPhase(ctx) + err = root.FinishInsertionPhase(ctx) + if err != nil { + t.Error(err) + } t.Log(root.String()) var result any diff --git a/pkg/tree/importer/proto/proto_tree_importer.go b/pkg/tree/importer/proto/proto_tree_importer.go new file mode 100644 index 00000000..e5412737 --- /dev/null +++ b/pkg/tree/importer/proto/proto_tree_importer.go @@ -0,0 +1,75 @@ +package proto + +import ( + "github.com/sdcio/data-server/pkg/tree/importer" + "github.com/sdcio/data-server/pkg/tree/tree_persist" + "github.com/sdcio/data-server/pkg/utils" + sdcpb "github.com/sdcio/sdc-protos/sdcpb" + log "github.com/sirupsen/logrus" + "google.golang.org/protobuf/proto" +) + +type ProtoTreeImporter struct { + data *tree_persist.TreeElement +} + +func NewProtoTreeImporter(data *tree_persist.TreeElement) *ProtoTreeImporter { + return &ProtoTreeImporter{ + data: data, + } +} + +func (p *ProtoTreeImporter) GetElements() []importer.ImportConfigAdapter { + if len(p.data.Childs) == 0 { + return nil + } + result := []importer.ImportConfigAdapter{} + for _, c := range p.data.Childs { + result = append(result, NewProtoTreeImporter(c)) + } + return result +} + +// func (p *ProtoTreeImporter) getListElements(elems []*tree_persist.TreeElement) []*tree_persist.TreeElement { +// result := make([]*tree_persist.TreeElement, 0, len(elems)) +// for _, elem := range elems { +// if elem.Name == "" { +// result = append(result, &tree_persist.TreeElement{Name: p.data.Name, Childs: p.getListElements(elem.GetChilds())}) +// } else { +// result = append(result, p.getListElements(elem.GetChilds())...) +// } +// } +// return result +// } + +func (p *ProtoTreeImporter) GetElement(key string) importer.ImportConfigAdapter { + for _, c := range p.data.Childs { + if c.Name == key { + return NewProtoTreeImporter(c) + } + } + return nil +} + +func (p *ProtoTreeImporter) GetKeyValue() string { + tv, err := p.GetTVValue(nil) + if err != nil { + log.Errorf("failed GetTVValue for %s", p.data.Name) + } + return utils.TypedValueToString(tv) +} + +func (p *ProtoTreeImporter) GetTVValue(slt *sdcpb.SchemaLeafType) (*sdcpb.TypedValue, error) { + result := &sdcpb.TypedValue{} + err := proto.Unmarshal(p.data.LeafVariant, result) + if err != nil { + return nil, err + } + return result, nil +} +func (p *ProtoTreeImporter) GetName() string { + return p.data.Name +} + +// Function to ensure ProtoTreeImporter implements ImportConfigAdapter (optional) +var _ importer.ImportConfigAdapter = (*ProtoTreeImporter)(nil) diff --git a/pkg/tree/importer/proto/proto_tree_importer_test.go b/pkg/tree/importer/proto/proto_tree_importer_test.go new file mode 100644 index 00000000..d16f4b71 --- /dev/null +++ b/pkg/tree/importer/proto/proto_tree_importer_test.go @@ -0,0 +1,171 @@ +package proto + +import ( + "context" + "encoding/json" + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + + "github.com/sdcio/data-server/pkg/tree" + jimport "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" + "go.uber.org/mock/gomock" +) + +func TestProtoTreeImporter(t *testing.T) { + + tests := []struct { + name string + input string + }{ + { + name: "One", + input: ` + { + "choices": { + "case1": { + "case-elem": { + "elem": "foocaseval" + } + } + }, + "interface": [ + { + "admin-state": "enable", + "description": "Foo", + "name": "ethernet-1/2", + "subinterface": [ + { + "description": "Subinterface 5", + "index": 5, + "type": "routed" + } + ] + }, + { + "admin-state": "enable", + "description": "Bar", + "name": "ethernet-1/3", + "subinterface": [ + { + "description": "Subinterface 5", + "index": 5, + "type": "routed" + } + ] + } + , { + "admin-state": "enable", + "description": "FooBar", + "name": "ethernet-1/4", + "subinterface": [ + { + "description": "Subinterface 5", + "index": 5, + "type": "routed" + }, { + "description": "Subinterface 6", + "index": 6, + "type": "routed" + }, { + "description": "Subinterface 7", + "index": 7, + "type": "routed" + } + ] + } + ], + "leaflist": { + "entry": [ + "foo", + "bar" + ] + }, + "network-instance": [ + { + "admin-state": "enable", + "description": "Other NI", + "name": "other", + "type": "ip-vrf", + "protocol":{ + "bgp": {} + } + } + ], + "patterntest": "hallo DU", + "emptyconf": {} + }`, + }, + } + + // create a gomock controller + controller := gomock.NewController(t) + + scb, err := testhelper.GetSchemaClientBound(t, controller) + if err != nil { + t.Error(err) + } + ctx := context.Background() + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tc := tree.NewTreeContext(scb, "test") + root, err := tree.NewTreeRoot(ctx, tc) + if err != nil { + t.Error(err) + } + + jsonBytes := []byte(tt.input) + + var j any + err = json.Unmarshal(jsonBytes, &j) + if err != nil { + t.Fatalf("error parsing json document: %v", err) + } + + jti := jimport.NewJsonTreeImporter(j) + err = root.ImportConfig(ctx, nil, jti, "owner1", 5, types.NewUpdateInsertFlags()) + if err != nil { + t.Fatal(err) + } + + err = root.FinishInsertionPhase(ctx) + if err != nil { + t.Error(err) + } + t.Log(root.String()) + + protoIntent, err := root.TreeExport("owner1", 5) + if err != nil { + t.Error(err) + } + + fmt.Println(protoIntent.PrettyString(" ")) + + tcNew := tree.NewTreeContext(scb, "test") + rootNew, err := tree.NewTreeRoot(ctx, tcNew) + if err != nil { + t.Error(err) + } + + protoAdapter := NewProtoTreeImporter(protoIntent.GetRoot()) + + err = rootNew.ImportConfig(ctx, nil, protoAdapter, protoIntent.GetIntentName(), protoIntent.GetPriority(), types.NewUpdateInsertFlags()) + if err != nil { + t.Error(err) + } + err = rootNew.FinishInsertionPhase(ctx) + if err != nil { + t.Error(err) + } + t.Log(rootNew.String()) + + if diff := cmp.Diff(root.String(), rootNew.String()); diff != "" { + t.Errorf("Error imported data differs:%s", diff) + } + }) + } +} diff --git a/pkg/tree/importer/xml/xml_tree_importer.go b/pkg/tree/importer/xml/xml_tree_importer.go index a2d5444c..9ec64106 100644 --- a/pkg/tree/importer/xml/xml_tree_importer.go +++ b/pkg/tree/importer/xml/xml_tree_importer.go @@ -51,3 +51,6 @@ func (x *XmlTreeImporter) GetTVValue(slt *sdcpb.SchemaLeafType) (*sdcpb.TypedVal func (x *XmlTreeImporter) GetName() string { return x.elem.Tag } + +// Function to ensure JsonTreeImporter implements ImportConfigAdapter (optional) +var _ importer.ImportConfigAdapter = (*XmlTreeImporter)(nil) diff --git a/pkg/tree/importer/xml/xml_tree_importer_test.go b/pkg/tree/importer/xml/xml_tree_importer_test.go index 2e196229..4ace5325 100644 --- a/pkg/tree/importer/xml/xml_tree_importer_test.go +++ b/pkg/tree/importer/xml/xml_tree_importer_test.go @@ -7,8 +7,8 @@ import ( "github.com/beevik/etree" "github.com/google/go-cmp/cmp" - "github.com/sdcio/data-server/mocks/mockcacheclient" "github.com/sdcio/data-server/pkg/tree" + "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils" "github.com/sdcio/data-server/pkg/utils/testhelper" "go.uber.org/mock/gomock" @@ -71,18 +71,13 @@ func TestXmlTreeImporter(t *testing.T) { // create a gomock controller controller := gomock.NewController(t) - // create a cache client mock - cacheClient := mockcacheclient.NewMockClient(controller) - testhelper.ConfigureCacheClientMock(t, cacheClient, nil, nil, nil, nil) - - dsName := "dev1" scb, err := testhelper.GetSchemaClientBound(t, controller) if err != nil { t.Fatal(err) } ctx := context.Background() - tc := tree.NewTreeContext(tree.NewTreeCacheClient(dsName, cacheClient), scb, "test") + tc := tree.NewTreeContext(scb, "test") for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -97,14 +92,17 @@ func TestXmlTreeImporter(t *testing.T) { t.Fatal(err) } - err = root.ImportConfig(ctx, NewXmlTreeImporter(&inputDoc.Element), tree.RunningIntentName, tree.RunningValuesPrio) + err = root.ImportConfig(ctx, nil, NewXmlTreeImporter(&inputDoc.Element), "owner1", 5, types.NewUpdateInsertFlags()) if err != nil { t.Fatal(err) } t.Log(root.String()) fmt.Println(root.String()) - root.FinishInsertionPhase(ctx) + err = root.FinishInsertionPhase(ctx) + if err != nil { + t.Error(err) + } result, err := root.ToXML(false, false, false, false) if err != nil { diff --git a/pkg/tree/json.go b/pkg/tree/json.go index cd0a03ee..4ef3d4a6 100644 --- a/pkg/tree/json.go +++ b/pkg/tree/json.go @@ -125,11 +125,7 @@ func (s *sharedEntryAttributes) toJsonInternal(onlyNewOrUpdated bool, ietf bool) if le == nil { return nil, nil } - v, err := le.Update.Value() - if err != nil { - return nil, err - } - return utils.GetJsonValue(v, ietf) + return utils.GetJsonValue(le.Value(), ietf) } return nil, fmt.Errorf("unable to convert to json (%s)", s.Path()) } diff --git a/pkg/tree/json_test.go b/pkg/tree/json_test.go index e4ea3346..c6adb217 100644 --- a/pkg/tree/json_test.go +++ b/pkg/tree/json_test.go @@ -8,14 +8,12 @@ import ( "github.com/google/go-cmp/cmp" "github.com/openconfig/ygot/ygot" - "github.com/sdcio/data-server/mocks/mockcacheclient" - "github.com/sdcio/data-server/pkg/cache" + "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils" "github.com/sdcio/data-server/pkg/utils/testhelper" sdcio_schema "github.com/sdcio/data-server/tests/sdcioygot" sdcpb "github.com/sdcio/sdc-protos/sdcpb" "go.uber.org/mock/gomock" - "google.golang.org/protobuf/proto" ) func TestToJsonTable(t *testing.T) { @@ -361,10 +359,10 @@ func TestToJsonTable(t *testing.T) { }, } - flagsNew := NewUpdateInsertFlags() + flagsNew := types.NewUpdateInsertFlags() flagsNew.SetNewFlag() - flagsOld := NewUpdateInsertFlags() + flagsOld := types.NewUpdateInsertFlags() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -380,11 +378,7 @@ func TestToJsonTable(t *testing.T) { ctx := context.Background() - // create a cache client mock - cacheClient := mockcacheclient.NewMockClient(mockCtrl) - testhelper.ConfigureCacheClientMock(t, cacheClient, []*cache.Update{}, []*cache.Update{}, []*cache.Update{}, [][]string{}) - - tc := NewTreeContext(NewTreeCacheClient("dev1", cacheClient), scb, owner) + tc := NewTreeContext(scb, owner) root, err := NewTreeRoot(ctx, tc) if err != nil { t.Fatal(err) @@ -423,7 +417,10 @@ func TestToJsonTable(t *testing.T) { t.Fatal(err) } } - root.FinishInsertionPhase(ctx) + err = root.FinishInsertionPhase(ctx) + if err != nil { + t.Error(err) + } fmt.Println(root.String()) @@ -550,19 +547,14 @@ func expandUpdateFromConfig(ctx context.Context, conf *sdcio_schema.Device, conv Elem: []*sdcpb.PathElem{}, }, Value: &sdcpb.TypedValue{Value: &sdcpb.TypedValue_JsonVal{JsonVal: []byte(strJson)}}, - }, - true) + }) } -func addToRoot(ctx context.Context, root *RootEntry, updates []*sdcpb.Update, flags *UpdateInsertFlags, owner string, prio int32) error { +func addToRoot(ctx context.Context, root *RootEntry, updates []*sdcpb.Update, flags *types.UpdateInsertFlags, owner string, prio int32) error { for _, upd := range updates { - b, err := proto.Marshal(upd.Value) - if err != nil { - return err - } - cacheUpd := cache.NewUpdate(utils.ToStrings(upd.GetPath(), false, false), b, prio, owner, 0) + cacheUpd := types.NewUpdate(utils.ToStrings(upd.GetPath(), false, false), upd.Value, prio, owner, 0) - _, err = root.AddCacheUpdateRecursive(ctx, cacheUpd, flags) + _, err := root.AddUpdateRecursive(ctx, cacheUpd, flags) if err != nil { return err } diff --git a/pkg/tree/leaf_entry.go b/pkg/tree/leaf_entry.go index 5e7623b9..961bf215 100644 --- a/pkg/tree/leaf_entry.go +++ b/pkg/tree/leaf_entry.go @@ -4,13 +4,16 @@ import ( "fmt" "sync" - "github.com/sdcio/data-server/pkg/cache" + "github.com/sdcio/data-server/pkg/tree/types" + "github.com/sdcio/data-server/pkg/utils" ) // LeafEntry stores the *cache.Update along with additional attributes. // These Attributes indicate if the entry is to be deleted / added (new) or updated. type LeafEntry struct { - *cache.Update + *types.Update + + // helper values parentEntry Entry IsNew bool Delete bool @@ -20,12 +23,24 @@ type LeafEntry struct { mu sync.RWMutex } +func (l *LeafEntry) DeepCopy() *LeafEntry { + return &LeafEntry{ + Update: l.Update.DeepCopy(), + parentEntry: l.parentEntry, + IsNew: l.IsNew, + Delete: l.Delete, + DeleteOnlyIntended: l.DeleteOnlyIntended, + IsUpdated: l.IsUpdated, + mu: sync.RWMutex{}, + } +} + func (l *LeafEntry) GetEntry() Entry { return l.parentEntry } // MarkUpdate indicate that the entry is an Updated value -func (l *LeafEntry) MarkUpdate(u *cache.Update) { +func (l *LeafEntry) MarkUpdate(u *types.Update) { l.mu.Lock() defer l.mu.Unlock() // set the new value @@ -97,23 +112,15 @@ func (l *LeafEntry) GetRootBasedEntryChain() []Entry { // String returns a string representation of the LeafEntry func (l *LeafEntry) String() string { - tv, err := l.Value() - var v string - if err != nil { - v = err.Error() - } else { - v = tv.String() - } - return fmt.Sprintf("Owner: %s, Priority: %d, Value: %s, New: %t, Delete: %t, Update: %t, DeleteIntendedOnly: %t", l.Owner(), l.Priority(), v, l.GetNewFlag(), l.GetDeleteFlag(), l.GetUpdateFlag(), l.GetDeleteOnlyIntendedFlag()) + return fmt.Sprintf("Owner: %s, Priority: %d, Value: %s, New: %t, Delete: %t, Update: %t, DeleteIntendedOnly: %t", l.Owner(), l.Priority(), utils.TypedValueToString(l.Value()), l.GetNewFlag(), l.GetDeleteFlag(), l.GetUpdateFlag(), l.GetDeleteOnlyIntendedFlag()) } // NewLeafEntry constructor for a new LeafEntry -func NewLeafEntry(c *cache.Update, flags *UpdateInsertFlags, parent Entry) *LeafEntry { +func NewLeafEntry(c *types.Update, flags *types.UpdateInsertFlags, parent Entry) *LeafEntry { le := &LeafEntry{ parentEntry: parent, Update: c, } flags.Apply(le) return le - } diff --git a/pkg/tree/leaf_entry_filter.go b/pkg/tree/leaf_entry_filter.go index ddc6b477..0eda6ac1 100644 --- a/pkg/tree/leaf_entry_filter.go +++ b/pkg/tree/leaf_entry_filter.go @@ -1,6 +1,6 @@ package tree -import "github.com/sdcio/data-server/pkg/cache" +import "github.com/sdcio/data-server/pkg/tree/types" type LeafEntryFilter func(*LeafEntry) bool @@ -35,8 +35,8 @@ func Unfiltered(l *LeafEntry) bool { } // LeafEntriesToCacheUpdates -func LeafEntriesToCacheUpdates(l []*LeafEntry) []*cache.Update { - result := make([]*cache.Update, 0, len(l)) +func LeafEntriesToUpdates(l []*LeafEntry) []*types.Update { + result := make([]*types.Update, 0, len(l)) for _, e := range l { result = append(result, e.Update) } diff --git a/pkg/tree/leaf_variant_slice.go b/pkg/tree/leaf_variant_slice.go index adf6eccf..a9d22741 100644 --- a/pkg/tree/leaf_variant_slice.go +++ b/pkg/tree/leaf_variant_slice.go @@ -1,11 +1,11 @@ package tree -import "github.com/sdcio/data-server/pkg/cache" +import "github.com/sdcio/data-server/pkg/tree/types" type LeafVariantSlice []*LeafEntry -func (lvs LeafVariantSlice) ToCacheUpdateSlice() []*cache.Update { - result := make([]*cache.Update, 0, len(lvs)) +func (lvs LeafVariantSlice) ToUpdateSlice() types.UpdateSlice { + result := make([]*types.Update, 0, len(lvs)) for _, x := range lvs { result = append(result, x.Update) } diff --git a/pkg/tree/leaf_variants.go b/pkg/tree/leaf_variants.go index 8bbc1224..29daba6a 100644 --- a/pkg/tree/leaf_variants.go +++ b/pkg/tree/leaf_variants.go @@ -5,7 +5,9 @@ import ( "math" "sync" + "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils" + log "github.com/sirupsen/logrus" ) type LeafVariants struct { @@ -23,7 +25,7 @@ func newLeafVariants(tc *TreeContext) *LeafVariants { func (lv *LeafVariants) Add(le *LeafEntry) { if leafVariant := lv.GetByOwner(le.Owner()); leafVariant != nil { - if leafVariant.EqualSkipPath(le.Update) { + if leafVariant.Equal(le.Update) { // it seems like the element was not deleted, so drop the delete flag leafVariant.DropDeleteFlag() } else { @@ -57,19 +59,6 @@ func (lv *LeafVariants) Length() int { return len(lv.les) } -// containsOtherOwnerThenDefaultOrRunning returns true if there is any other leafentry then default or running -func (lv *LeafVariants) containsOtherOwnerThenDefaultOrRunning() bool { - foundOther := false - for _, le := range lv.les { - foundOther = le.Owner() != RunningIntentName && le.Owner() != DefaultsIntentName - if foundOther { - break - } - } - - return foundOther -} - // 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() @@ -150,12 +139,12 @@ func (lv *LeafVariants) remainsToExist() bool { return false } -func (lv *LeafVariants) GetHighestPrecedenceValue() int32 { +func (lv *LeafVariants) GetHighestPrecedenceValue(filter HighestPrecedenceFilter) int32 { lv.lesMutex.RLock() defer lv.lesMutex.RUnlock() result := int32(math.MaxInt32) for _, e := range lv.les { - if !e.GetDeleteFlag() && e.Owner() != DefaultsIntentName && e.Update.Priority() < result { + if filter(e) && e.Owner() != DefaultsIntentName && e.Update.Priority() < result { result = e.Update.Priority() } } @@ -253,8 +242,8 @@ func (lv *LeafVariants) highestIsUnequalRunning(highest *LeafEntry) bool { } // ignore errors, they should not happen :-P I know... should... - rval, _ := runVal.Value() - hval, _ := highest.Value() + rval := runVal.Value() + hval := highest.Value() return !utils.EqualTypedValues(rval, hval) } @@ -271,3 +260,114 @@ func (lv *LeafVariants) GetByOwner(owner string) *LeafEntry { } return nil } + +// MarkOwnerForDeletion searches for a LefVariant of given owner, if it exists +// the entry is marked for deletion +func (lv *LeafVariants) MarkOwnerForDeletion(owner string, onlyIntended bool) { + le := lv.GetByOwner(owner) + if le != nil { + le.MarkDelete(onlyIntended) + } +} + +func (lv *LeafVariants) DeleteByOwner(owner string) (remainsToExist bool) { + foundOwner := false + for i, l := range lv.les { + // early exit if condition is met + if foundOwner && remainsToExist { + return remainsToExist + } + if l.Owner() == owner { + // Remove element from slice + lv.les = append(lv.les[:i], lv.les[i+1:]...) + foundOwner = true + continue + } + if l.Owner() == DefaultsIntentName { + continue + } + remainsToExist = true + + } + return remainsToExist +} + +func (lv *LeafVariants) GetDeviations(ch chan<- *types.DeviationEntry, isActiveCase bool) { + if len(lv.les) == 0 { + return + } + + // get the path via the first LeafEntry + // is valida for all entries + sdcpbPath, err := lv.les[0].parentEntry.SdcpbPath() + if err != nil { + log.Error(err) + } + + // we are part of an inactive case of a choice + if !isActiveCase { + for _, le := range lv.les { + ch <- types.NewDeviationEntry(le.Owner(), types.DeviationReasonOverruled, sdcpbPath).SetExpectedValue(le.Value()) + } + return + } + + var running *LeafEntry + var highest *LeafEntry + + overruled := make([]*types.DeviationEntry, 0, len(lv.les)) + for _, le := range lv.les { + // Defaults should be skipped + if le.Owner() == DefaultsIntentName { + continue + } + // running is stored in running var + if le.Owner() == RunningIntentName { + running = le + continue + } + // if no highest exists yet, set it + if highest == nil { + highest = le + continue + } + // if precedence of actual (le) is higher then highest + // replace highest with it + if le.Priority() < highest.Priority() { + de := types.NewDeviationEntry(highest.Owner(), types.DeviationReasonOverruled, sdcpbPath).SetExpectedValue(highest.Value()) + overruled = append(overruled, de) + highest = le + } + // if precedence of actual (le) is lower then le needs to be adeded to overruled + if le.Priority() >= highest.Priority() { + de := types.NewDeviationEntry(le.Owner(), types.DeviationReasonOverruled, sdcpbPath).SetExpectedValue(le.Value()) + overruled = append(overruled, de) + } + } + + // send all the overruleds + for _, de := range overruled { + ch <- de.SetCurrentValue(highest.Value()) + } + + // if there is no running and no highest (probably a default), skip + if running == nil && highest == nil { + return + } + + // unhandled -> running but no intent data + if running != nil && highest == nil { + ch <- types.NewDeviationEntry(running.Owner(), types.DeviationReasonUnhandled, sdcpbPath).SetCurrentValue(running.Value()) + return + } + + // if higeste exists but not running OR running != highest + if (running == nil && highest != nil) || running.Value().Cmp(highest.Value()) != 0 { + de := types.NewDeviationEntry(highest.Owner(), types.DeviationReasonNotApplied, sdcpbPath).SetExpectedValue(highest.Value()) + if running != nil { + de.SetCurrentValue(running.Value()) + } + ch <- de + } + +} diff --git a/pkg/tree/proto.go b/pkg/tree/proto.go index 6cfb2819..2b0e89e0 100644 --- a/pkg/tree/proto.go +++ b/pkg/tree/proto.go @@ -7,17 +7,13 @@ import ( ) func (r *RootEntry) ToProtoUpdates(ctx context.Context, onlyNewOrUpdated bool) ([]*sdcpb.Update, error) { - cacheUpdates := r.GetHighestPrecedence(onlyNewOrUpdated) upds := make([]*sdcpb.Update, 0, len(cacheUpdates)) // updates for _, cachUpdate := range cacheUpdates { - val, err := cachUpdate.Value() - if err != nil { - return nil, err - } + val := cachUpdate.Value() path, err := cachUpdate.parentEntry.SdcpbPath() if err != nil { return nil, err @@ -30,7 +26,6 @@ func (r *RootEntry) ToProtoUpdates(ctx context.Context, onlyNewOrUpdated bool) ( } func (r *RootEntry) ToProtoDeletes(ctx context.Context) ([]*sdcpb.Path, error) { - cacheDeletes, err := r.GetDeletes(true) if err != nil { return nil, err diff --git a/pkg/tree/root_entry.go b/pkg/tree/root_entry.go index 96b1b465..3d789223 100644 --- a/pkg/tree/root_entry.go +++ b/pkg/tree/root_entry.go @@ -2,10 +2,14 @@ package tree import ( "context" + "fmt" + "os" "strings" "github.com/sdcio/data-server/pkg/config" - "github.com/sdcio/data-server/pkg/types" + "github.com/sdcio/data-server/pkg/tree/importer" + "github.com/sdcio/data-server/pkg/tree/tree_persist" + "github.com/sdcio/data-server/pkg/tree/types" sdcpb "github.com/sdcio/sdc-protos/sdcpb" ) @@ -14,6 +18,10 @@ type RootEntry struct { *sharedEntryAttributes } +var ( + ErrorIntentNotPresent = fmt.Errorf("intent not present") +) + // NewTreeRoot Instantiate a new Tree Root element. func NewTreeRoot(ctx context.Context, tc *TreeContext) (*RootEntry, error) { sea, err := newSharedEntryAttributes(ctx, nil, "", tc) @@ -33,6 +41,12 @@ func NewTreeRoot(ctx context.Context, tc *TreeContext) (*RootEntry, error) { return root, nil } +// stringToDisk just for debugging purpose +func (r *RootEntry) stringToDisk(filename string) error { + err := os.WriteFile(filename, []byte(r.String()), 0755) + return err +} + func (r *RootEntry) DeepCopy(ctx context.Context) (*RootEntry, error) { tc := r.treeContext.deepCopy() se, err := r.sharedEntryAttributes.deepCopy(tc, nil) @@ -48,10 +62,10 @@ func (r *RootEntry) DeepCopy(ctx context.Context) (*RootEntry, error) { return result, nil } -func (r *RootEntry) AddCacheUpdatesRecursive(ctx context.Context, us UpdateSlice, flags *UpdateInsertFlags) error { +func (r *RootEntry) AddUpdatesRecursive(ctx context.Context, us types.UpdateSlice, flags *types.UpdateInsertFlags) error { var err error for _, u := range us { - _, err = r.sharedEntryAttributes.AddCacheUpdateRecursive(ctx, u, flags) + _, err = r.sharedEntryAttributes.AddUpdateRecursive(ctx, u, flags) if err != nil { return err } @@ -59,26 +73,15 @@ func (r *RootEntry) AddCacheUpdatesRecursive(ctx context.Context, us UpdateSlice return nil } -func (r *RootEntry) LoadIntendedStoreOwnerData(ctx context.Context, owner string, deleteOnlyIntended bool) (UpdateSlice, error) { - tc := r.getTreeContext() +func (r *RootEntry) ImportConfig(ctx context.Context, path types.PathSlice, importer importer.ImportConfigAdapter, intentName string, intentPrio int32, flags *types.UpdateInsertFlags) error { + r.treeContext.SetActualOwner(intentName) - // Get all entries of the already existing intent - ownerCacheEntries := tc.GetTreeSchemaCacheClient().ReadUpdatesOwner(ctx, owner) - - flags := NewUpdateInsertFlags() - - // add all the existing entries - for _, entry := range ownerCacheEntries { - _, err := r.AddCacheUpdateRecursive(ctx, entry, flags) - if err != nil { - return nil, err - } + e, err := r.sharedEntryAttributes.getOrCreateChilds(ctx, path) + if err != nil { + return err } - // Mark all the entries that belong to the owner / intent as deleted. - // This is to allow for intent updates. We mark all existing entries for deletion up front. - r.markOwnerDelete(owner, deleteOnlyIntended) - return ownerCacheEntries, nil + return e.ImportConfig(ctx, importer, intentName, intentPrio, flags) } func (r *RootEntry) Validate(ctx context.Context, vCfg *config.Validation) types.ValidationResults { @@ -111,25 +114,25 @@ func (r *RootEntry) String() string { } // GetUpdatesForOwner returns the updates that have been calculated for the given intent / owner -func (r *RootEntry) GetUpdatesForOwner(owner string) UpdateSlice { +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 LeafEntriesToCacheUpdates(r.getByOwnerFiltered(owner, FilterNonDeletedButNewOrUpdated)) + return LeafEntriesToUpdates(r.getByOwnerFiltered(owner, FilterNonDeletedButNewOrUpdated)) } // GetDeletesForOwner returns the deletes that have been calculated for the given intent / owner -func (r *RootEntry) GetDeletesForOwner(owner string) PathSlices { +func (r *RootEntry) GetDeletesForOwner(owner string) types.PathSlices { // retrieve all entries from the tree that belong to the given user // 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 := LeafEntriesToCacheUpdates(r.getByOwnerFiltered(owner, FilterDeleted)) + deletesOwnerUpdates := LeafEntriesToUpdates(r.getByOwnerFiltered(owner, FilterDeleted)) // they are retrieved as cache.update, we just need the path for deletion from cache - deletesOwner := make(PathSlices, 0, len(deletesOwnerUpdates)) + deletesOwner := make(types.PathSlices, 0, len(deletesOwnerUpdates)) // so collect the paths for _, d := range deletesOwnerUpdates { - deletesOwner = append(deletesOwner, d.GetPath()) + deletesOwner = append(deletesOwner, d.GetPathSlice()) } return deletesOwner } @@ -138,24 +141,38 @@ func (r *RootEntry) GetDeletesForOwner(owner string) PathSlices { // 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) + return r.sharedEntryAttributes.GetHighestPrecedence(make(LeafVariantSlice, 0), onlyNewOrUpdated, 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) (DeleteEntriesList, error) { - deletes := []DeleteEntry{} +func (r *RootEntry) GetDeletes(aggregatePaths bool) (types.DeleteEntriesList, error) { + deletes := []types.DeleteEntry{} return r.sharedEntryAttributes.GetDeletes(deletes, aggregatePaths) } -// getTreeContext returns the handle to the TreeContext -func (r *RootEntry) getTreeContext() *TreeContext { - return r.treeContext -} - func (r *RootEntry) GetAncestorSchema() (*sdcpb.SchemaElem, int) { return nil, 0 } +func (r *RootEntry) GetDeviations(ch chan<- *types.DeviationEntry) { + r.sharedEntryAttributes.GetDeviations(ch, true) +} + +func (r *RootEntry) TreeExport(owner string, priority int32) (*tree_persist.Intent, error) { + te, err := r.sharedEntryAttributes.TreeExport(owner) + if err != nil { + return nil, err + } + if te != nil { + return &tree_persist.Intent{ + IntentName: owner, + Root: te[0], + Priority: priority, + }, 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 ...LeafEntryFilter) []*LeafEntry { @@ -177,37 +194,8 @@ NEXTELEMENT: return result } -type DeleteEntry interface { - SdcpbPath() (*sdcpb.Path, error) - Path() PathSlice -} - -// DeleteEntryImpl is a crutch to flag oldbestcases if on a choice, the active case changed -type DeleteEntryImpl struct { - sdcpbPath *sdcpb.Path - pathslice PathSlice -} - -func NewDeleteEntryImpl(sdcpbPath *sdcpb.Path, pathslice PathSlice) *DeleteEntryImpl { - return &DeleteEntryImpl{ - sdcpbPath: sdcpbPath, - pathslice: pathslice, +func (r *RootEntry) DeleteSubtreePaths(deletes types.DeleteEntriesList, intentName string) { + for _, del := range deletes { + r.DeleteSubtree(del.Path(), intentName) } } - -func (d *DeleteEntryImpl) SdcpbPath() (*sdcpb.Path, error) { - return d.sdcpbPath, nil -} -func (d *DeleteEntryImpl) Path() PathSlice { - return d.pathslice -} - -type DeleteEntriesList []DeleteEntry - -func (d DeleteEntriesList) PathSlices() PathSlices { - result := make(PathSlices, 0, len(d)) - for _, del := range d { - result = append(result, del.Path()) - } - return result -} diff --git a/pkg/tree/sharedEntryAttributes.go b/pkg/tree/sharedEntryAttributes.go index ea8d1af2..eab9ade6 100644 --- a/pkg/tree/sharedEntryAttributes.go +++ b/pkg/tree/sharedEntryAttributes.go @@ -11,13 +11,13 @@ import ( "sync" "unicode/utf8" - "github.com/sdcio/data-server/pkg/cache" "github.com/sdcio/data-server/pkg/config" "github.com/sdcio/data-server/pkg/tree/importer" - "github.com/sdcio/data-server/pkg/types" + "github.com/sdcio/data-server/pkg/tree/tree_persist" + "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils" sdcpb "github.com/sdcio/sdc-protos/sdcpb" - "google.golang.org/protobuf/proto" + log "github.com/sirupsen/logrus" "google.golang.org/protobuf/types/known/emptypb" ) @@ -37,15 +37,16 @@ type sharedEntryAttributes struct { schema *sdcpb.SchemaElem schemaMutex sync.RWMutex - choicesResolvers choiceCasesResolvers + choicesResolvers choiceResolvers treeContext *TreeContext // state cache + cacheMutex sync.Mutex cacheShouldDelete *bool cacheCanDelete *bool cacheRemains *bool - cacheMutex sync.Mutex + level *int } func (s *sharedEntryAttributes) deepCopy(tc *TreeContext, parent Entry) (*sharedEntryAttributes, error) { @@ -57,60 +58,13 @@ func (s *sharedEntryAttributes) deepCopy(tc *TreeContext, parent Entry) (*shared schema: s.schema, treeContext: tc, choicesResolvers: s.choicesResolvers.deepCopy(), + childsMutex: sync.RWMutex{}, + schemaMutex: sync.RWMutex{}, + cacheMutex: sync.Mutex{}, + level: s.level, } - return result, nil -} - -type childMap struct { - c map[string]Entry - mu sync.RWMutex -} - -func newChildMap() *childMap { - return &childMap{ - c: map[string]Entry{}, - } -} - -func (c *childMap) Add(e Entry) { - c.mu.Lock() - defer c.mu.Unlock() - c.c[e.PathName()] = e -} - -func (c *childMap) GetEntry(s string) (Entry, bool) { - c.mu.RLock() - defer c.mu.RUnlock() - e, exists := c.c[s] - return e, exists -} - -func (c *childMap) GetAll() map[string]Entry { - c.mu.RLock() - defer c.mu.RUnlock() - - result := map[string]Entry{} - 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) + return result, nil } func newSharedEntryAttributes(ctx context.Context, parent Entry, pathElemName string, tc *TreeContext) (*sharedEntryAttributes, error) { @@ -150,7 +104,7 @@ func (s *sharedEntryAttributes) GetRoot() Entry { // loadDefaults helper to populate defaults on the initializiation of the sharedEntryAttribute func (s *sharedEntryAttributes) loadDefaults(ctx context.Context) error { - // if it is a container wihtout keys (not a list) then load the defaults + // if it is a container without keys (not a list) then load the defaults if s.schema.GetContainer() != nil && len(s.schema.GetContainer().GetKeys()) == 0 { for _, childname := range s.schema.GetContainer().ChildsWithDefaults { // tryLoadingDefaults is using the pathslice, that contains keys as well, @@ -198,6 +152,85 @@ func (s *sharedEntryAttributes) loadDefaults(ctx context.Context) error { return nil } +func (s *sharedEntryAttributes) GetDeviations(ch chan<- *types.DeviationEntry, activeCase bool) { + s.leafVariants.GetDeviations(ch, activeCase) + + // get all active childs + activeChilds := s.filterActiveChoiceCaseChilds() + + // 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(ch, isActiveChild) + } +} + +func (s *sharedEntryAttributes) checkAndCreateKeysAsLeafs(ctx context.Context, intentName string, prio int32) 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 { + // iterate through the keys + for idx, k := range ancestor.GetSchemaKeys() { + child, entryExists := s.childs.GetEntry(k) + // 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 + lvs := child.GetByOwner(intentName, result) + if len(lvs) > 0 { + continue + } + } + // construct the key path + keyPath := append(s.Path(), k) + + schem, err := s.treeContext.schemaClient.GetSchemaSlicePath(ctx, keyPath) + if err != nil { + return err + } + // convert the key value to the schema defined Typed_Value + tv, err := utils.Convert(keyPath[len(keyPath)-levelsUp-1+idx], schem.Schema.GetField().Type) + if err != nil { + return err + } + if !entryExists { + // create a new entry + child, err = newEntry(ctx, s, k, s.treeContext) + if err != nil { + return err + } + // add the new child entry to s + err = s.addChild(ctx, child) + if err != nil { + return err + } + } + _, err = child.AddUpdateRecursive(ctx, types.NewUpdate(keyPath, tv, prio, intentName, 0), types.NewUpdateInsertFlags()) + return err + + } + } + return nil +} + func (s *sharedEntryAttributes) populateSchema(ctx context.Context) error { s.schemaMutex.Lock() defer s.schemaMutex.Unlock() @@ -252,6 +285,36 @@ func (s *sharedEntryAttributes) getChildren() map[string]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) { + if s.schema == nil { + return nil, fmt.Errorf("error GetListChilds() non schema level %s", s.Path().String()) + } + if s.schema.GetContainer() == nil { + return nil, fmt.Errorf("error GetListChilds() not a Container %s", s.Path().String()) + } + keys := s.schema.GetContainer().GetKeys() + if len(keys) == 0 { + return nil, fmt.Errorf("error GetListChilds() not a List Container %s", s.Path().String()) + } + actualEntries := []Entry{s} + var newEntries []Entry + + for level := 0; level < len(keys); level++ { + for _, e := range actualEntries { + // add all children + for _, c := range e.getChildren() { + newEntries = append(newEntries, c) + } + } + actualEntries = newEntries + newEntries = []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 (*) @@ -314,7 +377,19 @@ func (s *sharedEntryAttributes) IsRoot() bool { // GetLevel returns the level / depth position of this element in the tree func (s *sharedEntryAttributes) GetLevel() int { - return len(s.Path()) + // if level is cached, return level + if s.level != nil { + return *s.level + } + // if we're at the root level, return 0 + if s.parent == nil { + return 0 + } + // Get parent level and add 1 + l := s.parent.GetLevel() + 1 + // cache level value + s.level = &l + return l } // Walk takes the EntryVisitor and applies it to every Entry in the tree @@ -358,7 +433,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 []DeleteEntry, aggregatePaths bool) ([]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 @@ -446,8 +521,15 @@ func (s *sharedEntryAttributes) shouldDelete() bool { // but a real delete should only be added if there is at least one shouldDelete() == true shouldDelete := false + activeChilds := s.filterActiveChoiceCaseChilds() + // if we have no active childs, we can and should delete. + if len(s.choicesResolvers) > 0 && len(activeChilds) == 0 { + canDelete = true + shouldDelete = true + } + // iterate through the active childs - for _, c := range s.filterActiveChoiceCaseChilds() { + for _, c := range activeChilds { // check if the child can be deleted canDelete = c.canDelete() // if it can explicitly not be deleted, then the result is clear, we should not delete @@ -492,56 +574,65 @@ func (s *sharedEntryAttributes) remainsToExist() bool { break } } - activeChoiceCase := false - // only needs to be checked if it still looks like there - // it is to be deleted - if !childsRemain { - activeChoiceCase = s.choicesResolvers.remainsToExist() - } // assumption is, that if the entry exists, there is at least a running value available. - remains := leafVariantResult || childsRemain || activeChoiceCase + remains := leafVariantResult || childsRemain s.cacheRemains = &remains return remains } // getRegularDeletes performs deletion calculation on elements that have a schema attached. -func (s *sharedEntryAttributes) getRegularDeletes(deletes []DeleteEntry, aggregate bool) ([]DeleteEntry, error) { +func (s *sharedEntryAttributes) getRegularDeletes(deletes []types.DeleteEntry, aggregate bool) ([]types.DeleteEntry, error) { var err error // if entry is a container type, check the keys, to be able to // issue a delte for the whole branch at once via keys - switch s.schema.GetSchema().(type) { - case *sdcpb.SchemaElem_Container: - - // deletes for child elements (choice cases) that newly became inactive. - for _, v := range s.choicesResolvers { - oldBestCaseName := v.getOldBestCaseName() - newBestCaseName := v.getBestCaseName() - // so if we have an old and a new best cases (not "") and the names are different, - // all the old to the deletion list - if oldBestCaseName != "" && newBestCaseName != "" && oldBestCaseName != newBestCaseName { - // try fetching the case from the childs - oldBestCaseEntry, exists := s.childs.GetEntry(oldBestCaseName) - if exists { - deletes = append(deletes, oldBestCaseEntry) - } else { - // it might be that the child is not loaded into the tree, but just considered from the treecontext cache for the choice/case resolution - // if so, we create and return the DeleteEntryImpl struct - path, err := s.SdcpbPath() - if err != nil { - return nil, err - } - deletes = append(deletes, NewDeleteEntryImpl(path, append(s.Path(), oldBestCaseName))) - } - } - } - } + // switch s.schema.GetSchema().(type) { + // case *sdcpb.SchemaElem_Container: + // // deletes for child elements (choice cases) that newly became inactive. + // for _, v := range s.choicesResolvers { + // // oldBestCaseName := v.getOldBestCaseName() + // // newBestCaseName := v.getBestCaseName() + // // // so if we have an old and a new best cases (not "") and the names are different, + // // // all the old to the deletion list + // // if oldBestCaseName != "" && newBestCaseName != "" && oldBestCaseName != newBestCaseName { + // // // try fetching the case from the childs + // // oldBestCaseEntry, exists := s.childs.GetEntry(oldBestCaseName) + // // if exists { + // // deletes = append(deletes, oldBestCaseEntry) + // // } else { + // // // it might be that the child is not loaded into the tree, but just considered from the treecontext cache for the choice/case resolution + // // // if so, we create and return the DeleteEntryImpl struct + // // path, err := s.SdcpbPath() + // // if err != nil { + // // return nil, err + // // } + // // deletes = append(deletes, types.NewDeleteEntryImpl(path, append(s.Path(), oldBestCaseName))) + // // } + // // } + // path, err := s.SdcpbPath() + // if err != nil { + // return nil, err + // } + // deleteElements := v.GetDeletes() + // for _, de := range deleteElements { + // deletes = append(deletes, types.NewDeleteEntryImpl(path, append(s.Path(), de))) + // } + // } + // } if s.shouldDelete() && !s.IsRoot() && len(s.GetSchemaKeys()) == 0 { return append(deletes, s), nil } + for _, elem := range s.choicesResolvers.GetDeletes() { + path, err := s.SdcpbPath() + if err != nil { + return nil, err + } + deletes = append(deletes, types.NewDeleteEntryImpl(path, append(s.Path(), elem))) + } + for _, e := range s.childs.GetAll() { deletes, err = e.GetDeletes(deletes, aggregate) if err != nil { @@ -552,7 +643,7 @@ func (s *sharedEntryAttributes) getRegularDeletes(deletes []DeleteEntry, aggrega } // GetDeletes calculate the deletes that need to be send to the device. -func (s *sharedEntryAttributes) GetDeletes(deletes []DeleteEntry, aggregatePaths bool) ([]DeleteEntry, error) { +func (s *sharedEntryAttributes) GetDeletes(deletes []types.DeleteEntry, aggregatePaths bool) ([]types.DeleteEntry, error) { // if the actual level has no schema assigned we're on a key level // element. Hence we try deletion via aggregation @@ -585,7 +676,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) []*LeafEntry { +func (s *sharedEntryAttributes) GetByOwner(owner string, result []*LeafEntry) LeafVariantSlice { lv := s.leafVariants.GetByOwner(owner) if lv != nil { result = append(result, lv) @@ -599,10 +690,10 @@ func (s *sharedEntryAttributes) GetByOwner(owner string, result []*LeafEntry) [] } // Path returns the root based path of the Entry -func (s *sharedEntryAttributes) Path() PathSlice { +func (s *sharedEntryAttributes) Path() types.PathSlice { // special handling for root node if s.parent == nil { - return PathSlice{} + return types.PathSlice{} } return append(s.parent.Path(), s.pathElemName) } @@ -662,16 +753,6 @@ func (s *sharedEntryAttributes) NavigateSdcpbPath(ctx context.Context, pathElems return entry.NavigateSdcpbPath(ctx, pathElems[1:], false) default: e, exists := s.filterActiveChoiceCaseChilds()[pathElems[0].Name] - if !exists { - e, err = s.tryLoading(ctx, []string{pathElems[0].Name}) - if err != nil { - return nil, err - } - if e != nil { - exists = true - } - } - if !exists { pth := &sdcpb.Path{Elem: pathElems} e, err = s.tryLoadingDefault(ctx, utils.ToStrings(pth, false, false)) @@ -683,7 +764,7 @@ func (s *sharedEntryAttributes) NavigateSdcpbPath(ctx context.Context, pathElems } for _, v := range pathElems[0].Key { - e, err = e.Navigate(ctx, []string{v}, false) + e, err = e.Navigate(ctx, []string{v}, false, false) if err != nil { return nil, err } @@ -702,14 +783,14 @@ func (s *sharedEntryAttributes) tryLoadingDefault(ctx context.Context, path []st return nil, fmt.Errorf("error trying to load defaults for %s: %v", strings.Join(path, "->"), err) } - upd, err := utils.DefaultValueRetrieve(schema.GetSchema(), path, DefaultValuesPrio, DefaultsIntentName) + upd, err := DefaultValueRetrieve(schema.GetSchema(), path, DefaultValuesPrio, DefaultsIntentName) if err != nil { return nil, err } - flags := NewUpdateInsertFlags() + flags := types.NewUpdateInsertFlags() - result, err := s.AddCacheUpdateRecursive(ctx, upd, flags) + result, err := s.AddUpdateRecursive(ctx, upd, flags) if err != nil { return nil, fmt.Errorf("failed adding default value for %s to tree; %v", strings.Join(path, "/"), err) } @@ -718,28 +799,33 @@ func (s *sharedEntryAttributes) tryLoadingDefault(ctx context.Context, path []st } // Navigate move through the tree, returns the Entry that is present under the given path -func (s *sharedEntryAttributes) Navigate(ctx context.Context, path []string, isRootPath bool) (Entry, error) { +func (s *sharedEntryAttributes) Navigate(ctx context.Context, path []string, isRootPath bool, dotdotSkipKeys bool) (Entry, error) { if len(path) == 0 { return s, nil } if isRootPath { - return s.GetRoot().Navigate(ctx, path, false) + return s.treeContext.root.Navigate(ctx, path, false, dotdotSkipKeys) } var err error switch path[0] { case ".": - return s.Navigate(ctx, path[1:], false) + return s.Navigate(ctx, path[1:], false, dotdotSkipKeys) case "..": - return s.parent.Navigate(ctx, path[1:], false) - default: - e, exists := s.filterActiveChoiceCaseChilds()[path[0]] - if !exists { - e, _ = s.tryLoading(ctx, append(s.Path(), path...)) - if e != nil { - exists = true + parent := s.parent + if dotdotSkipKeys { + // if dotdotSkipKeys is set, we need to advance to the next schema level above + // the issue is, that if there is a list, with keys, the last element even without a schema defined + // is the element to stop at. + // so here we need to check is the parent schema is nil and that we still want to move further up. + // if thats the case, move up to the next schema carrying element. + if parent.GetSchema() == nil && len(path) > 0 && path[1] == ".." { + parent, _ = parent.GetFirstAncestorWithSchema() } } + return parent.Navigate(ctx, path[1:], false, dotdotSkipKeys) + default: + e, exists := s.filterActiveChoiceCaseChilds()[path[0]] if !exists { e, err = s.tryLoadingDefault(ctx, append(s.Path(), path...)) if err != nil { @@ -747,41 +833,55 @@ func (s *sharedEntryAttributes) Navigate(ctx context.Context, path []string, isR } return e, nil } - return e.Navigate(ctx, path[1:], false) + return e.Navigate(ctx, path[1:], false, dotdotSkipKeys) } } -func (s *sharedEntryAttributes) tryLoading(ctx context.Context, path []string) (Entry, error) { - upd, err := s.treeContext.GetTreeSchemaCacheClient().ReadRunningPath(ctx, append(s.Path(), path...)) - if err != nil { - return nil, err - } - if upd == nil { - return nil, fmt.Errorf("reached %v but child %s does not exist", s.Path(), path[0]) +func (s *sharedEntryAttributes) DeleteSubtree(relativePath types.PathSlice, owner string) (bool, error) { + if len(relativePath) > 0 { + child, exists := s.childs.GetEntry(relativePath[0]) + if !exists { + path := make([]string, 0, len(s.Path())+len(relativePath)) + path = append(path, s.Path()...) + path = append(path, relativePath...) + return false, fmt.Errorf("trying to delete subtree %q but unable to find child %s at %s", path, relativePath[0], s.Path()) + } + remainingPath := relativePath[1:] + return child.DeleteSubtree(remainingPath, owner) } - flags := NewUpdateInsertFlags() + remainsToExist := false + // delete possibly existing leafvariants for the owner + remainsToExist = s.leafVariants.DeleteByOwner(owner) - _, err = s.treeContext.root.AddCacheUpdateRecursive(ctx, upd, flags) - if err != nil { - return nil, err + deleteKeys := []string{} + // recurse the call + for childName, child := range s.childs.Items() { + childRemains, err := child.DeleteSubtree(nil, owner) + if err != nil { + return false, err + } + if !childRemains { + deleteKeys = append(deleteKeys, childName) + } + remainsToExist = remainsToExist || childRemains } - - e, _ := s.childs.GetEntry(path[0]) - return e, nil + // finally delete the childs + s.childs.DeleteChilds(deleteKeys) + return remainsToExist, 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 LeafVariantSlice, onlyNewOrUpdated bool) LeafVariantSlice { +func (s *sharedEntryAttributes) GetHighestPrecedence(result LeafVariantSlice, onlyNewOrUpdated bool, includeDefaults bool) LeafVariantSlice { // get the highes precedence LeafeVariant and add it to the list - lv := s.leafVariants.GetHighestPrecedence(onlyNewOrUpdated, false) + lv := s.leafVariants.GetHighestPrecedence(onlyNewOrUpdated, includeDefaults) 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.filterActiveChoiceCaseChilds() { - result = c.GetHighestPrecedence(result, onlyNewOrUpdated) + result = c.GetHighestPrecedence(result, onlyNewOrUpdated, includeDefaults) } return result } @@ -803,22 +903,35 @@ func (s *sharedEntryAttributes) getHighestPrecedenceLeafValue(ctx context.Contex } func (s *sharedEntryAttributes) GetRootBasedEntryChain() []Entry { + s.GetLevel() if s.IsRoot() { return []Entry{} } return append(s.parent.GetRootBasedEntryChain(), s) } +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() int32 { +func (s *sharedEntryAttributes) getHighestPrecedenceValueOfBranch(filter HighestPrecedenceFilter) int32 { result := int32(math.MaxInt32) for _, e := range s.childs.GetAll() { - if val := e.getHighestPrecedenceValueOfBranch(); val < result { + if val := e.getHighestPrecedenceValueOfBranch(filter); val < result { result = val } } - if val := s.leafVariants.GetHighestPrecedenceValue(); val < result { + if val := s.leafVariants.GetHighestPrecedenceValue(filter); val < result { result = val } @@ -888,11 +1001,7 @@ func (s *sharedEntryAttributes) validateRange(resultChan chan<- *types.Validatio return } - tv, err := lv.Update.Value() - if err != nil { - resultChan <- types.NewValidationResultEntry(lv.Owner(), fmt.Errorf("path %s, error validating ranges: %w", s.Path(), err), types.ValidationResultEntryTypeError) - return - } + tv := lv.Update.Value() var tvs []*sdcpb.TypedValue var typeSchema *sdcpb.SchemaLeafType @@ -960,10 +1069,8 @@ func (s *sharedEntryAttributes) validateLeafListMinMaxAttributes(resultChan chan if schema := s.schema.GetLeaflist(); schema != nil { if schema.MinElements > 0 { if lv := s.leafVariants.GetHighestPrecedence(false, true); lv != nil { - tv, err := lv.Update.Value() - if err != nil { - resultChan <- types.NewValidationResultEntry(lv.Owner(), fmt.Errorf("validating LeafList Min Attribute: %v", err), types.ValidationResultEntryTypeError) - } + tv := lv.Update.Value() + if val := tv.GetLeaflistVal(); val != nil { // check minelements if set if schema.MinElements > 0 && len(val.GetElement()) < int(schema.GetMinElements()) { @@ -990,13 +1097,7 @@ func (s *sharedEntryAttributes) validateLength(resultChan chan<- *types.Validati if lv == nil { return } - - tv, err := lv.Value() - if err != nil { - resultChan <- types.NewValidationResultEntry(lv.Owner(), fmt.Errorf("failed reading value from %s LeafVariant %v: %w", s.Path(), lv, err), types.ValidationResultEntryTypeError) - return - } - value := tv.GetStringVal() + value := lv.Value().GetStringVal() actualLength := utf8.RuneCountInString(value) for _, lengthDef := range schema.GetType().Length { @@ -1018,12 +1119,7 @@ func (s *sharedEntryAttributes) validatePattern(resultChan chan<- *types.Validat return } lv := s.leafVariants.GetHighestPrecedence(false, true) - tv, err := lv.Update.Value() - if err != nil { - resultChan <- types.NewValidationResultEntry(lv.Owner(), fmt.Errorf("failed reading value from %s LeafVariant %v: %w", s.Path(), lv, err), types.ValidationResultEntryTypeError) - return - } - value := tv.GetStringVal() + value := lv.Value().GetStringVal() for _, pattern := range schema.Type.Patterns { if p := pattern.GetPattern(); p != "" { matched, err := regexp.MatchString(p, value) @@ -1039,14 +1135,9 @@ func (s *sharedEntryAttributes) validatePattern(resultChan chan<- *types.Validat } } -func (s *sharedEntryAttributes) ImportConfig(ctx context.Context, t importer.ImportConfigAdapter, intentName string, intentPrio int32) error { +func (s *sharedEntryAttributes) ImportConfig(ctx context.Context, t importer.ImportConfigAdapter, intentName string, intentPrio int32, insertFlags *types.UpdateInsertFlags) error { var err error - updateInsertFlags := NewUpdateInsertFlags() - if intentName != RunningIntentName { - updateInsertFlags.SetNewFlag() - } - switch x := s.schema.GetSchema().(type) { case *sdcpb.SchemaElem_Container, nil: switch { @@ -1073,7 +1164,7 @@ func (s *sharedEntryAttributes) ImportConfig(ctx context.Context, t importer.Imp } actualEntry = keyChild } - err = actualEntry.ImportConfig(ctx, t, intentName, intentPrio) + err = actualEntry.ImportConfig(ctx, t, intentName, intentPrio, insertFlags) if err != nil { return err } @@ -1086,12 +1177,8 @@ func (s *sharedEntryAttributes) ImportConfig(ctx context.Context, t importer.Imp } if schem.IsPresence { tv := &sdcpb.TypedValue{Value: &sdcpb.TypedValue_EmptyVal{EmptyVal: &emptypb.Empty{}}} - tvVal, err := proto.Marshal(tv) - if err != nil { - return err - } - upd := cache.NewUpdate(s.Path(), tvVal, intentPrio, intentName, 0) - s.leafVariants.Add(NewLeafEntry(upd, updateInsertFlags, s)) + upd := types.NewUpdate(s.Path(), tv, intentPrio, intentName, 0) + s.leafVariants.Add(NewLeafEntry(upd, insertFlags, s)) } } @@ -1110,7 +1197,7 @@ func (s *sharedEntryAttributes) ImportConfig(ctx context.Context, t importer.Imp return err } } - err = child.ImportConfig(ctx, elem, intentName, intentPrio) + err = child.ImportConfig(ctx, elem, intentName, intentPrio, insertFlags) if err != nil { return err } @@ -1127,27 +1214,18 @@ func (s *sharedEntryAttributes) ImportConfig(ctx context.Context, t importer.Imp if err != nil { return err } - tvVal, err := proto.Marshal(tv) - if err != nil { - return err - } - upd := cache.NewUpdate(s.Path(), tvVal, intentPrio, intentName, 0) + upd := types.NewUpdate(s.Path(), tv, intentPrio, intentName, 0) - s.leafVariants.Add(NewLeafEntry(upd, updateInsertFlags, s)) + s.leafVariants.Add(NewLeafEntry(upd, insertFlags, s)) case *sdcpb.SchemaElem_Leaflist: var scalarArr *sdcpb.ScalarArray mustAdd := false le := s.leafVariants.GetByOwner(intentName) if le != nil { - llvTv, err := le.Update.Value() - if err != nil { - return err - } - - scalarArr = llvTv.GetLeaflistVal() + scalarArr = le.Value().GetLeaflistVal() } else { - le = NewLeafEntry(nil, updateInsertFlags, s) + le = NewLeafEntry(nil, insertFlags, s) mustAdd = true scalarArr = &sdcpb.ScalarArray{Element: []*sdcpb.TypedValue{}} } @@ -1156,14 +1234,14 @@ func (s *sharedEntryAttributes) ImportConfig(ctx context.Context, t importer.Imp if err != nil { return err } - scalarArr.Element = append(scalarArr.Element, tv) - tvVal, err := proto.Marshal(&sdcpb.TypedValue{Value: &sdcpb.TypedValue_LeaflistVal{LeaflistVal: scalarArr}}) - if err != nil { - return err + // the proto implementation will return leaflist tvs + if tv.GetLeaflistVal() == nil { + scalarArr.Element = append(scalarArr.Element, tv) + tv = &sdcpb.TypedValue{Value: &sdcpb.TypedValue_LeaflistVal{LeaflistVal: scalarArr}} } - le.Update = cache.NewUpdate(s.Path(), tvVal, intentPrio, intentName, 0) + le.Update = types.NewUpdate(s.Path(), tv, intentPrio, intentName, 0) if mustAdd { s.leafVariants.Add(le) } @@ -1178,37 +1256,73 @@ func (s *sharedEntryAttributes) validateMandatory(ctx context.Context, resultCha switch s.schema.GetSchema().(type) { case *sdcpb.SchemaElem_Container: for _, c := range s.schema.GetContainer().GetMandatoryChildrenConfig() { - s.validateMandatoryWithKeys(ctx, len(s.GetSchema().GetContainer().GetKeys()), c.Name, resultChan) + attributes := []string{} + choiceName := "" + // check if it is a ChildContainer + if slices.Contains(s.schema.GetContainer().GetChildren(), c.Name) { + attributes = append(attributes, c.Name) + } + + // check if it is a Field + if slices.ContainsFunc(s.schema.GetContainer().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 := s.schema.GetContainer().GetChoiceInfo().GetChoiceByName(c.Name) + if choice != nil { + attributes = append(attributes, choice.GetAllAttributes()...) + choiceName = c.Name + } + } + + if len(attributes) == 0 { + log.Errorf("error path: %s, validationg mandatory attribute %s could not be found as child, field or choice.", s.Path(), c.Name) + } + + s.validateMandatoryWithKeys(ctx, len(s.GetSchema().GetContainer().GetKeys()), attributes, choiceName, resultChan) } } } } -func (s *sharedEntryAttributes) validateMandatoryWithKeys(ctx context.Context, level int, attribute string, resultChan chan<- *types.ValidationResultEntry) { +// 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 level == 0 { - // first check if the mandatory value is set via the intent, e.g. part of the tree already - v, existsInTree := s.filterActiveChoiceCaseChilds()[attribute] - + success := false + existsInTree := false + var v 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.filterActiveChoiceCaseChilds()[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 !(existsInTree && v.remainsToExist()) { - exists, err := s.treeContext.cacheClient.IntendedPathExists(ctx, append(s.Path(), attribute)) - owner := "unknown" - if s.leafVariants.Length() > 0 { - s.leafVariants.GetHighestPrecedence(false, true).Owner() - } - if err != nil { - resultChan <- types.NewValidationResultEntry(owner, fmt.Errorf("error validating mandatory childs %s: %v", s.Path(), err), types.ValidationResultEntryTypeError) - } - if !exists { - resultChan <- types.NewValidationResultEntry(owner, fmt.Errorf("error mandatory child %s does not exist, path: %s", attribute, s.Path()), types.ValidationResultEntryTypeError) + 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.Path()), types.ValidationResultEntryTypeError) } + // 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.Path()), types.ValidationResultEntryTypeError) } return } for _, c := range s.filterActiveChoiceCaseChilds() { - c.validateMandatoryWithKeys(ctx, level-1, attribute, resultChan) + c.validateMandatoryWithKeys(ctx, level-1, attributes, choiceName, resultChan) } } @@ -1232,7 +1346,7 @@ func (s *sharedEntryAttributes) initChoiceCasesResolvers() { } // create a new choiceCasesResolvers struct - choicesResolvers := choiceCasesResolvers{} + choicesResolvers := choiceResolvers{} // iterate through choices defined in schema for choiceName, choice := range ci.GetChoice() { @@ -1251,15 +1365,21 @@ func (s *sharedEntryAttributes) initChoiceCasesResolvers() { // FinishInsertionPhase certain values that are costly to calculate but used multiple times // will be calculated and stored for later use. However therefore the insertion phase into the // tree needs to be over. Calling this function indicated the end of the phase and thereby triggers the calculation -func (s *sharedEntryAttributes) FinishInsertionPhase(ctx context.Context) { +func (s *sharedEntryAttributes) FinishInsertionPhase(ctx context.Context) error { // populate the ChoiceCaseResolvers to determine the active case - s.populateChoiceCaseResolvers(ctx) + err := s.populateChoiceCaseResolvers(ctx) + if err != nil { + return err + } // recurse the call to all (active) entries within the tree. // Thereby already using the choiceCaseResolver via filterActiveChoiceCaseChilds() for _, child := range s.filterActiveChoiceCaseChilds() { - child.FinishInsertionPhase(ctx) + err = child.FinishInsertionPhase(ctx) + if err != nil { + return err + } } // reset state @@ -1268,6 +1388,8 @@ func (s *sharedEntryAttributes) FinishInsertionPhase(ctx context.Context) { s.cacheRemains = nil s.cacheShouldDelete = nil s.cacheCanDelete = nil + + return nil } // populateChoiceCaseResolvers iterates through the ChoiceCaseResolvers, @@ -1276,32 +1398,43 @@ func (s *sharedEntryAttributes) FinishInsertionPhase(ctx context.Context) { // caches index (old intent content) as well as from the tree (new intent content). // the choiceResolver is fed with the resulting values and thereby ready to be queried // in a later stage (filterActiveChoiceCaseChilds()). -func (s *sharedEntryAttributes) populateChoiceCaseResolvers(ctx context.Context) { +func (s *sharedEntryAttributes) populateChoiceCaseResolvers(_ context.Context) error { if s.schema == nil { - return + return nil } // if choice/cases exist, process it for _, choiceResolver := range s.choicesResolvers { for _, elem := range choiceResolver.GetElementNames() { - isNew := false - var val2 *int32 - // Query the Index, stored in the treeContext for the per branch highes precedence - v := s.treeContext.GetTreeSchemaCacheClient().GetBranchesHighesPrecedence(ctx, append(s.Path(), elem), CacheUpdateFilterExcludeOwner(s.treeContext.GetActualOwner())) + isDeleted := false + highestWDeleted := int32(math.MaxInt32) + highestWODeleted := int32(math.MaxInt32) + highestWONew := int32(math.MaxInt32) child, childExists := s.childs.GetEntry(elem) // set the value from the tree as well if childExists { - x := child.getHighestPrecedenceValueOfBranch() - val2 = &x - } + valWDeleted := child.getHighestPrecedenceValueOfBranch(HighestPrecedenceFilterAll) + if valWDeleted <= highestWDeleted { + highestWDeleted = valWDeleted + if child.canDelete() { + isDeleted = true + } + } + + valWODeleted := child.getHighestPrecedenceValueOfBranch(HighestPrecedenceFilterWithoutDeleted) + if valWODeleted <= highestWODeleted { + highestWODeleted = valWODeleted + } + valWONew := child.getHighestPrecedenceValueOfBranch(HighestPrecedenceFilterWithoutNew) + if valWONew <= highestWONew { + highestWONew = valWONew + } - if val2 != nil && v >= *val2 { - v = *val2 - isNew = true } - choiceResolver.SetValue(elem, v, isNew) + choiceResolver.SetValue(elem, highestWODeleted, highestWDeleted, highestWONew, isDeleted) } } + return nil } // filterActiveChoiceCaseChilds returns the list of child elements. In case the Entry is @@ -1336,10 +1469,9 @@ func (s *sharedEntryAttributes) StringIndent(result []string) []string { // ranging over children and LeafVariants // then should be mutual exclusive, either a node has children or LeafVariants - // range over children - for _, c := range s.childs.GetAll() { - result = c.StringIndent(result) + for _, child := range s.childs.GetAllSorted() { + result = child.StringIndent(result) } // range over LeafVariants for l := range s.leafVariants.Items() { @@ -1349,15 +1481,11 @@ func (s *sharedEntryAttributes) StringIndent(result []string) []string { } // markOwnerDelete Sets the delete flag on all the LeafEntries belonging to the given owner. -func (s *sharedEntryAttributes) markOwnerDelete(o string, onlyIntended bool) { - lvEntry := s.leafVariants.GetByOwner(o) - // if an entry for the given user exists, mark it for deletion - if lvEntry != nil { - lvEntry.MarkDelete(onlyIntended) - } +func (s *sharedEntryAttributes) MarkOwnerDelete(o string, onlyIntended bool) { + s.leafVariants.MarkOwnerForDeletion(o, onlyIntended) // recurse into childs for _, child := range s.childs.GetAll() { - child.markOwnerDelete(o, onlyIntended) + child.MarkOwnerDelete(o, onlyIntended) } } @@ -1407,6 +1535,70 @@ func (s *sharedEntryAttributes) SdcpbPathInternal(spath []string) (*sdcpb.Path, return p, err } +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 +} + // getKeyName checks if s is a key level element in the tree, if not an error is throw // if it is a key level element, the name of the key is determined via the ancestor schemas func (s *sharedEntryAttributes) getKeyName() (string, error) { @@ -1429,33 +1621,54 @@ func (s *sharedEntryAttributes) getKeyName() (string, error) { return "", fmt.Errorf("error LeafList and Field should not have keys %s", strings.Join(s.Path(), " ")) } +func (s *sharedEntryAttributes) getOrCreateChilds(ctx context.Context, path types.PathSlice) (Entry, error) { + var err error + if len(path) == 0 { + return s, nil + } + + e, exists := s.childs.GetEntry(path[0]) + if !exists { + e, err = newEntry(ctx, s, path[0], s.treeContext) + if err != nil { + return nil, err + } + } + + return e.getOrCreateChilds(ctx, path[1:]) +} + // AddCacheUpdateRecursive 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) AddCacheUpdateRecursive(ctx context.Context, c *cache.Update, flags *UpdateInsertFlags) (Entry, error) { - idx := 0 - // if it is the root node, index remains == 0 - if s.parent != nil { - idx = s.GetLevel() +func (s *sharedEntryAttributes) AddUpdateRecursive(ctx context.Context, u *types.Update, flags *types.UpdateInsertFlags) (Entry, error) { + idx := s.GetLevel() + var err error + // make sure all the keys are also present as leafs + err = s.checkAndCreateKeysAsLeafs(ctx, u.Owner(), u.Priority()) + if err != nil { + return nil, err } + // end of path reached, add LeafEntry // continue with recursive add otherwise - if idx == len(c.GetPath()) { + if idx == len(u.GetPathSlice()) { // delegate update handling to leafVariants - s.leafVariants.Add(NewLeafEntry(c, flags, s)) + s.leafVariants.Add(NewLeafEntry(u, flags, s)) return s, nil } var e Entry - var err error + var exists bool // if child does not exist, create Entry - if e, exists = s.childs.GetEntry(c.GetPath()[idx]); !exists { - e, err = newEntry(ctx, s, c.GetPath()[idx], s.treeContext) + if e, exists = s.childs.GetEntry(u.GetPathSlice()[idx]); !exists { + e, err = newEntry(ctx, s, u.GetPathSlice()[idx], s.treeContext) if err != nil { return nil, err } + } - return e.AddCacheUpdateRecursive(ctx, c, flags) + return e.AddUpdateRecursive(ctx, u, flags) } // containsOnlyDefaults checks for presence containers, if only default values are present, @@ -1493,55 +1706,3 @@ func (s *sharedEntryAttributes) containsOnlyDefaults() bool { return true } - -type UpdateInsertFlags struct { - new bool - delete bool - onlyIntended bool -} - -// NewUpdateInsertFlags returns a new *UpdateInsertFlags instance -// with all values set to false, so not new, and not marked for deletion -func NewUpdateInsertFlags() *UpdateInsertFlags { - return &UpdateInsertFlags{} -} - -func (f *UpdateInsertFlags) SetDeleteFlag() { - f.delete = true - f.new = false -} - -func (f *UpdateInsertFlags) SetDeleteOnlyUpdatedFlag() { - f.delete = true - f.onlyIntended = true - f.new = false -} - -func (f *UpdateInsertFlags) SetNewFlag() { - f.new = true - f.delete = false - f.onlyIntended = false -} - -func (f *UpdateInsertFlags) GetDeleteFlag() bool { - return f.delete -} - -func (f *UpdateInsertFlags) GetDeleteOnlyIntendedFlag() bool { - return f.onlyIntended -} - -func (f *UpdateInsertFlags) GetNewFlag() bool { - return f.new -} - -func (f *UpdateInsertFlags) Apply(le *LeafEntry) { - if f.delete { - le.MarkDelete(f.onlyIntended) - return - } - if f.new { - le.MarkNew() - return - } -} diff --git a/pkg/tree/sharedEntryAttributes_test.go b/pkg/tree/sharedEntryAttributes_test.go new file mode 100644 index 00000000..988d09f7 --- /dev/null +++ b/pkg/tree/sharedEntryAttributes_test.go @@ -0,0 +1,59 @@ +package tree + +import ( + "context" + "fmt" + "testing" + + schemaClient "github.com/sdcio/data-server/pkg/datastore/clients/schema" + "github.com/sdcio/data-server/pkg/tree/types" + "github.com/sdcio/data-server/pkg/utils/testhelper" + "go.uber.org/mock/gomock" +) + +func Test_sharedEntryAttributes_checkAndCreateKeysAsLeafs(t *testing.T) { + controller := gomock.NewController(t) + defer controller.Finish() + + sc, schema, err := testhelper.InitSDCIOSchema() + if err != nil { + t.Fatal(err) + } + + ctx := context.Background() + scb := schemaClient.NewSchemaClientBound(schema, sc) + + tc := NewTreeContext(scb, "intent1") + + root, err := NewTreeRoot(ctx, tc) + if err != nil { + t.Error(err) + } + + flags := types.NewUpdateInsertFlags() + flags.SetNewFlag() + + prio := int32(5) + intentName := "intent1" + + _, err = root.AddUpdateRecursive(ctx, types.NewUpdate(types.PathSlice{"interface", "ethernet-1/1", "description"}, testhelper.GetStringTvProto("MyDescription"), prio, intentName, 0), flags) + if err != nil { + t.Error(err) + } + + _, err = root.AddUpdateRecursive(ctx, types.NewUpdate([]string{"doublekey", "k1.1", "k1.3", "mandato"}, testhelper.GetStringTvProto("TheMandatoryValue1"), prio, intentName, 0), flags) + if err != nil { + t.Error(err) + } + + t.Log(root.String()) + + fmt.Println(root.String()) + err = root.FinishInsertionPhase(ctx) + if err != nil { + t.Error(err) + } + fmt.Println(root.String()) + + // TODO: check the result +} diff --git a/pkg/tree/sorter.go b/pkg/tree/sorter.go index 57c5f433..44a97259 100644 --- a/pkg/tree/sorter.go +++ b/pkg/tree/sorter.go @@ -6,16 +6,21 @@ func getListEntrySortFunc(parent Entry) func(a, b Entry) int { keys := parent.GetSchemaKeys() var cmpResult int for _, v := range keys { - aLvSlice := a.getChildren()[v].GetHighestPrecedence(LeafVariantSlice{}, false) - bLvSlice := b.getChildren()[v].GetHighestPrecedence(LeafVariantSlice{}, false) + achild, exists := a.getChildren()[v] + if !exists { + return 0 + } + bchild, exists := b.getChildren()[v] + if !exists { + return 0 + } + aLvSlice := achild.GetHighestPrecedence(LeafVariantSlice{}, false, true) + bLvSlice := bchild.GetHighestPrecedence(LeafVariantSlice{}, false, true) aEntry := aLvSlice[0] bEntry := bLvSlice[0] - aTv, _ := aEntry.Value() - bTv, _ := bEntry.Value() - - cmpResult = aTv.Cmp(bTv) + cmpResult = aEntry.Value().Cmp(bEntry.Value()) if cmpResult != 0 { return cmpResult } diff --git a/pkg/tree/tree_cache_client.go b/pkg/tree/tree_cache_client.go deleted file mode 100644 index 3c722488..00000000 --- a/pkg/tree/tree_cache_client.go +++ /dev/null @@ -1,203 +0,0 @@ -package tree - -import ( - "context" - "math" - "strings" - "sync" - "time" - - "github.com/sdcio/cache/proto/cachepb" - "github.com/sdcio/data-server/pkg/cache" -) - -type TreeCacheClient interface { - // RefreshCaches refresh the running and intended Index cache - RefreshCaches(ctx context.Context) error - - // CACHE based Functions - // ReadIntended retrieves the highes priority value from the intended store - Read(ctx context.Context, opts *cache.Opts, paths [][]string) []*cache.Update - - ReadRunningPath(ctx context.Context, path PathSlice) (*cache.Update, error) - ReadRunningFull(ctx context.Context) ([]*cache.Update, error) - GetBranchesHighesPrecedence(ctx context.Context, path []string, filters ...CacheUpdateFilter) int32 - ReadCurrentUpdatesHighestPriorities(ctx context.Context, ccp PathSlices, count uint64) UpdateSlice - IntendedPathExists(ctx context.Context, path []string) (bool, error) - ReadUpdatesOwner(ctx context.Context, owner string) UpdateSlice -} - -type TreeCacheClientImpl struct { - cc cache.Client - datastore string - - timeout time.Duration - - intendedStoreIndex map[string]UpdateSlice // contains the keys that the intended store holds in the cache - intendedStoreIndexMutex sync.RWMutex - runningStoreIndex map[string]UpdateSlice // contains the keys of the running config - runningStoreIndexMutex sync.RWMutex -} - -func NewTreeCacheClient(datastore string, cc cache.Client) *TreeCacheClientImpl { - return &TreeCacheClientImpl{ - cc: cc, - datastore: datastore, - timeout: time.Second * 2, - intendedStoreIndexMutex: sync.RWMutex{}, - runningStoreIndexMutex: sync.RWMutex{}, - } -} - -func (t *TreeCacheClientImpl) IntendedPathExists(ctx context.Context, path []string) (bool, error) { - t.intendedStoreIndexMutex.RLock() - if t.intendedStoreIndex == nil { - t.intendedStoreIndexMutex.RUnlock() - t.RefreshCaches(ctx) - t.intendedStoreIndexMutex.RLock() - } - defer t.intendedStoreIndexMutex.RUnlock() - _, exists := t.intendedStoreIndex[strings.Join(path, KeysIndexSep)] - return exists, nil -} - -func (c *TreeCacheClientImpl) Read(ctx context.Context, opts *cache.Opts, paths [][]string) []*cache.Update { - if opts == nil { - opts = &cache.Opts{ - PriorityCount: 1, - } - } - - return c.cc.Read(ctx, c.datastore, opts, paths, c.timeout) -} - -func (c *TreeCacheClientImpl) RefreshCaches(ctx context.Context) error { - - var err error - c.runningStoreIndexMutex.Lock() - c.runningStoreIndex, err = c.readStoreKeysMeta(ctx, cachepb.Store_CONFIG) - c.runningStoreIndexMutex.Unlock() - if err != nil { - return err - } - c.intendedStoreIndexMutex.Lock() - c.intendedStoreIndex, err = c.readStoreKeysMeta(ctx, cachepb.Store_INTENDED) - c.intendedStoreIndexMutex.Unlock() - if err != nil { - return err - } - return nil -} - -func (c *TreeCacheClientImpl) readStoreKeysMeta(ctx context.Context, store cachepb.Store) (map[string]UpdateSlice, error) { - entryCh, err := c.cc.GetKeys(ctx, c.datastore, store) - if err != nil { - return nil, err - } - - result := map[string]UpdateSlice{} - for { - select { - case <-ctx.Done(): - return nil, ctx.Err() - case e, ok := <-entryCh: - if !ok { - return result, nil - } - key := strings.Join(e.GetPath(), KeysIndexSep) - _, exists := result[key] - if !exists { - result[key] = UpdateSlice{} - } - result[key] = append(result[key], e) - } - } -} - -func (c *TreeCacheClientImpl) GetBranchesHighesPrecedence(ctx context.Context, path []string, filters ...CacheUpdateFilter) int32 { - result := int32(math.MaxInt32) - pathKey := strings.Join(path, KeysIndexSep) - c.intendedStoreIndexMutex.RLock() - if c.intendedStoreIndex == nil { - c.intendedStoreIndexMutex.RUnlock() - c.RefreshCaches(ctx) - c.intendedStoreIndexMutex.RLock() - } - defer c.intendedStoreIndexMutex.RUnlock() - - // TODO: Improve this, since it is probably an expensive operation - for key, entries := range c.intendedStoreIndex { - if strings.HasPrefix(key, pathKey) { - if prio := entries.GetLowestPriorityValue(filters); prio < result { - result = prio - } - } - } - return result -} - -func (c *TreeCacheClientImpl) ReadCurrentUpdatesHighestPriorities(ctx context.Context, ccp PathSlices, count uint64) UpdateSlice { - return c.Read(ctx, &cache.Opts{ - Store: cachepb.Store_INTENDED, - PriorityCount: count, - }, ccp.ToStringSlice()) -} - -func (c *TreeCacheClientImpl) ReadUpdatesOwner(ctx context.Context, owner string) UpdateSlice { - - ownerPaths := c.getPathsOfOwner(ctx, owner) - - return c.Read(ctx, &cache.Opts{ - Store: cachepb.Store_INTENDED, - Owner: owner, - }, ownerPaths.paths.ToStringSlice()) -} - -func (c *TreeCacheClientImpl) getPathsOfOwner(ctx context.Context, owner string) *PathSet { - if c.intendedStoreIndex == nil { - c.RefreshCaches(ctx) - } - - p := NewPathSet() - for _, keyMeta := range c.intendedStoreIndex { - for _, k := range keyMeta { - if k.Owner() == owner { - // if the key is not yet listed in the keys slice, add it otherwise skip - p.AddPath(k.GetPath()) - } - } - } - return p -} - -// ReadRunning reads the value from running if the value does not exist, nil is returned -func (c *TreeCacheClientImpl) ReadRunningPath(ctx context.Context, path PathSlice) (*cache.Update, error) { - c.runningStoreIndexMutex.RLock() - if c.runningStoreIndex == nil { - c.runningStoreIndexMutex.RUnlock() - c.RefreshCaches(ctx) - c.runningStoreIndexMutex.RLock() - } - defer c.runningStoreIndexMutex.RUnlock() - // check if the value exists in running - _, exists := c.runningStoreIndex[strings.Join(path, KeysIndexSep)] - if !exists { - return nil, nil - } - - updates := c.Read(ctx, &cache.Opts{ - Store: cachepb.Store_CONFIG, - PriorityCount: 1, - }, [][]string{path}) - - return updates[0], nil -} - -// ReadRunning reads the value from running if the value does not exist, nil is returned -func (c *TreeCacheClientImpl) ReadRunningFull(ctx context.Context) ([]*cache.Update, error) { - updates := c.Read(ctx, &cache.Opts{ - Store: cachepb.Store_CONFIG, - }, [][]string{{}}) - - return updates, nil -} diff --git a/pkg/tree/tree_context.go b/pkg/tree/tree_context.go index a33153e7..8dc1d19c 100644 --- a/pkg/tree/tree_context.go +++ b/pkg/tree/tree_context.go @@ -8,14 +8,12 @@ import ( type TreeContext struct { root Entry // the trees root element - cacheClient TreeCacheClient schemaClient schemaClient.SchemaClientBound actualOwner string } -func NewTreeContext(cc TreeCacheClient, sc schemaClient.SchemaClientBound, actualOwner string) *TreeContext { +func NewTreeContext(sc schemaClient.SchemaClientBound, actualOwner string) *TreeContext { return &TreeContext{ - cacheClient: cc, schemaClient: sc, actualOwner: actualOwner, } @@ -24,14 +22,10 @@ func NewTreeContext(cc TreeCacheClient, sc schemaClient.SchemaClientBound, actua // deepCopy root is required to be set manually func (t *TreeContext) deepCopy() *TreeContext { return &TreeContext{ - cacheClient: t.cacheClient, + schemaClient: t.schemaClient, } } -func (t *TreeContext) GetTreeSchemaCacheClient() TreeCacheClient { - return t.cacheClient -} - func (t *TreeContext) SetRoot(e Entry) error { if t.root != nil { return fmt.Errorf("trying to set treecontexts root, although it is already set") diff --git a/pkg/tree/tree_persist/tree_persist.pb.go b/pkg/tree/tree_persist/tree_persist.pb.go new file mode 100644 index 00000000..989aba06 --- /dev/null +++ b/pkg/tree/tree_persist/tree_persist.pb.go @@ -0,0 +1,234 @@ +// Copyright 2025 Nokia +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.35.1 +// protoc v3.21.12 +// source: tree_persist.proto + +package tree_persist + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Intent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + IntentName string `protobuf:"bytes,1,opt,name=intent_name,json=intentName,proto3" json:"intent_name,omitempty"` + Root *TreeElement `protobuf:"bytes,2,opt,name=root,proto3" json:"root,omitempty"` + Priority int32 `protobuf:"varint,3,opt,name=priority,proto3" json:"priority,omitempty"` +} + +func (x *Intent) Reset() { + *x = Intent{} + mi := &file_tree_persist_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Intent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Intent) ProtoMessage() {} + +func (x *Intent) ProtoReflect() protoreflect.Message { + mi := &file_tree_persist_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Intent.ProtoReflect.Descriptor instead. +func (*Intent) Descriptor() ([]byte, []int) { + return file_tree_persist_proto_rawDescGZIP(), []int{0} +} + +func (x *Intent) GetIntentName() string { + if x != nil { + return x.IntentName + } + return "" +} + +func (x *Intent) GetRoot() *TreeElement { + if x != nil { + return x.Root + } + return nil +} + +func (x *Intent) GetPriority() int32 { + if x != nil { + return x.Priority + } + return 0 +} + +type TreeElement struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Childs []*TreeElement `protobuf:"bytes,4,rep,name=childs,proto3" json:"childs,omitempty"` + LeafVariant []byte `protobuf:"bytes,5,opt,name=leaf_variant,json=leafVariant,proto3" json:"leaf_variant,omitempty"` +} + +func (x *TreeElement) Reset() { + *x = TreeElement{} + mi := &file_tree_persist_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TreeElement) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TreeElement) ProtoMessage() {} + +func (x *TreeElement) ProtoReflect() protoreflect.Message { + mi := &file_tree_persist_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TreeElement.ProtoReflect.Descriptor instead. +func (*TreeElement) Descriptor() ([]byte, []int) { + return file_tree_persist_proto_rawDescGZIP(), []int{1} +} + +func (x *TreeElement) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *TreeElement) GetChilds() []*TreeElement { + if x != nil { + return x.Childs + } + return nil +} + +func (x *TreeElement) GetLeafVariant() []byte { + if x != nil { + return x.LeafVariant + } + return nil +} + +var File_tree_persist_proto protoreflect.FileDescriptor + +var file_tree_persist_proto_rawDesc = []byte{ + 0x0a, 0x12, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x73, 0x69, + 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x7a, 0x0a, 0x06, 0x49, 0x6e, 0x74, 0x65, + 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1f, 0x2e, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x72, 0x65, 0x65, 0x45, 0x6c, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, + 0x72, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, + 0x72, 0x69, 0x74, 0x79, 0x22, 0x7d, 0x0a, 0x0b, 0x54, 0x72, 0x65, 0x65, 0x45, 0x6c, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x37, 0x0a, 0x06, 0x63, 0x68, 0x69, 0x6c, 0x64, + 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x70, + 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x72, 0x65, + 0x65, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x73, + 0x12, 0x21, 0x0a, 0x0c, 0x6c, 0x65, 0x61, 0x66, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6c, 0x65, 0x61, 0x66, 0x56, 0x61, 0x72, 0x69, + 0x61, 0x6e, 0x74, 0x42, 0x10, 0x5a, 0x0e, 0x2e, 0x3b, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x70, 0x65, + 0x72, 0x73, 0x69, 0x73, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_tree_persist_proto_rawDescOnce sync.Once + file_tree_persist_proto_rawDescData = file_tree_persist_proto_rawDesc +) + +func file_tree_persist_proto_rawDescGZIP() []byte { + file_tree_persist_proto_rawDescOnce.Do(func() { + file_tree_persist_proto_rawDescData = protoimpl.X.CompressGZIP(file_tree_persist_proto_rawDescData) + }) + return file_tree_persist_proto_rawDescData +} + +var file_tree_persist_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_tree_persist_proto_goTypes = []any{ + (*Intent)(nil), // 0: tree_persist.proto.Intent + (*TreeElement)(nil), // 1: tree_persist.proto.TreeElement +} +var file_tree_persist_proto_depIdxs = []int32{ + 1, // 0: tree_persist.proto.Intent.root:type_name -> tree_persist.proto.TreeElement + 1, // 1: tree_persist.proto.TreeElement.childs:type_name -> tree_persist.proto.TreeElement + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_tree_persist_proto_init() } +func file_tree_persist_proto_init() { + if File_tree_persist_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_tree_persist_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_tree_persist_proto_goTypes, + DependencyIndexes: file_tree_persist_proto_depIdxs, + MessageInfos: file_tree_persist_proto_msgTypes, + }.Build() + File_tree_persist_proto = out.File + file_tree_persist_proto_rawDesc = nil + file_tree_persist_proto_goTypes = nil + file_tree_persist_proto_depIdxs = nil +} diff --git a/pkg/tree/tree_persist/tree_persist_additions.go b/pkg/tree/tree_persist/tree_persist_additions.go new file mode 100644 index 00000000..ddb80ee6 --- /dev/null +++ b/pkg/tree/tree_persist/tree_persist_additions.go @@ -0,0 +1,36 @@ +package tree_persist + +import ( + "fmt" + "strings" + + sdcpb "github.com/sdcio/sdc-protos/sdcpb" + "google.golang.org/protobuf/proto" +) + +func (x *Intent) PrettyString(indent string) string { + sb := &strings.Builder{} + sb.WriteString(fmt.Sprintf("Intent: %s\nPriority: %d\n", x.GetIntentName(), x.GetPriority())) + x.GetRoot().prettyString(indent, 0, sb) + return sb.String() +} + +func (te *TreeElement) PrettyString(indent string) string { + sb := &strings.Builder{} + te.prettyString(indent, 0, sb) + return sb.String() +} + +func (x *TreeElement) prettyString(indent string, level int, sb *strings.Builder) { + prefix := strings.Repeat(indent, level) + keylevel := "" + sb.WriteString(fmt.Sprintf("%s%s%s\n", prefix, x.GetName(), keylevel)) + for _, c := range x.GetChilds() { + c.prettyString(indent, level+1, sb) + } + if len(x.LeafVariant) > 0 { + tv := &sdcpb.TypedValue{} + proto.Unmarshal(x.LeafVariant, tv) + sb.WriteString(prefix + indent + tv.String() + "\n") + } +} diff --git a/pkg/tree/types/cache_update_filter.go b/pkg/tree/types/cache_update_filter.go new file mode 100644 index 00000000..e7e849a3 --- /dev/null +++ b/pkg/tree/types/cache_update_filter.go @@ -0,0 +1,21 @@ +package types + +type CacheUpdateFilter func(u *Update) bool + +func CacheUpdateFilterExcludeOwner(owner string) func(u *Update) bool { + return func(u *Update) bool { + return u.Owner() != owner + } +} + +// ApplyCacheUpdateFilters takes a bunch of CacheUpdateFilters applies them in an AND fashion +// and returns the result. +func ApplyCacheUpdateFilters(u *Update, fs []CacheUpdateFilter) bool { + for _, f := range fs { + b := f(u) + if !b { + return false + } + } + return true +} diff --git a/pkg/tree/types/delete_entry.go b/pkg/tree/types/delete_entry.go new file mode 100644 index 00000000..c9804acc --- /dev/null +++ b/pkg/tree/types/delete_entry.go @@ -0,0 +1,40 @@ +package types + +import ( + sdcpb "github.com/sdcio/sdc-protos/sdcpb" +) + +type DeleteEntry interface { + SdcpbPath() (*sdcpb.Path, error) + Path() PathSlice +} + +// DeleteEntryImpl is a crutch to flag oldbestcases if on a choice, the active case changed +type DeleteEntryImpl struct { + sdcpbPath *sdcpb.Path + pathslice PathSlice +} + +func NewDeleteEntryImpl(sdcpbPath *sdcpb.Path, pathslice PathSlice) *DeleteEntryImpl { + return &DeleteEntryImpl{ + sdcpbPath: sdcpbPath, + pathslice: pathslice, + } +} + +func (d *DeleteEntryImpl) SdcpbPath() (*sdcpb.Path, error) { + return d.sdcpbPath, nil +} +func (d *DeleteEntryImpl) Path() PathSlice { + return d.pathslice +} + +type DeleteEntriesList []DeleteEntry + +func (d DeleteEntriesList) PathSlices() PathSlices { + result := make(PathSlices, 0, len(d)) + for _, del := range d { + result = append(result, del.Path()) + } + return result +} diff --git a/pkg/tree/types/deviation_entry.go b/pkg/tree/types/deviation_entry.go new file mode 100644 index 00000000..a37248d4 --- /dev/null +++ b/pkg/tree/types/deviation_entry.go @@ -0,0 +1,55 @@ +package types + +import ( + sdcpb "github.com/sdcio/sdc-protos/sdcpb" +) + +type DeviationEntry struct { + intentName string + reason DeviationReason + path *sdcpb.Path + currentValue *sdcpb.TypedValue + expectedValue *sdcpb.TypedValue +} + +func NewDeviationEntry(intentName string, reason DeviationReason, path *sdcpb.Path) *DeviationEntry { + return &DeviationEntry{ + intentName: intentName, + reason: reason, + path: path, + } +} + +func (d *DeviationEntry) IntentName() string { + return d.intentName +} +func (d *DeviationEntry) Reason() DeviationReason { + return d.reason +} +func (d *DeviationEntry) Path() *sdcpb.Path { + return d.path +} +func (d *DeviationEntry) CurrentValue() *sdcpb.TypedValue { + return d.currentValue +} +func (d *DeviationEntry) ExpectedValue() *sdcpb.TypedValue { + return d.expectedValue +} +func (d *DeviationEntry) SetCurrentValue(cv *sdcpb.TypedValue) *DeviationEntry { + d.currentValue = cv + return d +} +func (d *DeviationEntry) SetExpectedValue(ev *sdcpb.TypedValue) *DeviationEntry { + d.expectedValue = ev + return d +} + +type DeviationReason int + +const ( + DeviationReasonUndefined DeviationReason = iota + DeviationReasonUnhandled + DeviationReasonNotApplied + DeviationReasonOverruled + DeviationReasonIntentExists +) diff --git a/pkg/tree/key_set.go b/pkg/tree/types/path_set.go similarity index 92% rename from pkg/tree/key_set.go rename to pkg/tree/types/path_set.go index 9e0f1f36..60a5d238 100644 --- a/pkg/tree/key_set.go +++ b/pkg/tree/types/path_set.go @@ -1,7 +1,11 @@ -package tree +package types import "strings" +const ( + KeysIndexSep = "_" +) + type PathSet struct { index map[string]struct{} paths PathSlices diff --git a/pkg/tree/path_slices.go b/pkg/tree/types/path_slices.go similarity index 78% rename from pkg/tree/path_slices.go rename to pkg/tree/types/path_slices.go index 9bb52f81..47f15463 100644 --- a/pkg/tree/path_slices.go +++ b/pkg/tree/types/path_slices.go @@ -1,4 +1,4 @@ -package tree +package types import ( "strings" @@ -11,8 +11,12 @@ func (p PathSlice) String() string { return strings.Join(p, "/") } -func (p PathSlice) ToStringSlice() []string { - return p +func (p PathSlice) DeepCopy() PathSlice { + result := make(PathSlice, 0, len(p)) + for _, entry := range p { + result = append(result, entry) + } + return result } // PathSlices is the slice collection of multiple PathSlice objects. diff --git a/pkg/tree/types/update.go b/pkg/tree/types/update.go new file mode 100644 index 00000000..5b262e44 --- /dev/null +++ b/pkg/tree/types/update.go @@ -0,0 +1,121 @@ +package types + +import ( + "context" + "fmt" + "slices" + + "github.com/sdcio/data-server/pkg/utils" + sdcpb "github.com/sdcio/sdc-protos/sdcpb" + "google.golang.org/protobuf/proto" +) + +type Update struct { + value *sdcpb.TypedValue + priority int32 + intentName string + timestamp int64 + path PathSlice +} + +func NewUpdateFromSdcpbUpdate(u *sdcpb.Update, prio int32, intent string, ts int64) *Update { + return NewUpdate(utils.ToStrings(u.GetPath(), false, false), u.GetValue(), prio, intent, ts) +} + +func NewUpdate(path PathSlice, val *sdcpb.TypedValue, prio int32, intent string, ts int64) *Update { + return &Update{ + value: val, + priority: prio, + intentName: intent, + timestamp: ts, + path: path, + } +} + +func (u *Update) DeepCopy() *Update { + + clonedVal := proto.Clone(u.Value()).(*sdcpb.TypedValue) + + return &Update{ + value: clonedVal, + priority: u.Priority(), + intentName: u.intentName, + timestamp: u.timestamp, + path: u.path.DeepCopy(), + } +} + +func (u *Update) Owner() string { + return u.intentName +} + +func (u *Update) SetOwner(owner string) { + u.intentName = owner +} + +func (u *Update) Priority() int32 { + return u.priority +} + +func (u *Update) SetPriority(prio int32) { + u.priority = prio +} + +func (u *Update) Timestamp() int64 { + return u.timestamp +} + +func (u *Update) Value() *sdcpb.TypedValue { + return u.value +} + +func (u *Update) ValueAsBytes() ([]byte, error) { + return proto.Marshal(u.value) +} + +func (u *Update) String() string { + return fmt.Sprintf("path: %s, owner: %s, priority: %d, value: %s", u.path, u.intentName, u.priority, u.value.String()) +} + +// EqualSkipPath checks the equality of two updates. +// It however skips comparing paths and timestamps. +// This is a shortcut for performace, for cases in which it is already clear that the path is definately equal. +func (u *Update) Equal(other *Update) bool { + if u.intentName != other.intentName || u.priority != other.priority { + return false + } + + uVal, _ := u.ValueAsBytes() + oVal, _ := other.ValueAsBytes() + return slices.Equal(uVal, oVal) +} + +func (u *Update) GetPathSlice() PathSlice { + return u.path +} + +// ExpandAndConvertIntent takes a slice of Updates ([]*sdcpb.Update) and converts it into a tree.UpdateSlice, that contains *treetypes.Updates. +func ExpandAndConvertIntent(ctx context.Context, scb utils.SchemaClientBound, intentName string, priority int32, upds []*sdcpb.Update, ts int64) (UpdateSlice, error) { + converter := utils.NewConverter(scb) + + // list of updates to be added to the cache + // Expands the value, in case of json to single typed value updates + expandedReqUpdates, err := converter.ExpandUpdates(ctx, upds) + if err != nil { + return nil, err + } + + // temp storage for cache.Update of the req. They are to be added later. + newCacheUpdates := make(UpdateSlice, 0, len(expandedReqUpdates)) + + for _, u := range expandedReqUpdates { + pathslice, err := utils.CompletePath(nil, u.GetPath()) + if err != nil { + return nil, err + } + + // construct the cache.Update + newCacheUpdates = append(newCacheUpdates, NewUpdate(pathslice, u.GetValue(), priority, intentName, ts)) + } + return newCacheUpdates, nil +} diff --git a/pkg/tree/types/update_insert_flags.go b/pkg/tree/types/update_insert_flags.go new file mode 100644 index 00000000..6eef64a9 --- /dev/null +++ b/pkg/tree/types/update_insert_flags.go @@ -0,0 +1,62 @@ +package types + +type LeafEntry interface { + MarkDelete(onlyIntended bool) + MarkNew() +} + +type UpdateInsertFlags struct { + new bool + delete bool + onlyIntended bool +} + +// NewUpdateInsertFlags returns a new *UpdateInsertFlags instance +// with all values set to false, so not new, and not marked for deletion +func NewUpdateInsertFlags() *UpdateInsertFlags { + return &UpdateInsertFlags{} +} + +func (f *UpdateInsertFlags) SetDeleteFlag() *UpdateInsertFlags { + f.delete = true + f.new = false + return f +} + +func (f *UpdateInsertFlags) SetDeleteOnlyUpdatedFlag() *UpdateInsertFlags { + f.delete = true + f.onlyIntended = true + f.new = false + return f +} + +func (f *UpdateInsertFlags) SetNewFlag() *UpdateInsertFlags { + f.new = true + f.delete = false + f.onlyIntended = false + return f +} + +func (f *UpdateInsertFlags) GetDeleteFlag() bool { + return f.delete +} + +func (f *UpdateInsertFlags) GetDeleteOnlyIntendedFlag() bool { + return f.onlyIntended +} + +func (f *UpdateInsertFlags) GetNewFlag() bool { + return f.new +} + +func (f *UpdateInsertFlags) Apply(le LeafEntry) *UpdateInsertFlags { + if f.delete { + le.MarkDelete(f.onlyIntended) + return f + } + if f.new { + le.MarkNew() + return f + } + return f +} diff --git a/pkg/tree/update_slice.go b/pkg/tree/types/update_slice.go similarity index 64% rename from pkg/tree/update_slice.go rename to pkg/tree/types/update_slice.go index b24c8664..f42695b9 100644 --- a/pkg/tree/update_slice.go +++ b/pkg/tree/types/update_slice.go @@ -1,13 +1,28 @@ -package tree +package types import ( "math" - - "github.com/sdcio/data-server/pkg/cache" ) // UpdateSlice A slice of *cache.Update, that defines additional helper functions. -type UpdateSlice []*cache.Update +type UpdateSlice []*Update + +func (u UpdateSlice) CopyWithNewOwnerAndPrio(owner string, prio int32) UpdateSlice { + result := u.DeepCopy() + for _, x := range result { + x.SetPriority(prio) + x.SetOwner(owner) + } + return result +} + +func (u UpdateSlice) DeepCopy() UpdateSlice { + result := make(UpdateSlice, 0, len(u)) + for _, x := range u { + result = append(result, x.DeepCopy()) + } + return result +} // GetFirstPriorityValue returns the priority of the first element or math.MaxInt32 if len() is zero func (u UpdateSlice) GetFirstPriorityValue() int32 { @@ -32,12 +47,12 @@ func (u UpdateSlice) ToPathSet() *PathSet { pathKeySet := NewPathSet() for _, upd := range u { - pathKeySet.AddPath(upd.GetPath()) + pathKeySet.AddPath(upd.GetPathSlice()) } return pathKeySet } -func Map[T any](u UpdateSlice, f func(*cache.Update) T) []T { +func Map[T any](u UpdateSlice, f func(*Update) T) []T { vsm := make([]T, len(u)) for i, v := range u { vsm[i] = f(v) diff --git a/pkg/types/validation_result.go b/pkg/tree/types/validation_result.go similarity index 100% rename from pkg/types/validation_result.go rename to pkg/tree/types/validation_result.go diff --git a/pkg/tree/validation_entry_leafref.go b/pkg/tree/validation_entry_leafref.go index 3d4aac53..3bf6829b 100644 --- a/pkg/tree/validation_entry_leafref.go +++ b/pkg/tree/validation_entry_leafref.go @@ -5,60 +5,34 @@ import ( "fmt" "strings" - "github.com/sdcio/data-server/pkg/types" + "github.com/sdcio/data-server/pkg/tree/types" "github.com/sdcio/data-server/pkg/utils" sdcpb "github.com/sdcio/sdc-protos/sdcpb" ) -// NavigateLeafRef -func (s *sharedEntryAttributes) NavigateLeafRef(ctx context.Context) ([]Entry, error) { - - 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() - default: - return nil, fmt.Errorf("error not a leafref %s", s.Path().String()) - } - - lv := s.leafVariants.GetHighestPrecedence(false, true) +func (s *sharedEntryAttributes) BreadthSearch(ctx context.Context, path string) ([]Entry, error) { + var err error + var resultEntries []Entry + var processEntries []Entry - lref, err := utils.StripPathElemPrefix(lref) + lref, err := utils.StripPathElemPrefix(path) if err != nil { - return nil, fmt.Errorf("failed stripping namespaces from leafref %s LeafVariant %v: %w", s.Path(), lv, err) + return nil, fmt.Errorf("failed stripping namespaces from leafref %s: %w", s.Path(), err) } - lrefSdcpbPath, err := utils.ParsePath(lref) + sdcpbPath, err := utils.ParsePath(lref) if err != nil { - return nil, fmt.Errorf("failed parsing leafref path %s LeafVariant %v: %w", s.Path(), lv, err) + return nil, fmt.Errorf("failed parsing leafref path %s: %w", s.Path(), err) } + lrefPath := newLrefPath(sdcpbPath) + // if the lrefs first character is "/" then it is a root based path isRootBasedPath := false if string(lref[0]) == "/" { isRootBasedPath = true } - tv, err := lv.Update.Value() - if err != nil { - return nil, fmt.Errorf("failed reading value from %s LeafVariant %v: %w", s.Path(), lv, err) - } - var values []*sdcpb.TypedValue - - switch ttv := tv.Value.(type) { - case *sdcpb.TypedValue_LeaflistVal: - values = append(values, ttv.LeaflistVal.GetElement()...) - default: - values = append(values, tv) - } - - var resultEntries []Entry - - lrefPath := newLrefPath(lrefSdcpbPath) - - var processEntries []Entry if isRootBasedPath { processEntries = []Entry{s.GetRoot()} } else { @@ -96,7 +70,7 @@ func (s *sharedEntryAttributes) NavigateLeafRef(ctx context.Context) ([]Entry, e // we need to do the forwarding for all the already lookedup paths for _, entry := range processEntries { - entry, err = entry.Navigate(ctx, []string{elem.Name}, false) + entry, err = entry.Navigate(ctx, []string{elem.Name}, false, false) if err != nil { return nil, err } @@ -129,19 +103,50 @@ func (s *sharedEntryAttributes) NavigateLeafRef(ctx context.Context) ([]Entry, e processEntries = resultEntries count++ } + return resultEntries, nil +} + +// NavigateLeafRef +func (s *sharedEntryAttributes) NavigateLeafRef(ctx context.Context) ([]Entry, error) { + + 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() + default: + return nil, fmt.Errorf("error not a leafref %s", s.Path().String()) + } + + lv := s.leafVariants.GetHighestPrecedence(false, true) + + tv := lv.Value() + var values []*sdcpb.TypedValue + + switch ttv := tv.Value.(type) { + case *sdcpb.TypedValue_LeaflistVal: + values = append(values, ttv.LeaflistVal.GetElement()...) + default: + values = append(values, tv) + } + + var resultEntries []Entry + + foundEntries, err := s.BreadthSearch(ctx, lref) + if err != nil { + return nil, err + } resultEntries = []Entry{} - for _, e := range processEntries { + for _, e := range foundEntries { r, err := e.getHighestPrecedenceLeafValue(ctx) if err != nil { return nil, err } - val, err := r.Update.Value() - if err != nil { - return nil, err - } + val := r.Value() for _, value := range values { if utils.EqualTypedValues(val, value) { resultEntries = append(resultEntries, e) @@ -179,11 +184,8 @@ func (s *sharedEntryAttributes) resolve_leafref_key_path(ctx context.Context, ke return err } - lvs := keyValue.GetHighestPrecedence(LeafVariantSlice{}, false) - tv, err := lvs[0].Value() - if err != nil { - return err - } + lvs := keyValue.GetHighestPrecedence(LeafVariantSlice{}, false, false) + tv := lvs[0].Value() keys[k].value = tv.GetStringVal() keys[k].doNotResolve = true } @@ -231,11 +233,7 @@ func generateOptionalWarning(ctx context.Context, s Entry, lref string, resultCh resultChan <- types.NewValidationResultEntry(lrefval.Owner(), err, types.ValidationResultEntryTypeError) return } - tvVal, err := lrefval.Update.Value() - if err != nil { - resultChan <- types.NewValidationResultEntry(lrefval.Owner(), err, types.ValidationResultEntryTypeError) - return - } + tvVal := lrefval.Value() resultChan <- types.NewValidationResultEntry(lrefval.Owner(), fmt.Errorf("leafref %s value %s unable to resolve non-mandatory reference %s", s.Path().String(), utils.TypedValueToString(tvVal), lref), types.ValidationResultEntryTypeWarning) } diff --git a/pkg/tree/validation_entry_must.go b/pkg/tree/validation_entry_must.go index ec914a2f..826d7fa5 100644 --- a/pkg/tree/validation_entry_must.go +++ b/pkg/tree/validation_entry_must.go @@ -5,7 +5,7 @@ import ( "fmt" "strings" - "github.com/sdcio/data-server/pkg/types" + "github.com/sdcio/data-server/pkg/tree/types" sdcpb "github.com/sdcio/sdc-protos/sdcpb" "github.com/sdcio/yang-parser/xpath" "github.com/sdcio/yang-parser/xpath/grammars/expr" @@ -66,7 +66,6 @@ func (s *sharedEntryAttributes) validateMustStatements(ctx context.Context, resu owner = s.leafVariants.GetHighestPrecedence(false, true).Owner() } resultChan <- types.NewValidationResultEntry(owner, err, types.ValidationResultEntryTypeError) - return } } } diff --git a/pkg/tree/validation_range_test.go b/pkg/tree/validation_range_test.go index a5136ed9..debe24f8 100644 --- a/pkg/tree/validation_range_test.go +++ b/pkg/tree/validation_range_test.go @@ -8,6 +8,7 @@ import ( "github.com/openconfig/ygot/ygot" json_importer "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" sdcio_schema "github.com/sdcio/data-server/tests/sdcioygot" "go.uber.org/mock/gomock" @@ -23,7 +24,7 @@ func TestValidate_Range_SDC_Schema(t *testing.T) { t.Error(err) } - tc := NewTreeContext(nil, scb, "owner1") + tc := NewTreeContext(scb, "owner1") root, err := NewTreeRoot(ctx, tc) @@ -63,12 +64,15 @@ func TestValidate_Range_SDC_Schema(t *testing.T) { jimporter := json_importer.NewJsonTreeImporter(jsonConfig) - err = root.ImportConfig(ctx, jimporter, "owner1", 5) + err = root.ImportConfig(ctx, types.PathSlice{}, jimporter, "owner1", 5, types.NewUpdateInsertFlags()) if err != nil { t.Error(err) } - root.FinishInsertionPhase(ctx) + err = root.FinishInsertionPhase(ctx) + if err != nil { + t.Error(err) + } validationResult := root.Validate(ctx, validationConfig) @@ -147,7 +151,7 @@ func TestValidate_RangesSigned(t *testing.T) { t.Run(tt.name, func(t *testing.T) { // the tree context - tc := NewTreeContext(nil, scb, "owner1") + tc := NewTreeContext(scb, "owner1") // the tree root root, err := NewTreeRoot(ctx, tc) @@ -172,12 +176,15 @@ func TestValidate_RangesSigned(t *testing.T) { jimporter := json_importer.NewJsonTreeImporter(jsonConfig) // import via importer - err = root.ImportConfig(ctx, jimporter, "owner1", 5) + err = root.ImportConfig(ctx, types.PathSlice{}, jimporter, "owner1", 5, types.NewUpdateInsertFlags()) if err != nil { t.Error(err) } - root.FinishInsertionPhase(ctx) + err = root.FinishInsertionPhase(ctx) + if err != nil { + t.Error(err) + } validationResult := root.Validate(ctx, validationConfig) @@ -277,7 +284,7 @@ func TestValidate_RangesUnSigned(t *testing.T) { t.Run(tt.name, func(t *testing.T) { // the tree context - tc := NewTreeContext(nil, scb, "owner1") + tc := NewTreeContext(scb, "owner1") // the tree root root, err := NewTreeRoot(ctx, tc) @@ -302,12 +309,15 @@ func TestValidate_RangesUnSigned(t *testing.T) { jimporter := json_importer.NewJsonTreeImporter(jsonConfig) // import via importer - err = root.ImportConfig(ctx, jimporter, "owner1", 5) + err = root.ImportConfig(ctx, types.PathSlice{}, jimporter, "owner1", 5, types.NewUpdateInsertFlags()) if err != nil { t.Error(err) } - root.FinishInsertionPhase(ctx) + err = root.FinishInsertionPhase(ctx) + if err != nil { + t.Error(err) + } // run validation validationResults := root.Validate(ctx, validationConfig) diff --git a/pkg/tree/xml.go b/pkg/tree/xml.go index 72a9bf51..ce94b519 100644 --- a/pkg/tree/xml.go +++ b/pkg/tree/xml.go @@ -213,17 +213,13 @@ func (s *sharedEntryAttributes) toXmlInternal(parent *etree.Element, onlyNewOrUp if le == nil { return false, nil } - v, err := le.Update.Value() - if err != nil { - return false, err - } ns := "" // process the namespace attribute if s.parent == nil || (honorNamespace && !namespaceIsEqual(s, s.parent)) { ns = utils.GetNamespaceFromGetSchema(s.GetSchema()) } // convert value to XML and add to parent - utils.TypedValueToXML(parent, v, s.PathName(), ns, onlyNewOrUpdated, operationWithNamespace, useOperationRemove) + utils.TypedValueToXML(parent, le.Value(), s.PathName(), ns, onlyNewOrUpdated, operationWithNamespace, useOperationRemove) return true, nil } return false, fmt.Errorf("unable to convert to xml (%s)", s.Path()) diff --git a/pkg/tree/xml_test.go b/pkg/tree/xml_test.go index e60c1f8e..79a3383d 100644 --- a/pkg/tree/xml_test.go +++ b/pkg/tree/xml_test.go @@ -7,8 +7,6 @@ import ( "github.com/google/go-cmp/cmp" "github.com/openconfig/ygot/ygot" - "github.com/sdcio/data-server/mocks/mockcacheclient" - "github.com/sdcio/data-server/pkg/cache" "github.com/sdcio/data-server/pkg/utils" "github.com/sdcio/data-server/pkg/utils/testhelper" sdcio_schema "github.com/sdcio/data-server/tests/sdcioygot" @@ -82,7 +80,7 @@ func TestToXMLTable(t *testing.T) { { name: "XML NewOrUpdated - some elements deleted, some updated", onlyNewOrUpdated: true, - skip: true, + skip: false, existingConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { c := config1() return expandUpdateFromConfig(ctx, c, converter) @@ -119,7 +117,7 @@ func TestToXMLTable(t *testing.T) { `, }, { - name: "XML - delete ethernet-1/1, honor namespace, operatin With namespace, remove", + 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() @@ -167,7 +165,7 @@ func TestToXMLTable(t *testing.T) { }, }, { - name: "XML - delete certain ethernet-1/1 attributes update another", + 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() @@ -199,7 +197,7 @@ func TestToXMLTable(t *testing.T) { }, }, { - name: "XML - delete ethernet-1/1 add ethernet-1/2", + 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() @@ -209,10 +207,8 @@ func TestToXMLTable(t *testing.T) { c := config1() return expandUpdateFromConfig(ctx, c, converter) }, - skip: true, - expected: ` - - + skip: false, + expected: ` ethernet-1/1 @@ -369,6 +365,36 @@ func TestToXMLTable(t *testing.T) { return upds, nil }, }, + { + name: "XML - replace choice", + onlyNewOrUpdated: true, + existingConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { + c := config1() + return expandUpdateFromConfig(ctx, c, converter) + }, + runningConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { + c := config1() + return expandUpdateFromConfig(ctx, c, converter) + }, + expected: ` + + + true + + +`, + honorNamespace: true, + operationWithNamespace: true, + useOperationRemove: true, + newConfig: func(ctx context.Context, converter *utils.Converter) ([]*sdcpb.Update, error) { + c := config1() + c.Choices.Case1 = nil + c.Choices.Case2 = &sdcio_schema.SdcioModel_Choices_Case2{ + Log: ygot.Bool(true), + } + return expandUpdateFromConfig(ctx, c, converter) + }, + }, } for _, tt := range tests { @@ -388,15 +414,35 @@ func TestToXMLTable(t *testing.T) { } owner := "owner1" - // mock cache client - ccMock := mockcacheclient.NewMockClient(mockCtrl) - testhelper.ConfigureCacheClientMock(t, ccMock, []*cache.Update{}, []*cache.Update{}, []*cache.Update{}, [][]string{}) + // var runningCacheUpds []*types.Update + // if tt.runningConfig != nil { + // runningSdcpbUpds, err := tt.runningConfig(context.Background(), utils.NewConverter(scb)) + // if err != nil { + // t.Error(err) + // } + // runningCacheUpds, err = utils.SdcpbUpdatesToCacheUpdates(runningSdcpbUpds, RunningIntentName, RunningValuesPrio) + // if err != nil { + // t.Error(err) + // } + // } + + // var intendedCacheUpds []*types.Update + // if tt.existingConfig != nil { + // intendedSdcpbUpds, err := tt.existingConfig(context.Background(), utils.NewConverter(scb)) + // if err != nil { + // t.Error(err) + // } + // intendedCacheUpds, err = utils.SdcpbUpdatesToCacheUpdates(intendedSdcpbUpds, owner, 5) + // if err != nil { + // t.Error(err) + // } + // } ctx := context.Background() converter := utils.NewConverter(scb) - tc := NewTreeContext(NewTreeCacheClient("dev1", ccMock), scb, owner) + tc := NewTreeContext(scb, owner) root, err := NewTreeRoot(ctx, tc) if err != nil { t.Fatal(err) @@ -411,9 +457,11 @@ func TestToXMLTable(t *testing.T) { t.Fatal(err) } } + fmt.Println("AFTER EXISTING:") + fmt.Println(root.String()) if tt.newConfig != nil { - root.markOwnerDelete(owner, false) + root.MarkOwnerDelete(owner, false) newUpds, err := tt.newConfig(ctx, converter) if err != nil { @@ -424,6 +472,8 @@ func TestToXMLTable(t *testing.T) { t.Fatal(err) } } + fmt.Println("AFTER NEW:") + fmt.Println(root.String()) if tt.runningConfig != nil { runningUpds, err := tt.runningConfig(ctx, converter) if err != nil { @@ -434,11 +484,16 @@ func TestToXMLTable(t *testing.T) { t.Fatal(err) } } - root.FinishInsertionPhase(ctx) + fmt.Println("AFTER RUNNING:") t.Log(root.String()) fmt.Println(root.String()) + err = root.FinishInsertionPhase(ctx) + if err != nil { + t.Error(err) + } + xmlDoc, err := root.ToXML(tt.onlyNewOrUpdated, tt.honorNamespace, tt.operationWithNamespace, tt.useOperationRemove) if err != nil { t.Fatal(err) diff --git a/pkg/tree/yang-parser-adapter.go b/pkg/tree/yang-parser-adapter.go index e7a6aa9c..2d7dc11c 100644 --- a/pkg/tree/yang-parser-adapter.go +++ b/pkg/tree/yang-parser-adapter.go @@ -48,20 +48,50 @@ func (y *yangParserEntryAdapter) valueToDatum(tv *sdcpb.TypedValue) xpath.Datum } func (y *yangParserEntryAdapter) GetValue() (xpath.Datum, error) { - if y.e.GetSchema() == nil || y.e.GetSchema().GetContainer() != nil { + if y.e.GetSchema() == nil { return xpath.NewBoolDatum(true), nil } + // if y.e is a container + if cs := y.e.GetSchema().GetContainer(); cs != nil { + // its a container + if len(cs.Keys) == 0 { + // regular container + return xpath.NewBoolDatum(true), nil + } + // list + childs, err := y.e.GetListChilds() + if err != nil { + return nil, err + } + datums := make([]xutils.XpathNode, 0, len(childs)) + for x := 0; x < len(childs); x++ { + // this is a dirty fix, that will enable count() to evaluate the right value + datums = append(datums, nil) + } + return xpath.NewNodesetDatum(datums), nil + } + + // if y.e is anything else then a container lv, _ := y.e.getHighestPrecedenceLeafValue(y.ctx) if lv == nil { return xpath.NewNodesetDatum([]xutils.XpathNode{}), nil } - tv, err := lv.Update.Value() + return y.valueToDatum(lv.Value()), nil +} + +func (y *yangParserEntryAdapter) BreadthSearch(ctx context.Context, path string) ([]xpath.Entry, error) { + entries, err := y.e.BreadthSearch(ctx, path) if err != nil { return nil, err } - return y.valueToDatum(tv), nil + result := make([]xpath.Entry, 0, len(entries)) + for _, x := range entries { + result = append(result, newYangParserEntryAdapter(ctx, x)) + } + + return result, nil } func (y *yangParserEntryAdapter) FollowLeafRef() (xpath.Entry, error) { @@ -95,22 +125,10 @@ func (y *yangParserEntryAdapter) Navigate(p []string) (xpath.Entry, error) { rootPath = true } - lookedUpEntry := y.e - for idx, pelem := range p { - // if we move up, on a .. we should just go up, staying in the branch that represents the instance - // if there is another .. then we need to forward to the element with the schema and just then forward - // to the parent. Thereby skipping the key levels that sit inbetween - if pelem == ".." && lookedUpEntry.GetSchema().GetSchema() == nil { - lookedUpEntry, _ = lookedUpEntry.GetFirstAncestorWithSchema() - } - - // rootPath && idx == 0 => means only allow true on first index, for sure false on all other - lookedUpEntry, err = lookedUpEntry.Navigate(y.ctx, []string{pelem}, rootPath && idx == 0) - if err != nil { - return newYangParserValueEntry(xpath.NewNodesetDatum([]xutils.XpathNode{}), err), nil - } + lookedUpEntry, err := y.e.Navigate(y.ctx, p, rootPath, true) + if err != nil { + return newYangParserValueEntry(xpath.NewNodesetDatum([]xutils.XpathNode{}), err), nil } - return newYangParserEntryAdapter(y.ctx, lookedUpEntry), nil } @@ -145,3 +163,7 @@ func (y *yangParserValueEntry) GetValue() (xpath.Datum, error) { func (y *yangParserValueEntry) GetPath() []string { return nil } + +func (y *yangParserValueEntry) BreadthSearch(ctx context.Context, path string) ([]xpath.Entry, error) { + return nil, nil +} diff --git a/pkg/utils/converter.go b/pkg/utils/converter.go index 72b288cf..27e21c8b 100644 --- a/pkg/utils/converter.go +++ b/pkg/utils/converter.go @@ -34,10 +34,10 @@ func NewConverter(scb SchemaClientBound) *Converter { } } -func (c *Converter) ExpandUpdates(ctx context.Context, updates []*sdcpb.Update, includeKeysAsLeaf bool) ([]*sdcpb.Update, error) { +func (c *Converter) ExpandUpdates(ctx context.Context, updates []*sdcpb.Update) ([]*sdcpb.Update, error) { outUpdates := make([]*sdcpb.Update, 0, len(updates)) for _, upd := range updates { - expUpds, err := c.ExpandUpdate(ctx, upd, includeKeysAsLeaf) + expUpds, err := c.ExpandUpdate(ctx, upd) if err != nil { return nil, err } @@ -47,16 +47,8 @@ func (c *Converter) ExpandUpdates(ctx context.Context, updates []*sdcpb.Update, } // expandUpdate Expands the value, in case of json to single typed value updates -func (c *Converter) ExpandUpdate(ctx context.Context, upd *sdcpb.Update, includeKeysAsLeaf bool) ([]*sdcpb.Update, error) { +func (c *Converter) ExpandUpdate(ctx context.Context, upd *sdcpb.Update) ([]*sdcpb.Update, error) { upds := make([]*sdcpb.Update, 0) - if includeKeysAsLeaf { - // expand update path if it contains keys - intUpd, err := c.ExpandUpdateKeysAsLeaf(ctx, upd) - if err != nil { - return nil, err - } - upds = append(upds, intUpd...) - } rsp, err := c.schemaClientBound.GetSchemaSdcpbPath(ctx, upd.GetPath()) if err != nil { return nil, err @@ -64,8 +56,20 @@ func (c *Converter) ExpandUpdate(ctx context.Context, upd *sdcpb.Update, include switch rsp := rsp.GetSchema().Schema.(type) { case *sdcpb.SchemaElem_Container: - log.Debugf("expanding update %v on container %q", upd, rsp.Container.Name) - var v interface{} + // if it is not a presence container and the value is nil, + // return without doing anything + if !rsp.Container.GetIsPresence() && upd.Value == nil { + return nil, nil + } + + // if it is a presence container and no value is set, set upd value to EmptyVal + if rsp.Container.GetIsPresence() && upd.Value == nil { + upd.Value = &sdcpb.TypedValue{ + Value: &sdcpb.TypedValue_EmptyVal{}, + } + } + + var v any var err error var jsonDecoder *json.Decoder switch upd.GetValue().Value.(type) { @@ -83,13 +87,14 @@ func (c *Converter) ExpandUpdate(ctx context.Context, upd *sdcpb.Update, include if err != nil { return nil, err } - log.Debugf("update has jsonVal: %T, %v\n", v, v) - rs, err := c.ExpandContainerValue(ctx, upd.GetPath(), v, rsp, includeKeysAsLeaf) + // log.Debugf("update has jsonVal: %T, %v\n", v, v) + rs, err := c.ExpandContainerValue(ctx, upd.GetPath(), v, rsp) if err != nil { return nil, err } upds := append(upds, rs...) return upds, nil + case *sdcpb.SchemaElem_Field: var v interface{} var err error @@ -114,11 +119,16 @@ func (c *Converter) ExpandUpdate(ctx context.Context, upd *sdcpb.Update, include } } - // TODO: Check if value is json and convert to String ? + if rsp.Field.GetType().Type == "identityref" { + upd.Value, err = Convert(upd.GetValue().GetStringVal(), rsp.Field.GetType()) + if err != nil { + return nil, err + } + } + upds = append(upds, upd) return upds, nil case *sdcpb.SchemaElem_Leaflist: - // TODO: Check if value is json and convert to String ? upds = append(upds, upd) return upds, nil } @@ -127,6 +137,8 @@ func (c *Converter) ExpandUpdate(ctx context.Context, upd *sdcpb.Update, include func (c *Converter) ExpandUpdateKeysAsLeaf(ctx context.Context, upd *sdcpb.Update) ([]*sdcpb.Update, error) { upds := make([]*sdcpb.Update, 0) + var err error + var schemaRsp *sdcpb.GetSchemaResponse // expand update path if it contains keys for i, pe := range upd.GetPath().GetElem() { if len(pe.GetKey()) == 0 { @@ -149,22 +161,25 @@ func (c *Converter) ExpandUpdateKeysAsLeaf(ctx context.Context, upd *sdcpb.Updat } intUpd.Path.Elem = append(intUpd.Path.Elem, &sdcpb.PathElem{Name: k}) - schemaRsp, err := c.schemaClientBound.GetSchemaSdcpbPath(ctx, intUpd.Path) + schemaRsp, err = c.schemaClientBound.GetSchemaSdcpbPath(ctx, intUpd.Path) if err != nil { return nil, err } + intUpd.Value, err = TypedValueToYANGType(&sdcpb.TypedValue{Value: &sdcpb.TypedValue_StringVal{StringVal: v}}, schemaRsp.GetSchema()) if err != nil { return nil, err } + upds = append(upds, intUpd) } } + return upds, nil } -func (c *Converter) ExpandContainerValue(ctx context.Context, p *sdcpb.Path, jv any, cs *sdcpb.SchemaElem_Container, includeKeysAsLeaf bool) ([]*sdcpb.Update, error) { - log.Debugf("expanding jsonVal %T | %v | %v", jv, jv, p) +func (c *Converter) ExpandContainerValue(ctx context.Context, p *sdcpb.Path, jv any, cs *sdcpb.SchemaElem_Container) ([]*sdcpb.Update, error) { + // log.Debugf("expanding jsonVal %T | %v | %v", jv, jv, p) switch jv := jv.(type) { case string: v := strings.Trim(jv, "\"") @@ -184,8 +199,8 @@ func (c *Converter) ExpandContainerValue(ctx context.Context, p *sdcpb.Path, jv if numElems := len(p.GetElem()); numElems > 0 { keysInPath = p.GetElem()[numElems-1].GetKey() } - // make sure all keys exist either in the JSON value or - // in the path but NOT in both and build keySet + // // make sure all keys exist either in the JSON value or + // // in the path but NOT in both and build keySet keySet := map[string]string{} for _, k := range cs.Container.GetKeys() { if v, ok := jv[k.Name]; ok { @@ -203,8 +218,8 @@ func (c *Converter) ExpandContainerValue(ctx context.Context, p *sdcpb.Path, jv } // handling keys in last element of the path or in the json value for _, k := range cs.Container.GetKeys() { - if v, ok := jv[k.Name]; ok { - log.Debugf("handling key %s", k.Name) + if _, ok := jv[k.Name]; ok { + // log.Debugf("handling key %s", k.Name) if _, ok := keysInPath[k.Name]; ok { return nil, fmt.Errorf("key %q is present in both the path and JSON value", k.Name) } @@ -212,23 +227,6 @@ func (c *Converter) ExpandContainerValue(ctx context.Context, p *sdcpb.Path, jv p.GetElem()[len(p.GetElem())-1].Key = make(map[string]string) } p.GetElem()[len(p.GetElem())-1].Key = keySet - if includeKeysAsLeaf { - np := proto.Clone(p).(*sdcpb.Path) - np.Elem = append(np.Elem, &sdcpb.PathElem{Name: k.Name}) - schemaRsp, err := c.schemaClientBound.GetSchemaSdcpbPath(ctx, np) - if err != nil { - return nil, err - } - updVal, err := TypedValueToYANGType(&sdcpb.TypedValue{Value: &sdcpb.TypedValue_StringVal{StringVal: fmt.Sprintf("%v", v)}}, schemaRsp.GetSchema()) - if err != nil { - return nil, err - } - upd := &sdcpb.Update{ - Path: np, - Value: updVal, - } - upds = append(upds, upd) - } continue } // if key is not in the value it must be set in the path @@ -237,7 +235,8 @@ func (c *Converter) ExpandContainerValue(ctx context.Context, p *sdcpb.Path, jv } } for k, v := range jv { - if isKey(k, cs) { + // TODO remove the statement_annotate again ... + if k == "_annotate" { continue } item, ok := getItem(ctx, k, cs, c.schemaClientBound) @@ -246,7 +245,7 @@ func (c *Converter) ExpandContainerValue(ctx context.Context, p *sdcpb.Path, jv } switch item := item.(type) { case *sdcpb.LeafSchema: // field - log.Debugf("handling field %s", item.Name) + // log.Debugf("handling field %s", item.Name) np := proto.Clone(p).(*sdcpb.Path) np.Elem = append(np.Elem, &sdcpb.PathElem{Name: item.Name}) upd := &sdcpb.Update{Path: np} @@ -310,7 +309,7 @@ func (c *Converter) ExpandContainerValue(ctx context.Context, p *sdcpb.Path, jv upds = append(upds, upd) case string: // child container - log.Debugf("handling child container %s", item) + // log.Debugf("handling child container %s", item) np := proto.Clone(p).(*sdcpb.Path) np.Elem = append(np.Elem, &sdcpb.PathElem{Name: item}) rsp, err := c.schemaClientBound.GetSchemaSdcpbPath(ctx, np) @@ -331,7 +330,7 @@ func (c *Converter) ExpandContainerValue(ctx context.Context, p *sdcpb.Path, jv }, }} } else { - rs, err = c.ExpandContainerValue(ctx, np, v, rsp, includeKeysAsLeaf) + rs, err = c.ExpandContainerValue(ctx, np, v, rsp) if err != nil { return nil, err } @@ -351,7 +350,7 @@ func (c *Converter) ExpandContainerValue(ctx context.Context, p *sdcpb.Path, jv upds := make([]*sdcpb.Update, 0) for _, v := range jv { np := proto.Clone(p).(*sdcpb.Path) - r, err := c.ExpandContainerValue(ctx, np, v, cs, includeKeysAsLeaf) + r, err := c.ExpandContainerValue(ctx, np, v, cs) if err != nil { return nil, err } @@ -364,15 +363,6 @@ func (c *Converter) ExpandContainerValue(ctx context.Context, p *sdcpb.Path, jv } } -func isKey(s string, cs *sdcpb.SchemaElem_Container) bool { - for _, k := range cs.Container.GetKeys() { - if k.Name == s { - return true - } - } - return false -} - func TypedValueToYANGType(tv *sdcpb.TypedValue, schemaObject *sdcpb.SchemaElem) (*sdcpb.TypedValue, error) { switch tv.Value.(type) { case *sdcpb.TypedValue_AsciiVal: @@ -530,6 +520,22 @@ func getItem(ctx context.Context, s string, cs *sdcpb.SchemaElem_Container, scb if ok { return c, true } + k, ok := getKey(s, cs) + if ok { + return k, true + } + return nil, false +} + +func getKey(s string, cs *sdcpb.SchemaElem_Container) (*sdcpb.LeafSchema, bool) { + for _, f := range cs.Container.GetKeys() { + if f.Name == s { + return f, true + } + if fmt.Sprintf("%s:%s", f.ModuleName, f.Name) == s { + return f, true + } + } return nil, false } @@ -777,7 +783,7 @@ func (c *Converter) ConvertNotificationTypedValues(ctx context.Context, n *sdcpb log.Debugf("converted update from: %v, to: %v", upd, nup) // gNMI get() could return a Notification with a single path element containing a JSON/JSON_IETF blob, we need to expand this into several typed values. if nup == nil && (upd.GetValue().GetJsonVal() != nil || upd.GetValue().GetJsonIetfVal() != nil) { - expUpds, err := c.ExpandUpdate(ctx, upd, true) + expUpds, err := c.ExpandUpdate(ctx, upd) if err != nil { return nil, err } diff --git a/pkg/utils/notification.go b/pkg/utils/notification.go index 7198a27d..1edbd07e 100644 --- a/pkg/utils/notification.go +++ b/pkg/utils/notification.go @@ -67,11 +67,11 @@ func FromGNMIPath(pre, p *gnmi.Path) *sdcpb.Path { } func FromGNMITypedValue(v *gnmi.TypedValue) *sdcpb.TypedValue { - log.Tracef("FromGNMITypedValue: %T : %v", v, v) + // log.Tracef("FromGNMITypedValue: %T : %v", v, v) if v == nil { return nil } - log.Tracef("FromGNMITypedValue - Value: %T : %v", v.GetValue(), v.GetValue()) + // log.Tracef("FromGNMITypedValue - Value: %T : %v", v.GetValue(), v.GetValue()) switch v.GetValue().(type) { // case *gnmi.TypedValue: case *gnmi.TypedValue_AnyVal: diff --git a/pkg/utils/testhelper/cacheclient.go b/pkg/utils/testhelper/cacheclient.go index 25bfdae9..5859a4d2 100644 --- a/pkg/utils/testhelper/cacheclient.go +++ b/pkg/utils/testhelper/cacheclient.go @@ -1,108 +1,62 @@ package testhelper -import ( - "context" - "strings" - "testing" - "time" - - "github.com/sdcio/cache/proto/cachepb" - "github.com/sdcio/data-server/mocks/mockcacheclient" - "github.com/sdcio/data-server/pkg/cache" - "github.com/sdcio/data-server/pkg/utils" - sdcpb "github.com/sdcio/sdc-protos/sdcpb" - "go.uber.org/mock/gomock" - "google.golang.org/protobuf/proto" -) - -func ConfigureCacheClientMock(t *testing.T, cacheClient *mockcacheclient.MockClient, updatesIntended []*cache.Update, updatesRunning []*cache.Update, expectedModify []*cache.Update, expectedDeletes [][]string) { - - // mock the .GetIntendedKeysMeta() call - cacheClient.EXPECT().GetKeys(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().DoAndReturn( - - func(_ context.Context, datastoreName string, store cachepb.Store) (chan *cache.Update, error) { - rsCh := make(chan *cache.Update) - - // GetKeys might be called with different stores... - // init with intended store, but if store is Config then use updatesRunning - source := updatesIntended - if store == cachepb.Store_CONFIG { - source = updatesRunning - } - - go func() { - for _, u := range source { - rsCh <- u - } - close(rsCh) - }() - return rsCh, nil - }, - ) - - // mock the .Read() call - // prepare a map of the updates indexed by the path for quick lookup - updatesMap := map[cachepb.Store]map[string][]*cache.Update{} - - updatesMap[cachepb.Store_CONFIG] = map[string][]*cache.Update{} - updatesMap[cachepb.Store_INTENDED] = map[string][]*cache.Update{} - // fill the map - for _, u := range updatesIntended { - key := strings.Join(u.GetPath(), pathSep) - updatesMap[cachepb.Store_INTENDED][key] = append(updatesMap[cachepb.Store_INTENDED][key], u) - } - for _, u := range updatesRunning { - key := strings.Join(u.GetPath(), pathSep) - updatesMap[cachepb.Store_CONFIG][key] = append(updatesMap[cachepb.Store_CONFIG][key], u) - } - cacheClient.EXPECT().Read(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().DoAndReturn( - func(_ context.Context, datastoreName string, opts *cache.Opts, paths [][]string, period time.Duration) []*cache.Update { - if len(paths) == 1 && len(paths[0]) == 0 { - return updatesRunning - } - result := make([]*cache.Update, 0, len(paths)) - for _, p := range paths { - if val, exists := updatesMap[opts.Store][strings.Join(p, pathSep)]; exists { - result = append(result, val...) - } - } - return result - }, - ) - - // mock the .HasCandidate() call - cacheClient.EXPECT().HasCandidate(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().DoAndReturn( - func(_ context.Context, _ string, _ string) (bool, error) { - return true, nil - }, - ) - - // mock the NewUpdate() call - cacheClient.EXPECT().NewUpdate(gomock.Any()).AnyTimes().DoAndReturn( - - func(upd *sdcpb.Update) (*cache.Update, error) { - b, err := proto.Marshal(upd.Value) - if err != nil { - return nil, err - } - return cache.NewUpdate(utils.ToStrings(upd.GetPath(), false, false), b, 0, "", 0), nil - - }, - ) - - // mock the .Modify() call - cacheClient.EXPECT().Modify(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().DoAndReturn( - func(ctx context.Context, name string, opts *cache.Opts, dels [][]string, upds []*cache.Update) error { - if opts.Store == cachepb.Store_INTENDED { - if diff := DiffCacheUpdates(expectedModify, upds); diff != "" { - t.Errorf("cache.Modify() updates mismatch (-want +got):\n%s", diff) - } - - if diff := DiffDoubleStringPathSlice(expectedDeletes, dels); diff != "" { - t.Errorf("cache.Modify() deletes mismatch (-want +got):\n%s", diff) - } - } - return nil - }, - ) -} +// func ConfigureCacheClientMock(t *testing.T, cacheClient *mockcacheclient.MockClient, updatesIntended []*cache.Update, updatesRunning []*cache.Update, expectedModify []*cache.Update, expectedDeletes [][]string) { + +// cacheClient.EXPECT().InstanceIntentGet(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( +// func (ctx context.Context, ) { + +// } + +// ) + +// Read(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().DoAndReturn( +// func(_ context.Context, datastoreName string, opts *cache.Opts, paths [][]string, period time.Duration) []*cache.Update { +// if len(paths) == 1 && len(paths[0]) == 0 { +// return updatesRunning +// } +// result := make([]*cache.Update, 0, len(paths)) +// for _, p := range paths { +// if val, exists := updatesMap[opts.Store][strings.Join(p, pathSep)]; exists { +// result = append(result, val...) +// } +// } +// return result +// }, +// ) + +// // mock the .HasCandidate() call +// cacheClient.EXPECT().HasCandidate(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().DoAndReturn( +// func(_ context.Context, _ string, _ string) (bool, error) { +// return true, nil +// }, +// ) + +// // mock the NewUpdate() call +// cacheClient.EXPECT().NewUpdate(gomock.Any()).AnyTimes().DoAndReturn( + +// func(upd *sdcpb.Update) (*cache.Update, error) { +// b, err := proto.Marshal(upd.Value) +// if err != nil { +// return nil, err +// } +// return cache.NewUpdate(utils.ToStrings(upd.GetPath(), false, false), b, 0, "", 0), nil + +// }, +// ) + +// // mock the .Modify() call +// cacheClient.EXPECT().Modify(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().DoAndReturn( +// func(ctx context.Context, name string, opts *cache.Opts, dels [][]string, upds []*cache.Update) error { +// if opts.Store == cachepb.Store_INTENDED { +// if diff := DiffCacheUpdates(expectedModify, upds); diff != "" { +// t.Errorf("cache.Modify() updates mismatch (-want +got):\n%s", diff) +// } + +// if diff := DiffDoubleStringPathSlice(expectedDeletes, dels); diff != "" { +// t.Errorf("cache.Modify() deletes mismatch (-want +got):\n%s", diff) +// } +// } +// return nil +// }, +// ) +// } diff --git a/pkg/utils/testhelper/utils.go b/pkg/utils/testhelper/utils.go index 334684c6..dafbedfe 100644 --- a/pkg/utils/testhelper/utils.go +++ b/pkg/utils/testhelper/utils.go @@ -9,10 +9,9 @@ import ( "github.com/google/go-cmp/cmp" "github.com/sdcio/data-server/mocks/mockschemaclientbound" - "github.com/sdcio/data-server/pkg/cache" + "github.com/sdcio/data-server/pkg/tree/types" sdcpb "github.com/sdcio/sdc-protos/sdcpb" "go.uber.org/mock/gomock" - "google.golang.org/protobuf/proto" ) const ( @@ -20,12 +19,12 @@ const ( ) // diffCacheUpdates takes two []*cache.Update and compares the diff -func DiffCacheUpdates(a, b []*cache.Update) string { +func DiffUpdates(a, b []*types.Update) string { return cmp.Diff(CacheUpdateSliceToStringSlice(a), CacheUpdateSliceToStringSlice(b)) } // CacheUpdateSliceToStringSlice converts a []*cache.Update to []string -func CacheUpdateSliceToStringSlice(s []*cache.Update) []string { +func CacheUpdateSliceToStringSlice(s []*types.Update) []string { result := make([]string, 0, len(s)) for _, e := range s { result = append(result, fmt.Sprintf("%v", e)) @@ -36,29 +35,18 @@ func CacheUpdateSliceToStringSlice(s []*cache.Update) []string { } // GetStringTvProto takes a string and returns the sdcpb.TypedValue for it in proto encoding as []byte -func GetStringTvProto(t *testing.T, s string) []byte { - result, err := proto.Marshal(&sdcpb.TypedValue{Value: &sdcpb.TypedValue_StringVal{StringVal: s}}) - if err != nil { - t.Error(err) - } - return result +func GetStringTvProto(s string) *sdcpb.TypedValue { + return &sdcpb.TypedValue{Value: &sdcpb.TypedValue_StringVal{StringVal: s}} } -func GetLeafListTvProto(t *testing.T, tvs []*sdcpb.TypedValue) []byte { - result, err := proto.Marshal(&sdcpb.TypedValue{Value: &sdcpb.TypedValue_LeaflistVal{LeaflistVal: &sdcpb.ScalarArray{Element: tvs}}}) - if err != nil { - t.Error(err) - } +func GetLeafListTvProto(tvs []*sdcpb.TypedValue) *sdcpb.TypedValue { + result := &sdcpb.TypedValue{Value: &sdcpb.TypedValue_LeaflistVal{LeaflistVal: &sdcpb.ScalarArray{Element: tvs}}} return result } // GetStringTvProto takes a string and returns the sdcpb.TypedValue for it in proto encoding as []byte -func GetUIntTvProto(t *testing.T, i uint64) []byte { - result, err := proto.Marshal(&sdcpb.TypedValue{Value: &sdcpb.TypedValue_UintVal{UintVal: uint64(i)}}) - if err != nil { - t.Error(err) - } - return result +func GetUIntTvProto(i uint64) *sdcpb.TypedValue { + return &sdcpb.TypedValue{Value: &sdcpb.TypedValue_UintVal{UintVal: uint64(i)}} } // PathMapIndex calculates a common map index for string slice based paths diff --git a/pkg/utils/value.go b/pkg/utils/value.go index 642b07c2..9e0db3a2 100644 --- a/pkg/utils/value.go +++ b/pkg/utils/value.go @@ -21,7 +21,10 @@ import ( "strings" "github.com/openconfig/gnmi/proto/gnmi" + "github.com/sdcio/data-server/pkg/cache" + "github.com/sdcio/schema-server/pkg/utils" sdcpb "github.com/sdcio/sdc-protos/sdcpb" + "google.golang.org/protobuf/proto" ) func GetValue(updValue *gnmi.TypedValue) (interface{}, error) { @@ -543,3 +546,24 @@ func ParseDecimal64(v string) (*sdcpb.Decimal64, error) { func BoolPtr(b bool) *bool { return &b } + +func SdcpbUpdateToCacheUpdate(upd *sdcpb.Update, owner string, prio int32) (*cache.Update, error) { + b, err := proto.Marshal(upd.Value) + if err != nil { + return nil, err + } + return cache.NewUpdate(utils.ToStrings(upd.GetPath(), false, false), b, prio, owner, 0), nil +} + +func SdcpbUpdatesToCacheUpdates(upds []*sdcpb.Update, owner string, prio int32) ([]*cache.Update, error) { + result := []*cache.Update{} + for _, upd := range upds { + cUpd, err := SdcpbUpdateToCacheUpdate(upd, owner, prio) + if err != nil { + return nil, err + } + result = append(result, cUpd) + } + + return result, nil +} diff --git a/proto/clang-format.style b/proto/clang-format.style new file mode 100644 index 00000000..cbced3b6 --- /dev/null +++ b/proto/clang-format.style @@ -0,0 +1,192 @@ +--- +Language: Proto +# BasedOnStyle: LLVM +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: None +AlignConsecutiveMacros: true +AlignConsecutiveAssignments: AcrossEmptyLinesAndComments +AlignConsecutiveBitFields: None +AlignConsecutiveDeclarations: true +AlignEscapedNewlines: Right +AlignOperands: Align +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortEnumsOnASingleLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +AttributeMacros: + - __capability +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: true +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +QualifierAlignment: Leave +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +PackConstructorInitializers: BinPack +BasedOnStyle: '' +ConstructorInitializerAllOnOneLineOrOnePerLine: false +AllowAllConstructorInitializersOnNextLine: true +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseLabels: false +IndentCaseBlocks: false +IndentGotoLabels: true +IndentPPDirectives: None +IndentExternBlock: AfterExternBlock +IndentRequires: false +IndentWidth: 2 +IndentWrappedFunctionNames: false +InsertTrailingCommas: None +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +LambdaBodyIndentation: Signature +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PenaltyIndentedWhitespace: 0 +PointerAlignment: Right +PPIndentWidth: -1 +ReferenceAlignment: Pointer +ReflowComments: true +RemoveBracesLLVM: false +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SortIncludes: CaseSensitive +SortJavaStaticImport: Before +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: true + AfterOverloadedOperator: false + BeforeNonEmptyParentheses: false +SpaceAroundPointerQualifiers: Default +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +BitFieldColonSpacing: Both +Standard: Latest +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseCRLF: false +UseTab: Never +WhitespaceSensitiveMacros: + - STRINGIZE + - PP_STRINGIZE + - BOOST_PP_STRINGIZE + - NS_SWIFT_NAME + - CF_SWIFT_NAME +... + diff --git a/proto/tree_persist.proto b/proto/tree_persist.proto new file mode 100644 index 00000000..ce221fa8 --- /dev/null +++ b/proto/tree_persist.proto @@ -0,0 +1,31 @@ +// Copyright 2025 Nokia +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package tree_persist.proto; + +option go_package = ".;tree_persist"; + +message Intent { + string intent_name = 1; + TreeElement root = 2; + int32 priority = 3; +} + +message TreeElement { + string name = 1; + repeated TreeElement childs = 4; + bytes leaf_variant = 5; +} diff --git a/tests/schema/sdcio_model.yang b/tests/schema/sdcio_model.yang index b3362403..86f6684d 100644 --- a/tests/schema/sdcio_model.yang +++ b/tests/schema/sdcio_model.yang @@ -72,7 +72,7 @@ module sdcio_model { } leaf-list rangetestLeaflist { type uint32 { - range "10..300 | 5000..5020 | 9999"; + range "10..300 | 5000..5020 | 9999"; } -} + } } diff --git a/tests/schema/sdcio_model_if.yang b/tests/schema/sdcio_model_if.yang index f5f3136c..fb7e21d2 100644 --- a/tests/schema/sdcio_model_if.yang +++ b/tests/schema/sdcio_model_if.yang @@ -81,6 +81,15 @@ module sdcio_model_if { description "A user-entered description of this network instance."; } + leaf admin-state { + type sdcio_model_common:admin-state; + default "enable"; + description + "The configured, desired state of the subinterface"; + must "((. = 'enable') and starts-with(../../name, 'system0')) or not(starts-with(../../name, 'system0'))" { + error-message "admin-state must be enable"; + } + } } } } diff --git a/tests/sdcioygot/sdcio_schema.go b/tests/sdcioygot/sdcio_schema.go index 9273d279..cb6af6a4 100644 --- a/tests/sdcioygot/sdcio_schema.go +++ b/tests/sdcioygot/sdcio_schema.go @@ -4,7 +4,7 @@ of structs which represent a YANG schema. The generated schema can be compressed by a series of transformations (compression was false in this case). -This package was generated by /home/mava/go/pkg/mod/github.com/openconfig/ygot@v0.29.20/genutil/names.go +This package was generated by /home/mava/go/pkg/mod/github.com/openconfig/ygot@v0.29.22/genutil/names.go using the following YANG input files: - ./tests/schema/sdcio_model.yang - ./tests/schema/sdcio_model_choice.yang @@ -592,6 +592,7 @@ func (*SdcioModel_Interface) ΛBelongingModule() string { // SdcioModel_Interface_Subinterface represents the /sdcio_model/interface/subinterface YANG schema element. type SdcioModel_Interface_Subinterface struct { + AdminState E_SdcioModelIf_AdminState `path:"admin-state" module:"sdcio_model"` Description *string `path:"description" module:"sdcio_model"` Index *uint32 `path:"index" module:"sdcio_model"` Type E_SdcioModelCommon_SiType `path:"type" module:"sdcio_model"` @@ -1364,334 +1365,341 @@ var ( // contents of a goyang yang.Entry struct, which defines the schema for the // fields within the struct. ySchema = []byte{ - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x3d, 0x7b, 0x73, 0xda, 0xca, - 0x7e, 0xff, 0xe7, 0x53, 0xec, 0x51, 0xdb, 0x31, 0xb4, 0x96, 0x11, 0x60, 0x48, 0x70, 0xc7, 0x93, - 0xda, 0x39, 0x39, 0xa7, 0x9e, 0x1b, 0x9f, 0x9b, 0x49, 0xce, 0xbd, 0x7f, 0xd4, 0xa6, 0xae, 0x80, - 0x05, 0xef, 0x44, 0x48, 0x54, 0x5a, 0x39, 0x71, 0x63, 0xf7, 0xb3, 0xdf, 0xd1, 0x03, 0x21, 0x21, - 0x69, 0xb5, 0xbb, 0x5a, 0x21, 0x01, 0x3b, 0x73, 0xc6, 0x87, 0xc0, 0x4a, 0xfb, 0xf8, 0xbd, 0x9f, - 0xfb, 0xf3, 0x0d, 0x00, 0x00, 0x28, 0x7f, 0xe8, 0x4b, 0xa8, 0x5c, 0x00, 0x65, 0x06, 0x9f, 0xd0, - 0x14, 0x2a, 0xa7, 0xc1, 0xb7, 0x7f, 0x41, 0xe6, 0x4c, 0xb9, 0x00, 0xdd, 0xf0, 0x9f, 0x1f, 0x2c, - 0x73, 0x8e, 0x16, 0xca, 0x05, 0xd0, 0xc2, 0x2f, 0x7e, 0x45, 0xb6, 0x72, 0x01, 0x82, 0x57, 0xf8, - 0x5f, 0x4c, 0x1f, 0x2d, 0x34, 0x85, 0x4e, 0xe2, 0xcb, 0xc4, 0xfb, 0xd7, 0x03, 0x4e, 0x93, 0x3f, - 0x27, 0x27, 0x8a, 0xbe, 0xde, 0x9e, 0x30, 0xfa, 0xe1, 0xb3, 0x0d, 0xe7, 0xe8, 0x47, 0x6a, 0x9a, - 0xc4, 0x54, 0xce, 0x6c, 0x8a, 0xac, 0x87, 0xa5, 0x35, 0x83, 0xc6, 0x43, 0x30, 0xed, 0xd6, 0xac, - 0xfe, 0xe8, 0xaf, 0x96, 0x6b, 0x4f, 0x61, 0xe6, 0x9b, 0x82, 0x95, 0xc1, 0xe7, 0xef, 0x96, 0xed, - 0x2d, 0x4e, 0x59, 0x05, 0x93, 0x9e, 0x66, 0x0f, 0xfc, 0x4f, 0xdd, 0xb9, 0xb2, 0x17, 0xee, 0x12, - 0x9a, 0x58, 0xb9, 0x00, 0xd8, 0x76, 0x61, 0xce, 0xc0, 0xd8, 0xa8, 0xac, 0x35, 0xa6, 0x1e, 0x7a, - 0x4d, 0x7c, 0xf3, 0xba, 0x75, 0x12, 0xdb, 0x20, 0xd8, 0x02, 0xc5, 0x54, 0x77, 0x08, 0x9b, 0x4b, - 0x42, 0xc5, 0x1f, 0x9b, 0xb3, 0xe8, 0x10, 0x40, 0x83, 0x9c, 0x9f, 0xf3, 0x00, 0x45, 0x03, 0x30, - 0x3e, 0xc0, 0xd1, 0x02, 0x90, 0x19, 0x90, 0xcc, 0x00, 0xe5, 0x06, 0x6c, 0x36, 0x80, 0x73, 0x00, - 0x5d, 0x08, 0xf0, 0x0d, 0xe0, 0x75, 0x07, 0x76, 0x8b, 0xcf, 0x23, 0x82, 0xbd, 0x3f, 0xbc, 0x60, - 0x6b, 0x21, 0xf8, 0xcf, 0x0b, 0x86, 0x15, 0xa1, 0x01, 0x0b, 0x3a, 0x94, 0x43, 0x0b, 0x56, 0xf4, - 0xe0, 0x46, 0x13, 0x6e, 0x74, 0x29, 0x8d, 0x36, 0x64, 0xf4, 0x29, 0x40, 0x23, 0x6a, 0x74, 0x62, - 0x44, 0x2b, 0x2e, 0xf4, 0x2a, 0x10, 0x03, 0xa5, 0xd1, 0x8d, 0x07, 0xed, 0xc4, 0xa0, 0x1f, 0x2f, - 0x1a, 0x96, 0x46, 0xc7, 0xd2, 0x68, 0x29, 0x0c, 0x3d, 0xe9, 0xd0, 0x94, 0x12, 0x5d, 0x99, 0xd1, - 0x36, 0x81, 0xbe, 0x2a, 0x34, 0xe0, 0x92, 0x1d, 0x06, 0x71, 0x54, 0x0e, 0x5e, 0xc1, 0x78, 0x84, - 0x6c, 0x68, 0xcd, 0x8d, 0xde, 0x65, 0xd0, 0x5c, 0x2c, 0xba, 0x97, 0x45, 0x7b, 0x61, 0xe8, 0x2f, - 0x8c, 0x0c, 0x84, 0x93, 0x03, 0x1b, 0x59, 0x30, 0x92, 0x07, 0x37, 0x99, 0x44, 0x0f, 0x72, 0x51, - 0x4a, 0x0a, 0x89, 0x38, 0x88, 0x65, 0x9b, 0x68, 0x34, 0xce, 0xc7, 0x79, 0x89, 0x47, 0x04, 0x11, - 0x55, 0x43, 0x4c, 0xa2, 0x88, 0x4a, 0x38, 0x71, 0x09, 0x27, 0xb2, 0xca, 0x88, 0x8d, 0x8f, 0xe8, - 0x38, 0x89, 0x2f, 0xda, 0xc5, 0x9f, 0xcf, 0x2b, 0x28, 0x08, 0x8f, 0xb0, 0x8d, 0xcc, 0x45, 0x19, - 0xdc, 0x59, 0x8b, 0xa2, 0x77, 0x6f, 0x76, 0x73, 0x6e, 0xd5, 0xb2, 0xb7, 0x2b, 0xd3, 0xb4, 0xb0, - 0x8e, 0x91, 0x65, 0xf2, 0x71, 0x39, 0x67, 0xfa, 0x08, 0x97, 0xfa, 0x4a, 0xc7, 0x8f, 0xde, 0xe9, - 0x76, 0x62, 0x28, 0xd6, 0x09, 0x1d, 0x15, 0x9d, 0x8d, 0x69, 0xdc, 0xf1, 0x55, 0xd9, 0xd8, 0x5f, - 0x95, 0x93, 0xbd, 0x79, 0x60, 0x74, 0xa7, 0xd8, 0x0c, 0x81, 0xfa, 0xd5, 0x9b, 0xf5, 0xd6, 0xc7, - 0xeb, 0x0f, 0xc1, 0xa4, 0x0f, 0x1f, 0xbc, 0x39, 0xfc, 0xbf, 0x1f, 0xbd, 0x29, 0xde, 0x54, 0x73, - 0xe2, 0x0c, 0xa7, 0xad, 0x18, 0xd6, 0x82, 0x5f, 0x71, 0xf2, 0x1e, 0x66, 0x15, 0x5c, 0x70, 0xae, - 0xbb, 0x86, 0x47, 0xf8, 0x77, 0xec, 0xe7, 0x3b, 0xd7, 0x0d, 0x87, 0x91, 0x3b, 0x8c, 0xf9, 0x54, - 0x3a, 0x4d, 0xaa, 0x74, 0x52, 0xa5, 0xdb, 0x2d, 0xcf, 0xe3, 0x96, 0x26, 0x11, 0x1e, 0x4c, 0x2c, - 0xcb, 0x80, 0xba, 0xc9, 0x03, 0xfc, 0xb5, 0xf8, 0xe8, 0x56, 0xc5, 0x92, 0x84, 0x1a, 0x89, 0xf0, - 0x07, 0xb6, 0x75, 0xd5, 0x35, 0x1d, 0xac, 0x4f, 0x0c, 0xb6, 0x43, 0xf3, 0x70, 0xd3, 0x81, 0xa6, - 0x8f, 0xe8, 0x6c, 0x1c, 0xa8, 0x04, 0x60, 0x7c, 0xd1, 0x02, 0x90, 0x03, 0xf4, 0x29, 0x46, 0x4f, - 0x0d, 0xa0, 0xce, 0xe0, 0x04, 0x9a, 0x44, 0x9f, 0xdb, 0x47, 0x54, 0x35, 0x71, 0x52, 0x8f, 0x1e, - 0x0b, 0xc5, 0x5c, 0x4e, 0xc5, 0x86, 0x5f, 0xa1, 0x61, 0xf1, 0x65, 0xd1, 0xa9, 0x2f, 0x74, 0xa0, - 0x79, 0xad, 0xd8, 0xbd, 0xc9, 0x78, 0x90, 0x3c, 0x07, 0x48, 0xde, 0x68, 0xfe, 0xf2, 0x09, 0x4b, - 0xf7, 0xd1, 0xbc, 0xc7, 0xe6, 0xc3, 0xef, 0x49, 0x1f, 0x7e, 0x59, 0x15, 0xe3, 0x28, 0x7c, 0xf8, - 0x3d, 0x3e, 0x1f, 0x7e, 0x4f, 0xfa, 0xf0, 0xa5, 0x0f, 0xbf, 0x66, 0x1f, 0xbe, 0x34, 0x42, 0xa5, - 0x11, 0x2a, 0x8d, 0x50, 0x69, 0x84, 0x4a, 0x23, 0x54, 0x1a, 0xa1, 0xd2, 0x08, 0x3d, 0x20, 0x23, - 0xb4, 0xd7, 0x61, 0x51, 0x31, 0xe9, 0x8d, 0xd0, 0xde, 0xb1, 0x18, 0xa1, 0x3d, 0x6e, 0x23, 0x94, - 0x29, 0xf7, 0x8c, 0x72, 0x23, 0x6c, 0x1b, 0xc8, 0x5e, 0xfa, 0x2b, 0x63, 0x1e, 0x64, 0xc1, 0xd2, - 0x28, 0x96, 0x94, 0x95, 0x1d, 0x5a, 0x84, 0x65, 0xc9, 0xb5, 0x6f, 0x56, 0x18, 0x5b, 0x9d, 0x32, - 0xb3, 0xdc, 0x89, 0x01, 0xbf, 0xc1, 0xe7, 0xfc, 0xac, 0xd8, 0xcd, 0x90, 0x1d, 0xe7, 0xc5, 0xe6, - 0x4d, 0x0c, 0x9a, 0x95, 0x1a, 0xbb, 0x59, 0xa6, 0xb0, 0xec, 0x58, 0xcb, 0x9f, 0xa5, 0x28, 0x2f, - 0xd6, 0x1b, 0x45, 0xce, 0x88, 0xed, 0xee, 0x3a, 0x23, 0x96, 0x04, 0x32, 0x56, 0x09, 0x5c, 0x63, - 0x52, 0x2c, 0x01, 0xa4, 0x7c, 0xbc, 0xa9, 0x30, 0x2f, 0xf6, 0x49, 0x37, 0x5c, 0x96, 0xc4, 0xd8, - 0x70, 0x3c, 0x9d, 0x57, 0x4d, 0x6b, 0x8a, 0x57, 0x8d, 0x06, 0x3d, 0x0e, 0xc0, 0xb1, 0x46, 0x81, - 0x3e, 0x62, 0x64, 0x37, 0xb5, 0xa5, 0xc3, 0x9e, 0xa4, 0x41, 0x99, 0x8c, 0xc1, 0xe7, 0x41, 0xf6, - 0xb1, 0xb7, 0xc7, 0x88, 0xed, 0x3d, 0x89, 0xed, 0x12, 0xdb, 0x1b, 0x8b, 0xed, 0xbb, 0x56, 0x55, - 0xa3, 0x53, 0xef, 0x10, 0xf4, 0x00, 0xb2, 0x9a, 0xf8, 0xeb, 0xfa, 0x15, 0x0f, 0x1f, 0xbc, 0x57, - 0xd0, 0x6a, 0xba, 0x19, 0xca, 0xd8, 0x37, 0xf8, 0xdc, 0x2d, 0x56, 0x58, 0xfc, 0x51, 0x64, 0x85, - 0x45, 0x93, 0x0a, 0x4b, 0x33, 0x14, 0x96, 0x42, 0x5a, 0xa3, 0xa7, 0xb1, 0x02, 0xda, 0xa2, 0xc6, - 0xb0, 0x1e, 0x15, 0x86, 0xf5, 0x24, 0x86, 0x49, 0x0c, 0xe3, 0xc1, 0xb0, 0xa5, 0x6e, 0xce, 0x74, - 0x6c, 0x15, 0x23, 0xd9, 0x7a, 0xa0, 0xc4, 0xb3, 0xbd, 0xc0, 0xb3, 0xdb, 0x00, 0x5c, 0xf6, 0x33, - 0xc1, 0x1e, 0xae, 0x15, 0x17, 0x89, 0x3e, 0x82, 0xbf, 0xf8, 0x9e, 0x19, 0x5f, 0x74, 0x82, 0x0c, - 0xee, 0xa6, 0x7c, 0x42, 0x0e, 0xbe, 0xc2, 0x38, 0xc7, 0x91, 0x70, 0x8b, 0xcc, 0x8f, 0x06, 0xf4, - 0x8e, 0xd9, 0xc9, 0xc6, 0x38, 0xe5, 0x56, 0xff, 0x11, 0x1b, 0xd1, 0x7d, 0x77, 0x7e, 0x3e, 0x7c, - 0x7b, 0x7e, 0xae, 0xbd, 0xed, 0xbf, 0xd5, 0x46, 0x83, 0x41, 0x77, 0xd8, 0xcd, 0x28, 0xaa, 0x55, - 0xfe, 0x6a, 0xcf, 0xa0, 0x0d, 0x67, 0xd7, 0xde, 0xd2, 0x4c, 0xd7, 0x30, 0x48, 0x43, 0xfe, 0xe6, - 0x40, 0x6f, 0x71, 0x7e, 0xc0, 0xb1, 0x2a, 0x1f, 0x19, 0xd1, 0x51, 0x54, 0xac, 0xfe, 0xd0, 0xf8, - 0xc9, 0xe0, 0x72, 0x85, 0x9f, 0xa7, 0x96, 0x39, 0xcf, 0xf7, 0x93, 0x6d, 0x86, 0x64, 0xfb, 0xc9, - 0xb4, 0x8a, 0xfc, 0x64, 0x8d, 0xf6, 0x8e, 0xb1, 0xfa, 0xc4, 0x72, 0x29, 0x31, 0x79, 0xcc, 0x59, - 0x7b, 0x5e, 0x13, 0x5e, 0x9f, 0x02, 0x9c, 0x68, 0x06, 0x4d, 0x8c, 0xf0, 0xb3, 0x0d, 0x09, 0x00, - 0x8d, 0x0f, 0xda, 0x99, 0xeb, 0x73, 0x3d, 0x69, 0x53, 0xa1, 0x1a, 0xad, 0x4f, 0x98, 0xb3, 0xd3, - 0x7e, 0x5e, 0x61, 0xeb, 0x8a, 0xc2, 0xdf, 0x19, 0x0e, 0x6c, 0x8c, 0xdc, 0x25, 0x80, 0x6a, 0x2f, - 0x04, 0x6e, 0x3e, 0x28, 0xab, 0xd6, 0xe8, 0xf2, 0x29, 0x2b, 0x93, 0xca, 0x06, 0x84, 0x31, 0x37, - 0xe1, 0xab, 0xae, 0x49, 0xbd, 0x24, 0x72, 0x70, 0x49, 0xd5, 0x8d, 0x22, 0xe7, 0x80, 0xf2, 0x77, - 0xdd, 0x70, 0xfd, 0x9e, 0x21, 0xc5, 0x31, 0x6e, 0xc6, 0xc4, 0xb5, 0xd9, 0x76, 0x84, 0x86, 0x33, - 0x1c, 0xca, 0x3e, 0x6d, 0xbf, 0x8e, 0x79, 0x2d, 0xfc, 0x08, 0xed, 0x2b, 0x63, 0x61, 0xd5, 0x31, - 0xb9, 0xed, 0xe8, 0x65, 0xbd, 0x53, 0x63, 0x56, 0x2a, 0xa1, 0xb2, 0x3a, 0x02, 0x44, 0xbc, 0xa6, - 0xe5, 0x7e, 0xd7, 0x92, 0xfb, 0x49, 0xee, 0x27, 0xb9, 0x9f, 0xe4, 0x7e, 0x7b, 0xc8, 0xfd, 0xaa, - 0xb2, 0xfe, 0xc8, 0xe4, 0x94, 0x6f, 0xff, 0xdd, 0xc4, 0x9e, 0xa3, 0x31, 0x19, 0x4c, 0x0c, 0xed, - 0xb9, 0x9e, 0xc1, 0xce, 0x36, 0x84, 0x1d, 0x0d, 0xd9, 0x71, 0xa6, 0x04, 0x9a, 0x37, 0x3b, 0x45, - 0x02, 0xcd, 0x85, 0x99, 0x0b, 0xfa, 0x6c, 0x89, 0x4c, 0xd5, 0xc1, 0x3a, 0xa6, 0x68, 0x1d, 0x16, - 0x1f, 0x9c, 0x17, 0x99, 0xa7, 0x48, 0xa3, 0x56, 0xa0, 0xe9, 0xa7, 0x47, 0x66, 0x0e, 0x18, 0x37, - 0xcc, 0x0f, 0x88, 0xe6, 0xfb, 0xed, 0x00, 0xcc, 0xc2, 0x95, 0xaa, 0x25, 0x72, 0x31, 0x9e, 0x24, - 0xc8, 0x98, 0x50, 0xa5, 0xa4, 0x7c, 0x34, 0xdd, 0xe2, 0xde, 0x24, 0xca, 0x9f, 0xd6, 0xd7, 0xc0, - 0x91, 0x48, 0x15, 0xce, 0xec, 0xfa, 0x7e, 0x8f, 0x00, 0x07, 0x29, 0x62, 0x99, 0x3d, 0x5f, 0xd0, - 0x21, 0x27, 0x1f, 0x67, 0xa9, 0x23, 0xae, 0xd6, 0x0d, 0x21, 0x13, 0x29, 0x31, 0x74, 0x3d, 0xdf, - 0x05, 0xe8, 0x51, 0x2c, 0x31, 0xdc, 0xcc, 0x05, 0xe8, 0xee, 0x24, 0xde, 0xca, 0x90, 0xe6, 0xac, - 0x2c, 0x5d, 0xa7, 0xb8, 0xa8, 0x82, 0x21, 0x06, 0xdd, 0x6a, 0x9d, 0x81, 0x4b, 0x70, 0x12, 0xec, - 0xf8, 0xa4, 0x0d, 0x74, 0x73, 0x06, 0x1c, 0xac, 0xdb, 0xd8, 0x51, 0xbf, 0x23, 0xfc, 0xd8, 0x3a, - 0x3b, 0xeb, 0x78, 0xc2, 0xe9, 0x14, 0x9c, 0x38, 0xcf, 0x0e, 0x86, 0x4b, 0xed, 0xa4, 0xdd, 0x06, - 0x96, 0x0d, 0x4c, 0x0b, 0xb7, 0x8a, 0xc6, 0xd1, 0x60, 0xc3, 0x47, 0xdb, 0xb6, 0xec, 0x5b, 0xe8, - 0x38, 0xfa, 0x02, 0xb2, 0x57, 0x57, 0xc5, 0x28, 0x03, 0x78, 0x27, 0x03, 0x26, 0x10, 0x50, 0x63, - 0x22, 0xe0, 0xcc, 0xb7, 0x8e, 0x73, 0x1f, 0xe8, 0x2d, 0x5f, 0x5d, 0x86, 0xeb, 0x67, 0xc8, 0xc1, - 0x2d, 0x93, 0x5c, 0x9d, 0x60, 0x4e, 0x84, 0x23, 0xd8, 0x4d, 0x02, 0x6f, 0xee, 0xaf, 0x84, 0x92, - 0x1c, 0xc5, 0x86, 0x73, 0x68, 0x53, 0xa5, 0xe7, 0x33, 0xe0, 0xf2, 0x97, 0xdf, 0x3e, 0x80, 0xde, - 0xbb, 0x61, 0xff, 0x02, 0xfc, 0xf9, 0x08, 0xc1, 0xcd, 0x5a, 0xdd, 0x71, 0xc0, 0xef, 0xb6, 0xe5, - 0xae, 0xc0, 0xed, 0xcd, 0x35, 0x50, 0x01, 0x9a, 0x5f, 0x79, 0x27, 0xf6, 0x15, 0xeb, 0xd8, 0x75, - 0x2a, 0x4e, 0x7d, 0xd9, 0xec, 0x72, 0x97, 0xd9, 0x2f, 0x1c, 0xc7, 0x50, 0x1d, 0x0e, 0x94, 0x70, - 0x3e, 0xcc, 0xa0, 0x33, 0xb5, 0xd1, 0x8a, 0x98, 0x87, 0x12, 0xb7, 0x9c, 0xa2, 0xc1, 0x52, 0xe5, - 0xd9, 0x7b, 0x95, 0xa7, 0x18, 0x9c, 0xc9, 0x68, 0x26, 0x61, 0xcc, 0x27, 0x68, 0x2e, 0x7c, 0xbb, - 0xac, 0x34, 0x9f, 0xb9, 0x45, 0xf4, 0xd5, 0x1c, 0x81, 0x6f, 0x82, 0xa1, 0x98, 0xf7, 0x37, 0x5b, - 0x9f, 0x7a, 0xdb, 0xfd, 0x15, 0x2d, 0x50, 0x5e, 0x3c, 0x34, 0xfb, 0xc8, 0xe0, 0x42, 0xf7, 0x4b, - 0x69, 0xb2, 0xc2, 0x98, 0x9c, 0x16, 0xbc, 0x72, 0xab, 0xff, 0x60, 0xdf, 0x6a, 0x6f, 0x30, 0x68, - 0xde, 0x66, 0x6b, 0x60, 0x5c, 0x91, 0xa1, 0xad, 0x62, 0x12, 0xc2, 0xa7, 0x0d, 0xf3, 0x60, 0xbc, - 0x64, 0x5f, 0x7b, 0xcf, 0xbe, 0x76, 0x94, 0x13, 0x84, 0x5d, 0x8a, 0x7c, 0x20, 0xec, 0x4a, 0x8c, - 0xda, 0x7f, 0x8c, 0x72, 0x91, 0x89, 0xbb, 0x43, 0x0a, 0x8c, 0x1a, 0x12, 0x86, 0x7c, 0xd1, 0xcd, - 0x05, 0xac, 0x4d, 0x12, 0x6a, 0xc7, 0x23, 0x09, 0x87, 0x83, 0x41, 0x5f, 0xca, 0x42, 0x6f, 0x91, - 0xa1, 0x67, 0xbb, 0x80, 0x49, 0xf9, 0xa3, 0x24, 0x97, 0xda, 0x7b, 0x2e, 0xb5, 0xd1, 0x64, 0x08, - 0x10, 0xdd, 0x17, 0xcd, 0xbd, 0x7f, 0x44, 0x9a, 0xbb, 0x76, 0x38, 0xcc, 0x8a, 0x80, 0x50, 0x9f, - 0x75, 0x8c, 0xa1, 0x6d, 0x16, 0x62, 0x94, 0xd2, 0x5a, 0x2e, 0x96, 0x58, 0x7b, 0x09, 0x5d, 0x99, - 0x2f, 0x86, 0xd5, 0xd2, 0x5e, 0xba, 0x77, 0x9a, 0x3a, 0x1a, 0xfb, 0x7f, 0x5e, 0x7a, 0xad, 0x3b, - 0x4d, 0x3d, 0x0f, 0xff, 0x31, 0xb8, 0xd3, 0xd4, 0xc1, 0xb8, 0xfd, 0x72, 0xd7, 0x8d, 0x7e, 0xf7, - 0x3f, 0xb6, 0x5f, 0x20, 0x7e, 0x84, 0xb6, 0x09, 0xb1, 0xda, 0xf2, 0xbf, 0x68, 0xdd, 0xdf, 0xcf, - 0xda, 0x3f, 0xb5, 0xd3, 0xee, 0x6b, 0xab, 0x73, 0xa7, 0x4f, 0xa6, 0xb3, 0x71, 0xfb, 0x7d, 0xab, - 0xb3, 0xf5, 0x53, 0xfb, 0x7d, 0xa7, 0xb5, 0x3d, 0xbc, 0xfd, 0xd2, 0xf2, 0x66, 0xef, 0x8e, 0xbd, - 0x6f, 0x5e, 0x5a, 0xdd, 0xde, 0x9d, 0xa6, 0xbe, 0x1b, 0xb7, 0xdb, 0xed, 0x17, 0x64, 0x4f, 0xf8, - 0x96, 0x66, 0xe8, 0x8b, 0xad, 0x69, 0x7a, 0xfe, 0x34, 0x9a, 0xa6, 0xb5, 0xdb, 0x6d, 0xa5, 0x12, - 0x59, 0xe0, 0xb8, 0x93, 0xfc, 0x30, 0x65, 0x9a, 0x17, 0xc7, 0x47, 0x37, 0xac, 0x90, 0x58, 0xca, - 0x86, 0xf5, 0x0c, 0x85, 0xa5, 0xc3, 0x34, 0x5e, 0x3c, 0x4e, 0xf7, 0x0f, 0x68, 0x62, 0x59, 0x25, - 0x11, 0x35, 0x44, 0x38, 0x95, 0x6b, 0xae, 0xa7, 0x24, 0xa1, 0x0e, 0x25, 0xef, 0x16, 0x5e, 0x48, - 0x49, 0x8f, 0x2e, 0xb4, 0xba, 0x07, 0xab, 0x0e, 0x42, 0xaf, 0x8b, 0x70, 0xe9, 0x24, 0xbc, 0x5e, - 0xc5, 0xd2, 0x72, 0x9b, 0x5f, 0x7e, 0x33, 0xe8, 0x2c, 0x5c, 0xba, 0x0b, 0xbf, 0xf7, 0xb1, 0x09, - 0x87, 0x22, 0x28, 0x34, 0x37, 0xae, 0xa0, 0xfa, 0x1d, 0x99, 0x33, 0xf8, 0x83, 0x9e, 0x55, 0x07, - 0xc3, 0x25, 0x93, 0x96, 0x4c, 0x9a, 0xc2, 0x83, 0xd5, 0xef, 0x31, 0xf0, 0xe7, 0xb7, 0x14, 0x43, - 0xe9, 0x3c, 0x5a, 0xbb, 0xe6, 0xce, 0x9a, 0xe4, 0xce, 0xdb, 0x47, 0x32, 0x1a, 0x8d, 0x46, 0x92, - 0x3d, 0x33, 0x91, 0x18, 0x47, 0xf7, 0x43, 0xba, 0xf4, 0x20, 0x0e, 0x72, 0x48, 0xa6, 0x0b, 0x69, - 0x99, 0x79, 0x42, 0x0c, 0xa9, 0x42, 0x9c, 0xd9, 0x42, 0xd1, 0x72, 0xb8, 0xb2, 0x86, 0x88, 0x16, - 0x26, 0xf0, 0xe5, 0x58, 0x94, 0x41, 0xa3, 0x81, 0xb9, 0x65, 0x83, 0x70, 0x71, 0xa0, 0xc8, 0x0e, - 0x15, 0x25, 0x41, 0xf2, 0xa4, 0x09, 0x6f, 0x96, 0x51, 0x69, 0x01, 0x93, 0x2f, 0x6c, 0xd8, 0x8f, - 0xae, 0xde, 0x8e, 0xa3, 0xb5, 0xa8, 0x52, 0x98, 0x46, 0x48, 0x46, 0xe8, 0x48, 0x08, 0xfe, 0x4a, - 0x45, 0x4a, 0x2a, 0x52, 0x8c, 0x05, 0x3a, 0x29, 0x6b, 0x97, 0xc2, 0x3a, 0x62, 0x2b, 0xd8, 0x49, - 0x23, 0x06, 0x52, 0x29, 0x70, 0x38, 0xa9, 0x1a, 0x38, 0x4c, 0xfd, 0x79, 0x39, 0x19, 0xfd, 0xc4, - 0x46, 0xb3, 0x05, 0x9c, 0x29, 0x55, 0xa8, 0x45, 0x9c, 0x4b, 0xb2, 0x2d, 0x17, 0x33, 0xad, 0x88, - 0x6a, 0xe4, 0xb8, 0x62, 0x64, 0x6d, 0xa4, 0x4a, 0x92, 0xab, 0x5b, 0x20, 0x7b, 0x72, 0xe2, 0x6b, - 0x20, 0xb9, 0x23, 0xd6, 0x0e, 0xfa, 0x82, 0x61, 0x86, 0xbe, 0x28, 0x1a, 0x81, 0xe6, 0x45, 0x53, - 0x99, 0xab, 0x82, 0x11, 0x4f, 0x8f, 0x66, 0x2d, 0x6a, 0x90, 0x47, 0xb3, 0x9e, 0x92, 0x06, 0x1c, - 0x77, 0xb5, 0xb2, 0x6c, 0x0c, 0x67, 0xc0, 0x32, 0x01, 0x7e, 0x44, 0x8e, 0x54, 0x7c, 0x52, 0xc2, - 0x81, 0xea, 0xb0, 0xf6, 0xa1, 0xb9, 0x3a, 0x0f, 0xa5, 0x9d, 0x81, 0x5f, 0x2e, 0xc1, 0x89, 0x61, - 0x4d, 0x75, 0x43, 0x5d, 0x22, 0x1f, 0x36, 0x33, 0xe8, 0x84, 0xd4, 0xd3, 0x12, 0x42, 0x65, 0x35, - 0xe0, 0x7f, 0x6a, 0x3f, 0x00, 0x39, 0x92, 0x1e, 0x28, 0xe9, 0x81, 0xeb, 0xf0, 0x0e, 0xd5, 0x14, - 0x60, 0x8a, 0x9c, 0x85, 0xbd, 0x93, 0x48, 0xfe, 0x53, 0x72, 0xff, 0xa4, 0xb8, 0xaf, 0x8a, 0xdc, - 0x47, 0x29, 0xee, 0xc2, 0x89, 0x8d, 0x3c, 0xd7, 0x46, 0xa4, 0xea, 0xed, 0xe2, 0x3e, 0x4a, 0xe9, - 0xa1, 0xb9, 0xfd, 0x94, 0x8a, 0x4e, 0x43, 0x40, 0xcf, 0xc9, 0x08, 0xbf, 0x3a, 0x14, 0xa1, 0x63, - 0x40, 0x2e, 0xbe, 0x5d, 0x3f, 0xfd, 0xf0, 0x35, 0xfe, 0x2a, 0x91, 0x3d, 0xb3, 0x32, 0x12, 0x64, - 0x64, 0xbb, 0xac, 0x04, 0x18, 0xd9, 0xca, 0xa5, 0xb3, 0x81, 0x94, 0x5d, 0x2c, 0x6d, 0x40, 0x7d, - 0x6e, 0x20, 0x07, 0xe7, 0xd7, 0x4a, 0x47, 0x23, 0x76, 0x5c, 0x2a, 0x9d, 0x33, 0x2f, 0x68, 0x56, - 0xc1, 0x74, 0xb4, 0x4a, 0x51, 0x65, 0xd3, 0xd0, 0xc4, 0xf6, 0x73, 0x71, 0x4a, 0x48, 0x30, 0xac, - 0x61, 0x79, 0x82, 0x04, 0x90, 0xb1, 0x8a, 0xff, 0x1a, 0x33, 0x42, 0xf2, 0x41, 0xca, 0x27, 0xdd, - 0x76, 0x98, 0x2b, 0x2f, 0x50, 0x76, 0xf6, 0xa8, 0x65, 0x67, 0x7f, 0xe7, 0x82, 0x73, 0x77, 0x2d, - 0x2b, 0x48, 0x4c, 0x28, 0x97, 0x01, 0x7f, 0xca, 0x44, 0xa0, 0x7c, 0xfe, 0x6b, 0xc3, 0xb9, 0x6a, - 0xf9, 0x29, 0x1b, 0xba, 0x41, 0xe6, 0xc3, 0x89, 0x91, 0xbb, 0x6d, 0x5e, 0xf8, 0x10, 0xce, 0xdf, - 0x7c, 0x76, 0x9c, 0x6a, 0x13, 0x02, 0x04, 0x34, 0x33, 0x24, 0xec, 0x7e, 0x4d, 0x93, 0x6f, 0xb3, - 0xd4, 0x85, 0x10, 0x58, 0x37, 0xa6, 0x83, 0xf5, 0xa0, 0xc6, 0x37, 0x7b, 0x57, 0xca, 0xe7, 0x35, - 0xfa, 0x6d, 0x74, 0x37, 0x1f, 0xb5, 0x68, 0x70, 0x08, 0x39, 0x58, 0x75, 0x4d, 0xf4, 0xbf, 0x2e, - 0xa1, 0xe5, 0x49, 0x7c, 0xd0, 0xae, 0x25, 0x39, 0x72, 0xf0, 0x43, 0xe6, 0xd4, 0x4d, 0xc3, 0x9e, - 0xd8, 0x42, 0x45, 0xc9, 0x73, 0x07, 0xda, 0x4f, 0xd0, 0xa6, 0xc8, 0xf1, 0x0c, 0xc6, 0x35, 0x2c, - 0xbb, 0x93, 0x0c, 0xba, 0x7d, 0x12, 0xea, 0x24, 0xd0, 0xf2, 0xc9, 0xf5, 0xc2, 0x7c, 0x4f, 0xb4, - 0x62, 0xc8, 0x1d, 0x5a, 0xed, 0x5b, 0xbc, 0x8b, 0x0e, 0x35, 0x0e, 0x20, 0xf0, 0x45, 0x85, 0x3a, - 0x75, 0x45, 0xc0, 0x9a, 0x71, 0x4d, 0x0c, 0xb1, 0xb4, 0x29, 0xb5, 0xe6, 0x82, 0x82, 0x18, 0x89, - 0xed, 0x12, 0xdb, 0x1b, 0x8d, 0xed, 0x2b, 0xcb, 0xc6, 0xf4, 0xd8, 0xee, 0x8f, 0x96, 0xd8, 0x2e, - 0xb1, 0x9d, 0x2b, 0x4d, 0x94, 0x58, 0xe8, 0xbc, 0x8d, 0x3f, 0x43, 0x99, 0x26, 0x4a, 0x1d, 0xf1, - 0x6a, 0x7c, 0x9a, 0x28, 0x43, 0xe1, 0x74, 0x13, 0x8e, 0x65, 0x1f, 0x03, 0x4e, 0xa4, 0x5a, 0xeb, - 0x6a, 0xe3, 0x4d, 0x74, 0x01, 0x89, 0x7a, 0xe2, 0x4f, 0x2c, 0x3d, 0xf8, 0x22, 0xaf, 0x83, 0xb0, - 0xce, 0x65, 0x68, 0x05, 0x28, 0x24, 0x66, 0x59, 0xb1, 0x43, 0x2d, 0xdb, 0xc4, 0x89, 0x9d, 0xf5, - 0xc6, 0x76, 0xdd, 0x9b, 0xa0, 0xba, 0x28, 0x63, 0xcc, 0xa5, 0xd4, 0x21, 0xba, 0x2f, 0x0a, 0xdc, - 0xa5, 0xc8, 0xc1, 0x7f, 0xf3, 0xdf, 0xf2, 0xf0, 0x35, 0x78, 0x4b, 0xed, 0x5e, 0xdf, 0x5c, 0x5f, - 0x19, 0xed, 0x4e, 0x68, 0xdc, 0x76, 0xcb, 0xc5, 0x12, 0xab, 0x14, 0xcd, 0x8a, 0xb7, 0xc6, 0xed, - 0xfa, 0x6e, 0x67, 0xd8, 0x78, 0xa7, 0x6f, 0xb0, 0x44, 0x51, 0x0e, 0xbb, 0xfd, 0x6d, 0xd3, 0x91, - 0x07, 0xaa, 0x3d, 0x72, 0xd3, 0xe5, 0x80, 0xb2, 0xea, 0xc0, 0x5b, 0xbe, 0x77, 0x9f, 0xc6, 0xcb, - 0xcf, 0xe6, 0xc3, 0x27, 0xf0, 0xb3, 0x0c, 0x2a, 0xa3, 0x6b, 0x9b, 0xd6, 0xc4, 0x66, 0x69, 0x12, - 0x1b, 0x1b, 0x87, 0x8d, 0x77, 0x1e, 0x36, 0x5e, 0x4e, 0x5d, 0xdb, 0x86, 0x26, 0x6e, 0xb5, 0xd7, - 0x09, 0x87, 0xe3, 0xce, 0x56, 0xdb, 0xbd, 0xbd, 0x69, 0xd9, 0xec, 0x03, 0xa8, 0x75, 0xe6, 0xef, - 0x24, 0xde, 0xfd, 0xf7, 0x12, 0xdc, 0x87, 0x9d, 0xab, 0xef, 0x95, 0xea, 0x5b, 0x2f, 0xff, 0xf9, - 0x08, 0xc1, 0x52, 0x37, 0xf5, 0x85, 0xaf, 0xde, 0x6f, 0x92, 0xfb, 0xc0, 0x54, 0xf7, 0x74, 0x10, - 0x30, 0x81, 0x20, 0x6c, 0xba, 0x3d, 0x3b, 0x3b, 0x9a, 0x66, 0xcc, 0x4c, 0x87, 0x52, 0x77, 0x7b, - 0xe6, 0xba, 0x15, 0x4e, 0xa2, 0x8a, 0x47, 0xd6, 0x39, 0x6f, 0x17, 0x4b, 0xcc, 0x94, 0xf1, 0x65, - 0x42, 0xfc, 0xdd, 0xb2, 0xbf, 0xa9, 0x68, 0x13, 0x92, 0xce, 0x51, 0x3c, 0x53, 0x23, 0x77, 0xac, - 0x7a, 0x9a, 0xa8, 0xd9, 0x7a, 0xa7, 0x89, 0xe4, 0x65, 0x19, 0xbb, 0xd2, 0x26, 0x32, 0x71, 0x61, - 0x8f, 0x54, 0x89, 0x2c, 0x5c, 0xa9, 0x5a, 0x8f, 0x90, 0x97, 0x65, 0xe4, 0x2d, 0x73, 0xcf, 0x2e, - 0xcb, 0x38, 0xaa, 0x1e, 0xf1, 0x92, 0xd2, 0xd9, 0x29, 0x5d, 0xf6, 0x88, 0x97, 0x3d, 0xe2, 0xc5, - 0x6c, 0xb6, 0xce, 0x1e, 0xf1, 0x0c, 0xed, 0xe1, 0x9b, 0x96, 0x27, 0x27, 0x99, 0x56, 0x91, 0x8a, - 0x9b, 0x06, 0xb7, 0x9a, 0x75, 0xa7, 0x77, 0x31, 0xe8, 0xd5, 0xe2, 0x62, 0xff, 0x02, 0x34, 0xa0, - 0x46, 0x07, 0x16, 0xb4, 0xe0, 0x47, 0x0f, 0x11, 0xf1, 0xac, 0x9a, 0xd3, 0x28, 0x48, 0xe8, 0x43, - 0xc9, 0x5a, 0x0a, 0x20, 0x50, 0x84, 0x56, 0x0c, 0xdc, 0x84, 0x9b, 0xbb, 0x30, 0xaa, 0x48, 0xdc, - 0xe8, 0xc6, 0x83, 0x76, 0xe5, 0xd1, 0xaf, 0x8c, 0xeb, 0xa9, 0x14, 0x3a, 0x0a, 0xf1, 0x3d, 0x95, - 0x46, 0x4f, 0x7a, 0x2f, 0x13, 0xa0, 0xcf, 0x02, 0xa1, 0x4f, 0xfa, 0xe1, 0x70, 0x02, 0xf3, 0x38, - 0x85, 0xcb, 0x85, 0x2c, 0x78, 0x4f, 0x81, 0xa3, 0x57, 0x06, 0x9b, 0xcb, 0x98, 0x5d, 0x3b, 0xcd, - 0x3d, 0xf7, 0xb3, 0xb3, 0x44, 0xcd, 0x2c, 0xb8, 0x77, 0x35, 0xad, 0x0f, 0x2f, 0x81, 0xc6, 0x53, - 0x63, 0x5e, 0xaa, 0x2a, 0x3f, 0x4d, 0xd2, 0xf1, 0x75, 0xad, 0x7b, 0x4c, 0x4d, 0x7d, 0xa6, 0xe2, - 0xda, 0x70, 0x06, 0xbe, 0x3f, 0x42, 0x33, 0xe6, 0x68, 0x45, 0x0e, 0x70, 0x20, 0xe6, 0x58, 0x36, - 0x28, 0x59, 0xa6, 0x2f, 0xd2, 0x21, 0x2d, 0x9c, 0x49, 0xe4, 0x33, 0x0c, 0xae, 0xd3, 0xe5, 0x9a, - 0xfb, 0xf5, 0x4d, 0xb5, 0x4f, 0xbc, 0x9e, 0xee, 0x8a, 0x5a, 0x52, 0x0d, 0xed, 0x4e, 0x4f, 0x0c, - 0x2b, 0xbc, 0x3b, 0x33, 0xe8, 0x68, 0x71, 0x76, 0xd6, 0xf1, 0x1b, 0x85, 0x5c, 0x82, 0x93, 0x38, - 0x4b, 0x9e, 0x5a, 0xcb, 0xa5, 0x65, 0x5e, 0x2c, 0xf5, 0xa9, 0xfa, 0x64, 0xcf, 0x4f, 0xda, 0xf5, - 0x53, 0xd7, 0x27, 0xcb, 0x5a, 0x4d, 0xf4, 0xe9, 0xb7, 0x0d, 0x8c, 0x9d, 0x58, 0xac, 0x22, 0x86, - 0x08, 0x96, 0x09, 0xb6, 0x3d, 0xe3, 0xc0, 0x9a, 0x03, 0x7f, 0x97, 0xe1, 0x7e, 0x24, 0xd1, 0x51, - 0x12, 0x9d, 0xd0, 0x43, 0x97, 0xb4, 0xb8, 0x4d, 0x8b, 0x41, 0xeb, 0xc1, 0x4c, 0x7a, 0xfc, 0x25, - 0x9b, 0x20, 0x67, 0x41, 0xe0, 0xa0, 0x09, 0x04, 0xf9, 0xd5, 0x5f, 0xfc, 0x16, 0x66, 0x00, 0xcb, - 0x34, 0x9e, 0x19, 0x70, 0x23, 0xdc, 0x8f, 0x24, 0x48, 0x4a, 0x82, 0x14, 0x7a, 0xe8, 0x47, 0x4e, - 0x90, 0xb9, 0x89, 0x17, 0xe3, 0x84, 0x8a, 0x79, 0xe7, 0xf7, 0xbd, 0x49, 0xa6, 0x65, 0xc4, 0x7f, - 0x1f, 0x6f, 0x08, 0x36, 0xdd, 0x6f, 0xaa, 0x61, 0x5a, 0xa9, 0xc7, 0x78, 0x02, 0x1c, 0x48, 0x77, - 0x43, 0xa2, 0xe5, 0xeb, 0x92, 0x56, 0x79, 0x34, 0x56, 0x21, 0x27, 0xdf, 0x4c, 0x82, 0xa5, 0x1e, - 0x3d, 0x16, 0x95, 0x47, 0x42, 0xe1, 0xac, 0xa3, 0xba, 0xc1, 0x87, 0x8a, 0x68, 0xa4, 0xb7, 0x49, - 0x7a, 0x9b, 0x8e, 0xd6, 0xdb, 0x94, 0x91, 0x92, 0x18, 0x13, 0x7c, 0x71, 0x32, 0xe9, 0x04, 0xed, - 0xe1, 0x8e, 0xd9, 0x31, 0xc5, 0xdb, 0x73, 0xb1, 0x02, 0xa9, 0x5f, 0xec, 0x2a, 0x49, 0xf6, 0x45, - 0x97, 0xbe, 0x28, 0x16, 0x76, 0xc2, 0x77, 0xba, 0x52, 0x7a, 0x53, 0x8c, 0x28, 0x0a, 0x34, 0x51, - 0xd6, 0x45, 0x6d, 0xf4, 0x80, 0xfc, 0xac, 0xce, 0x6d, 0xf5, 0xaa, 0x13, 0xe7, 0x65, 0xf4, 0xd1, - 0x4c, 0x40, 0xcc, 0xff, 0xfc, 0x23, 0x98, 0x64, 0xdd, 0x60, 0x28, 0xd6, 0xaf, 0x31, 0xfa, 0xf4, - 0x05, 0x16, 0xf8, 0x46, 0x64, 0xd3, 0x07, 0x19, 0xb6, 0xad, 0x86, 0x9a, 0xd8, 0x8b, 0xde, 0xb7, - 0x89, 0x46, 0x0d, 0x38, 0xdd, 0x5c, 0xa5, 0x40, 0x20, 0x20, 0xaf, 0xb4, 0x93, 0x57, 0xda, 0x35, - 0xe0, 0x50, 0xe4, 0x9d, 0x49, 0x14, 0x3a, 0xac, 0x27, 0xf9, 0x36, 0xff, 0xca, 0xbc, 0x1d, 0x29, - 0x15, 0x4c, 0xca, 0x8a, 0x24, 0x99, 0x88, 0x2b, 0x8a, 0x24, 0xa8, 0x63, 0x7a, 0x8d, 0x51, 0xa3, - 0xc3, 0xed, 0xa4, 0x5e, 0x5f, 0x54, 0x68, 0x7f, 0x6e, 0x20, 0x60, 0x26, 0xa5, 0x8c, 0x58, 0x50, - 0x66, 0x20, 0xc8, 0x44, 0x5c, 0x41, 0x20, 0x41, 0xf4, 0x54, 0x63, 0xd0, 0xe7, 0x70, 0xe9, 0xa9, - 0xbe, 0xa0, 0xce, 0xa1, 0xd0, 0xd3, 0xb6, 0xc3, 0xca, 0x71, 0x27, 0x41, 0x2f, 0x32, 0x75, 0x02, - 0xe7, 0x96, 0x0d, 0x5b, 0x91, 0x07, 0xeb, 0x14, 0x9c, 0x9c, 0x9d, 0x64, 0x47, 0x78, 0x36, 0x0f, - 0xe9, 0x73, 0x0c, 0xed, 0xf4, 0x33, 0x62, 0xe2, 0x3c, 0x55, 0x5c, 0x09, 0x58, 0x43, 0x5c, 0xe7, - 0x48, 0x6e, 0x0c, 0xdc, 0x7d, 0xdc, 0xe6, 0x55, 0x36, 0x75, 0x92, 0x4d, 0x9d, 0x76, 0x59, 0xa5, - 0x4f, 0xd0, 0x34, 0x1e, 0x2d, 0x3a, 0xde, 0x56, 0xb2, 0x42, 0xff, 0x26, 0x4b, 0x8f, 0xd4, 0x67, - 0x33, 0x38, 0x03, 0xd8, 0x02, 0xfa, 0x9a, 0xa0, 0x40, 0x4a, 0xe8, 0x79, 0xeb, 0x3b, 0x9a, 0x92, - 0xfd, 0x72, 0xa7, 0xd4, 0xcc, 0x1a, 0xfe, 0xea, 0xba, 0x5f, 0xe5, 0x7b, 0x77, 0xb9, 0xba, 0x60, - 0xe5, 0xfa, 0x71, 0xcb, 0x74, 0x8f, 0xd9, 0xdf, 0x5e, 0x46, 0xb2, 0xa0, 0x8a, 0xda, 0x85, 0xba, - 0xb9, 0x48, 0x13, 0x7a, 0xea, 0xe5, 0x14, 0xc3, 0x59, 0x91, 0xb3, 0x54, 0x56, 0x82, 0x36, 0xab, - 0x12, 0xf4, 0xfc, 0xed, 0xe1, 0x54, 0x82, 0x92, 0x3b, 0x12, 0x61, 0x68, 0x9b, 0x85, 0x28, 0xa5, - 0xdc, 0x5d, 0xa9, 0xff, 0xa5, 0xab, 0xff, 0xa7, 0xa9, 0xa3, 0x5f, 0xfe, 0xe3, 0x9f, 0xfe, 0xf9, - 0x5f, 0xfe, 0xfb, 0xde, 0xd5, 0xb4, 0xde, 0xb0, 0xd5, 0x7e, 0xf9, 0xb7, 0xcb, 0xff, 0xf9, 0xff, - 0xb3, 0xd3, 0x93, 0x87, 0x8b, 0x7f, 0x7f, 0xaf, 0x8e, 0x37, 0xa3, 0x00, 0x69, 0xd8, 0xbf, 0x2a, - 0x95, 0x94, 0xad, 0xae, 0x6c, 0x0b, 0x5b, 0x53, 0xcb, 0x28, 0xe6, 0xaf, 0xd1, 0x48, 0x59, 0xb4, - 0xba, 0xaf, 0x45, 0xab, 0x93, 0x05, 0xc3, 0x5d, 0x0e, 0xde, 0x60, 0x59, 0xa0, 0x2a, 0x0b, 0x54, - 0xb3, 0x07, 0xd2, 0xb4, 0xfa, 0xc9, 0x85, 0x19, 0x5d, 0x4b, 0x97, 0xe4, 0xc2, 0x28, 0x5a, 0x01, - 0xa5, 0xcd, 0x45, 0xb3, 0xb8, 0xcd, 0x0a, 0x05, 0xc7, 0x67, 0x50, 0x25, 0xb9, 0x71, 0x9f, 0x87, - 0x06, 0xca, 0xd3, 0x42, 0x59, 0x2f, 0x95, 0xcc, 0x5f, 0xe4, 0x51, 0x75, 0x05, 0xd2, 0x05, 0xa0, - 0x6c, 0x7d, 0x94, 0xf6, 0x44, 0xd0, 0xb4, 0x42, 0x4a, 0x6f, 0x8d, 0xa5, 0x35, 0x52, 0xea, 0x69, - 0xb6, 0x56, 0x49, 0xa9, 0xc7, 0xe9, 0x5b, 0x27, 0x71, 0x42, 0x2e, 0xb6, 0x4d, 0xda, 0xd6, 0x4a, - 0xa9, 0x47, 0x99, 0x5a, 0x2d, 0xe5, 0x31, 0xac, 0xa2, 0xd6, 0x4b, 0x0d, 0x89, 0x4f, 0xec, 0x55, - 0xe2, 0x29, 0x68, 0x9d, 0x81, 0x4b, 0x70, 0x12, 0xc2, 0xc7, 0xbf, 0xb9, 0xfc, 0xde, 0x04, 0xad, - 0xb3, 0xb3, 0x8e, 0x3e, 0x47, 0xaa, 0xa3, 0xcf, 0xd1, 0xdd, 0xfa, 0x83, 0x6f, 0x77, 0x5e, 0x9e, - 0xa0, 0xd5, 0xd3, 0xb9, 0xea, 0x9a, 0x68, 0xaa, 0x3b, 0xf8, 0x64, 0xbc, 0xd5, 0x99, 0xf3, 0x24, - 0x00, 0x15, 0xe5, 0x7b, 0x86, 0x22, 0xde, 0x03, 0x9f, 0x56, 0x66, 0xc9, 0x75, 0x9c, 0xab, 0x86, - 0x3e, 0x81, 0x06, 0x9c, 0x09, 0xdb, 0x97, 0xc0, 0xf7, 0x19, 0xfd, 0xa7, 0x95, 0xa9, 0x8a, 0x3a, - 0xf5, 0xe8, 0x6d, 0x42, 0xce, 0xde, 0xb6, 0x5c, 0x0c, 0x55, 0xac, 0xdb, 0x0b, 0x48, 0x78, 0x4f, - 0xfd, 0x09, 0xcd, 0x7f, 0x35, 0x03, 0x7f, 0xe6, 0xa3, 0xef, 0xf2, 0xb4, 0xa1, 0xe3, 0x80, 0xb9, - 0xbe, 0x44, 0x06, 0x82, 0x4e, 0x94, 0x84, 0x1b, 0xac, 0x96, 0xba, 0x89, 0xab, 0xc8, 0x88, 0x56, - 0x15, 0x91, 0x2d, 0xa1, 0x11, 0xae, 0x4c, 0xb5, 0x82, 0xe9, 0x50, 0x65, 0xfa, 0x32, 0xb5, 0x97, - 0x69, 0x4d, 0x63, 0x1c, 0x06, 0xc4, 0xfa, 0x49, 0x36, 0xa5, 0xbd, 0x2b, 0x95, 0x76, 0xa9, 0xb4, - 0x97, 0x33, 0x7c, 0x4b, 0x19, 0xc0, 0x82, 0x14, 0x7e, 0x6e, 0x83, 0x38, 0xa5, 0xa5, 0x32, 0x3d, - 0x39, 0x66, 0x5c, 0x21, 0x9b, 0xa5, 0xcc, 0x4d, 0x7c, 0x65, 0x88, 0x50, 0x1c, 0x31, 0x8a, 0x92, - 0x8e, 0xa5, 0x89, 0x53, 0xb8, 0x38, 0x14, 0x42, 0xac, 0x9c, 0x62, 0x8b, 0xd5, 0x6e, 0x63, 0xb5, - 0xbc, 0x05, 0x12, 0x24, 0xaf, 0x25, 0x5e, 0xce, 0x22, 0x17, 0x63, 0x99, 0x0b, 0xb2, 0xd0, 0x05, - 0x58, 0xea, 0x9c, 0x90, 0x17, 0x60, 0xb9, 0x8b, 0xb1, 0xe0, 0xcb, 0x5a, 0xf2, 0xcd, 0x53, 0x08, - 0x59, 0x6a, 0x23, 0x12, 0x46, 0x53, 0x09, 0xa9, 0x98, 0x78, 0x8d, 0x94, 0x3a, 0x52, 0xea, 0x1c, - 0xb8, 0xd4, 0x41, 0x33, 0x68, 0x62, 0x84, 0x9f, 0xd9, 0x6a, 0xd7, 0x53, 0x52, 0x67, 0xc0, 0xf1, - 0xec, 0x4d, 0x38, 0xf5, 0xb5, 0xee, 0x08, 0x70, 0x3f, 0x4c, 0x16, 0x2b, 0x35, 0x34, 0x91, 0x55, - 0xdf, 0x44, 0x7e, 0xe6, 0xc5, 0x23, 0x3f, 0xa9, 0xc0, 0xe1, 0xd2, 0x6e, 0xf9, 0x9d, 0x85, 0x99, - 0x7b, 0x82, 0x4f, 0x2b, 0x53, 0xe1, 0x7e, 0xd5, 0xeb, 0x69, 0xdd, 0xeb, 0xcf, 0x72, 0xfb, 0xed, - 0xfd, 0x7e, 0x0e, 0x63, 0x1f, 0xc3, 0x03, 0x83, 0xcb, 0xf0, 0x10, 0xf6, 0x91, 0x76, 0x43, 0x1f, - 0xc4, 0x6e, 0x0e, 0x02, 0x36, 0x71, 0x37, 0x7c, 0x89, 0x7d, 0x70, 0x3d, 0x39, 0x6e, 0x98, 0xda, - 0x51, 0x22, 0xf8, 0x16, 0xbd, 0x83, 0x2b, 0x08, 0x57, 0x1e, 0xa6, 0x89, 0x66, 0x8f, 0x7e, 0x58, - 0xce, 0x8f, 0x6d, 0xb5, 0xfd, 0x1a, 0xa5, 0xed, 0x8c, 0x64, 0xbf, 0x54, 0x09, 0x5c, 0x82, 0x44, - 0x77, 0x9d, 0xe8, 0xbf, 0xe0, 0xe6, 0xbf, 0x75, 0x45, 0xed, 0xba, 0xe4, 0xaf, 0x8c, 0xf1, 0x2a, - 0x24, 0x20, 0x93, 0xda, 0xe9, 0xc7, 0xbf, 0x7f, 0xfe, 0x03, 0x20, 0x07, 0x98, 0x16, 0x06, 0x8e, - 0xbb, 0x5a, 0x59, 0x36, 0x86, 0x33, 0x80, 0xcc, 0x54, 0x3a, 0xba, 0x03, 0x2c, 0xfc, 0x08, 0x6d, - 0x80, 0x1f, 0x75, 0xb3, 0x64, 0xdf, 0x47, 0x51, 0xd6, 0x41, 0x9e, 0xa5, 0x20, 0x2a, 0x72, 0x23, - 0xdc, 0x78, 0xc8, 0x35, 0x24, 0xca, 0x41, 0xa1, 0xd4, 0x5a, 0x5e, 0x77, 0xcc, 0xae, 0x38, 0x99, - 0xb5, 0x48, 0xa2, 0x4e, 0x04, 0x4d, 0x0f, 0x99, 0xb8, 0xe3, 0x1b, 0x95, 0x44, 0x5e, 0x37, 0x91, - 0x8b, 0x81, 0x86, 0x24, 0x76, 0x36, 0x62, 0xcf, 0xc8, 0xde, 0x68, 0x83, 0x03, 0xa6, 0xf9, 0x4f, - 0x7d, 0x29, 0xd1, 0x1b, 0x40, 0xec, 0x25, 0xc1, 0x20, 0xa9, 0x9c, 0x93, 0xca, 0x87, 0x31, 0x2a, - 0x97, 0x44, 0x2e, 0x89, 0x5c, 0x12, 0xf9, 0xe1, 0x10, 0x79, 0x66, 0xa2, 0xe8, 0x41, 0x93, 0xb9, - 0xb7, 0x57, 0xf5, 0xe6, 0xf3, 0xd3, 0xb9, 0xa4, 0xf5, 0xda, 0x69, 0x5d, 0x00, 0x2c, 0x24, 0xc1, - 0x33, 0x13, 0xfc, 0xf0, 0x38, 0x09, 0x7e, 0x28, 0x09, 0xbe, 0x31, 0x04, 0x3f, 0x3c, 0x16, 0x82, - 0x7f, 0x53, 0x6d, 0xe8, 0xa1, 0xae, 0x6a, 0xa0, 0xb0, 0x73, 0x11, 0x4f, 0xf6, 0x0e, 0x5d, 0x4b, - 0xa3, 0xd4, 0x53, 0xd4, 0x2d, 0x8e, 0xd2, 0x4f, 0x96, 0x68, 0x79, 0x94, 0x7a, 0x19, 0x7d, 0x0b, - 0xa4, 0xfc, 0x47, 0x0b, 0x5b, 0x22, 0xf1, 0x42, 0x85, 0xb1, 0x13, 0x78, 0xf4, 0x1c, 0x4b, 0xcf, - 0x98, 0x75, 0x6b, 0x80, 0xce, 0x64, 0xb1, 0xea, 0x30, 0xe6, 0xe6, 0x03, 0xa6, 0x7e, 0x32, 0x9f, - 0xc3, 0x99, 0x1e, 0xae, 0x17, 0xab, 0x87, 0xab, 0x39, 0xfa, 0xea, 0x4d, 0xb4, 0xcb, 0x92, 0x05, - 0x17, 0x5b, 0xa6, 0xb5, 0xb4, 0x5c, 0x47, 0x0d, 0x3a, 0x51, 0x72, 0xd4, 0x2e, 0xa4, 0x5e, 0x21, - 0x2b, 0x8f, 0x65, 0x11, 0x83, 0x10, 0x5a, 0xbf, 0xd5, 0xcd, 0x99, 0x8e, 0x2d, 0xfb, 0x99, 0xa1, - 0xd6, 0xa5, 0x44, 0xb5, 0xb2, 0xa3, 0x9a, 0xee, 0x72, 0x02, 0x6d, 0x8e, 0x5a, 0x65, 0x96, 0xeb, - 0x56, 0xbe, 0xe8, 0xa6, 0xaf, 0xfb, 0x55, 0x5e, 0x2a, 0xca, 0xd3, 0xed, 0x3c, 0x7a, 0x98, 0xb3, - 0xeb, 0x79, 0xf4, 0x7c, 0xd9, 0x46, 0xdf, 0x1b, 0xf0, 0xf0, 0x36, 0xfc, 0x2e, 0x61, 0x90, 0x70, - 0x75, 0x45, 0x4f, 0x1d, 0xdd, 0x79, 0x6f, 0x74, 0x3e, 0x1a, 0xbe, 0xed, 0x8d, 0x06, 0xfb, 0x7f, - 0x86, 0x07, 0x50, 0x9e, 0xe7, 0xc7, 0xd1, 0x6c, 0x15, 0xcd, 0xd8, 0x65, 0xdc, 0xe6, 0x51, 0x29, - 0xdb, 0xa4, 0x6c, 0xdb, 0x43, 0xd9, 0x86, 0xa2, 0x3c, 0x66, 0x9e, 0x46, 0x1c, 0x23, 0x86, 0x67, - 0xc2, 0x35, 0xee, 0xac, 0x0f, 0x82, 0xef, 0xdc, 0x65, 0xdf, 0x5b, 0x6a, 0x8f, 0xef, 0x38, 0x9e, - 0xa5, 0xed, 0x9c, 0x96, 0xfb, 0x82, 0x56, 0xeb, 0x4e, 0x53, 0x47, 0xe3, 0x97, 0xbb, 0xae, 0x3a, - 0x1a, 0x07, 0x1f, 0xbb, 0xfe, 0xff, 0x82, 0xcf, 0xbd, 0x3b, 0x4d, 0x3d, 0x5f, 0x7f, 0x1e, 0xdc, - 0x69, 0xea, 0x60, 0xdc, 0xbe, 0xbf, 0x3f, 0x6b, 0xff, 0xec, 0xbf, 0xb2, 0x3f, 0xa8, 0x54, 0x6d, - 0xa6, 0x9f, 0xee, 0x10, 0xe4, 0xc3, 0x3d, 0x06, 0xf9, 0xc5, 0x8b, 0x07, 0x18, 0x5d, 0x9d, 0x5f, - 0xa9, 0xbf, 0x8d, 0x7f, 0x6a, 0xa7, 0xe7, 0xaf, 0xed, 0x8b, 0x76, 0x6b, 0xfb, 0xbb, 0x8b, 0xf6, - 0x4f, 0xed, 0x74, 0xf0, 0xda, 0x6a, 0x65, 0xfc, 0xf2, 0x3e, 0xeb, 0x1d, 0xed, 0x97, 0x56, 0xab, - 0x15, 0x02, 0x3b, 0x81, 0x00, 0x77, 0x5a, 0x77, 0xfc, 0xde, 0xff, 0x18, 0xfc, 0x8d, 0x50, 0x88, - 0x6a, 0x70, 0xbb, 0x7a, 0xc4, 0xd9, 0xb3, 0xbb, 0xc8, 0x9a, 0x78, 0x85, 0x8b, 0x69, 0xe1, 0x16, - 0xb5, 0x6f, 0x3b, 0xe1, 0xd5, 0xbe, 0x57, 0xc2, 0x2b, 0x39, 0xee, 0x95, 0x1a, 0x2e, 0x96, 0xb8, - 0xfe, 0xfd, 0x73, 0xd4, 0x45, 0xdd, 0x77, 0xea, 0xac, 0x7d, 0xa6, 0x2b, 0xcb, 0x71, 0xd0, 0xc4, - 0x80, 0x34, 0x97, 0x89, 0x00, 0xd9, 0xc3, 0x3e, 0x54, 0x6d, 0x04, 0x1d, 0xe7, 0xe1, 0x5e, 0x26, - 0x51, 0x70, 0xcf, 0x3e, 0x6d, 0xe3, 0xf3, 0x0a, 0xa8, 0x20, 0x19, 0x28, 0xf0, 0xef, 0x1f, 0xc8, - 0x05, 0x14, 0x43, 0xff, 0xf3, 0xe3, 0xc0, 0x79, 0xe6, 0xc3, 0x6b, 0xfa, 0xd5, 0x0c, 0x14, 0xe6, - 0xec, 0xca, 0x86, 0x0e, 0x34, 0xa7, 0xb0, 0x4a, 0xb1, 0xf2, 0x61, 0x7d, 0xc1, 0x05, 0xb8, 0xfe, - 0xfd, 0xf3, 0xee, 0x2d, 0xc5, 0x60, 0x7f, 0x75, 0xda, 0x8a, 0xc9, 0x03, 0xd8, 0xb3, 0xbb, 0xe6, - 0xaa, 0xbc, 0x34, 0x35, 0x1e, 0x22, 0x11, 0x7a, 0x53, 0x6a, 0x3c, 0x22, 0xa2, 0xec, 0xe4, 0x86, - 0x92, 0x2a, 0x6e, 0x1d, 0x28, 0x68, 0x2e, 0xcd, 0x77, 0x24, 0x65, 0xee, 0x1c, 0xc0, 0x24, 0xd7, - 0x41, 0x44, 0xef, 0xfe, 0xa8, 0xbc, 0x6e, 0xcc, 0x14, 0x0d, 0x7a, 0x14, 0x62, 0x00, 0x7b, 0x2c, - 0x6f, 0x33, 0xd8, 0xfb, 0xdb, 0x0c, 0xe8, 0x4a, 0xfb, 0x69, 0x4a, 0xf8, 0xd9, 0x4a, 0xf5, 0x37, - 0x96, 0x0e, 0x52, 0x09, 0x58, 0x9a, 0xf4, 0xc1, 0xd3, 0xd5, 0xdb, 0x33, 0xfa, 0x82, 0xa9, 0x73, - 0x34, 0x28, 0x14, 0x59, 0xc6, 0xa9, 0xe9, 0x54, 0x17, 0xf1, 0xf3, 0xa2, 0x15, 0xdd, 0x9d, 0x8d, - 0xe2, 0x67, 0xa6, 0xbe, 0x2e, 0x92, 0x2c, 0x4b, 0xc7, 0xac, 0x74, 0xf2, 0x86, 0xfc, 0xcd, 0xd6, - 0x46, 0x09, 0x97, 0x5c, 0x91, 0x33, 0x41, 0x8a, 0x33, 0x3e, 0xb8, 0x32, 0x3b, 0x28, 0x32, 0x38, - 0x28, 0x32, 0x35, 0xb6, 0x37, 0x59, 0x20, 0x27, 0x59, 0xe4, 0x63, 0x06, 0xf5, 0x52, 0x8b, 0xc3, - 0x24, 0x2e, 0x6c, 0xe0, 0x12, 0x5b, 0xae, 0xb2, 0x0a, 0xbc, 0x74, 0x18, 0x3a, 0xe9, 0xe6, 0x49, - 0x9b, 0x0b, 0x20, 0x62, 0x83, 0xb6, 0xe1, 0x99, 0x29, 0x91, 0x72, 0x25, 0x11, 0x49, 0x02, 0x65, - 0x49, 0x9e, 0xac, 0xed, 0x17, 0x88, 0x1b, 0x6a, 0x31, 0x43, 0x2d, 0x5e, 0xf2, 0xc4, 0x8a, 0xc2, - 0x88, 0xfb, 0xb9, 0xb2, 0x63, 0xb3, 0xf3, 0xa0, 0x93, 0x57, 0xc6, 0xa6, 0x09, 0x9e, 0xd8, 0xa2, - 0x8b, 0x6e, 0xc8, 0x37, 0xc5, 0x15, 0xcb, 0x93, 0x75, 0x90, 0xb6, 0x20, 0x72, 0xcf, 0x1c, 0x83, - 0xa5, 0x8f, 0xb5, 0xbe, 0x92, 0xaf, 0xb0, 0xa3, 0xdf, 0x42, 0x57, 0xab, 0x6f, 0x0f, 0x94, 0xec, - 0x33, 0x43, 0xf5, 0x2b, 0x74, 0xa5, 0x2b, 0x8f, 0xba, 0x61, 0x58, 0x20, 0xe6, 0xe8, 0xce, 0xb8, - 0x33, 0x66, 0x4c, 0xc1, 0x0d, 0x6c, 0xdd, 0x5c, 0x40, 0x8f, 0xcc, 0x3f, 0x41, 0x7d, 0x6e, 0x20, - 0x12, 0x4f, 0x48, 0x0f, 0x95, 0x9c, 0xa1, 0x42, 0xce, 0xe0, 0x22, 0x13, 0xf7, 0x7b, 0x04, 0xce, - 0x90, 0x41, 0x9d, 0x05, 0xf9, 0x33, 0xa2, 0xf8, 0x42, 0x9d, 0x44, 0x25, 0x8a, 0x31, 0xf4, 0xb5, - 0xc6, 0x71, 0x86, 0xd3, 0x4a, 0x81, 0x36, 0xd0, 0xb4, 0x03, 0x00, 0xdb, 0x40, 0xeb, 0x1d, 0x19, - 0xdc, 0x46, 0xa3, 0xd1, 0x68, 0xff, 0xe1, 0x56, 0xef, 0x2e, 0x68, 0x25, 0x31, 0x91, 0x65, 0xef, - 0xb1, 0xb5, 0x42, 0x16, 0xfd, 0x0e, 0x5a, 0x98, 0x70, 0x46, 0x21, 0xf8, 0xc3, 0x81, 0x52, 0xec, - 0x57, 0x28, 0xf6, 0x8b, 0xa4, 0x7e, 0xbf, 0x36, 0xa9, 0xdf, 0xaf, 0x56, 0x80, 0x78, 0x87, 0x5f, - 0x3d, 0x1f, 0x1a, 0xd6, 0xb7, 0x85, 0x7a, 0x84, 0xfe, 0xde, 0x43, 0x6c, 0x70, 0x5c, 0x10, 0xeb, - 0xf7, 0xf6, 0x1e, 0x62, 0xdd, 0xa3, 0x02, 0x98, 0x34, 0x86, 0x6a, 0x51, 0xce, 0xc8, 0x2a, 0x8d, - 0x6b, 0x52, 0x2b, 0x35, 0xd1, 0x50, 0xa9, 0xd6, 0x48, 0x6f, 0x86, 0x24, 0x60, 0xe9, 0xcd, 0x90, - 0xde, 0x0c, 0xe9, 0xcd, 0x38, 0x40, 0x6f, 0xc6, 0x9b, 0xe4, 0xa7, 0xf0, 0x2c, 0xf2, 0x22, 0x97, - 0x0a, 0x72, 0x7e, 0xd3, 0xbf, 0xc1, 0x2f, 0x96, 0x95, 0x96, 0x20, 0xdb, 0xd1, 0x4c, 0x25, 0xfe, - 0x53, 0x22, 0x52, 0xf9, 0x2b, 0x7c, 0x42, 0xeb, 0xa8, 0xe4, 0xeb, 0x9b, 0xd7, 0x7f, 0x00, 0x00, - 0x00, 0xff, 0xff, 0x01, 0x00, 0x00, 0xff, 0xff, 0xd9, 0x32, 0x70, 0x53, 0x74, 0x73, 0x01, 0x00, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x3d, 0x6b, 0x73, 0xda, 0xc8, + 0x96, 0xdf, 0xf3, 0x2b, 0x7a, 0xb4, 0xbb, 0x65, 0xd8, 0xb5, 0x8c, 0x00, 0x43, 0x82, 0xb7, 0x5c, + 0x59, 0x3b, 0x93, 0x99, 0x75, 0xdd, 0x78, 0x6e, 0x2a, 0x99, 0x7b, 0x3f, 0xac, 0xcd, 0x7a, 0x05, + 0x34, 0xb8, 0x2b, 0x20, 0xb1, 0x52, 0xcb, 0x89, 0x37, 0xf6, 0xfe, 0xf6, 0x5b, 0x7a, 0x20, 0x24, + 0x24, 0xb5, 0xba, 0x5b, 0x2d, 0x24, 0x41, 0x57, 0x4d, 0x79, 0x08, 0x1c, 0xa9, 0x1f, 0xe7, 0xfd, + 0xe8, 0xd3, 0x3f, 0xdf, 0x00, 0x00, 0x80, 0xf2, 0x87, 0xbe, 0x82, 0xca, 0x05, 0x50, 0x66, 0xf0, + 0x09, 0x4d, 0xa1, 0x72, 0xea, 0x7f, 0xfb, 0x17, 0x64, 0xcc, 0x94, 0x0b, 0xd0, 0x0d, 0xfe, 0xf9, + 0xc1, 0x34, 0xe6, 0x68, 0xa1, 0x5c, 0x00, 0x2d, 0xf8, 0xe2, 0x57, 0x64, 0x29, 0x17, 0xc0, 0x7f, + 0x85, 0xf7, 0xc5, 0xf4, 0xd1, 0x44, 0x53, 0x68, 0xc7, 0xbe, 0x8c, 0xbd, 0x7f, 0x03, 0x70, 0x1a, + 0xff, 0x39, 0x3e, 0x50, 0xf8, 0xf5, 0xee, 0x80, 0xe1, 0x0f, 0x9f, 0x2d, 0x38, 0x47, 0x3f, 0x12, + 0xc3, 0xc4, 0x86, 0xb2, 0x67, 0x53, 0x64, 0x3e, 0xac, 0xcc, 0x19, 0x5c, 0x3e, 0xf8, 0xc3, 0xee, + 0x8c, 0xea, 0x41, 0x7f, 0x35, 0x1d, 0x6b, 0x0a, 0x53, 0xdf, 0xe4, 0xcf, 0x0c, 0x3e, 0x7f, 0x37, + 0x2d, 0x77, 0x72, 0xca, 0xda, 0x1f, 0xf4, 0x34, 0x1d, 0xf0, 0x3f, 0x75, 0xfb, 0xca, 0x5a, 0x38, + 0x2b, 0x68, 0x60, 0xe5, 0x02, 0x60, 0xcb, 0x81, 0x19, 0x80, 0x11, 0xa8, 0xb4, 0x39, 0x26, 0x1e, + 0x7a, 0x8d, 0x7d, 0xf3, 0xba, 0xb3, 0x13, 0xbb, 0x28, 0xd8, 0x41, 0xc5, 0x54, 0xb7, 0x09, 0x8b, + 0x8b, 0x63, 0xc5, 0x83, 0xcd, 0x98, 0x74, 0x80, 0xa0, 0x41, 0xc6, 0xcf, 0x59, 0x88, 0xa2, 0x41, + 0x18, 0x1f, 0xe2, 0x68, 0x11, 0xc8, 0x8c, 0x48, 0x66, 0x84, 0x72, 0x23, 0x36, 0x1d, 0xc1, 0x19, + 0x88, 0xce, 0x45, 0xf8, 0x16, 0xf1, 0xba, 0x0d, 0xbb, 0xf9, 0xfb, 0x11, 0xe2, 0xde, 0x03, 0xcf, + 0x59, 0x5a, 0x80, 0xfe, 0xf3, 0x1c, 0xb0, 0x3c, 0x32, 0x60, 0x21, 0x87, 0x62, 0x64, 0xc1, 0x4a, + 0x1e, 0xdc, 0x64, 0xc2, 0x4d, 0x2e, 0x85, 0xc9, 0x86, 0x4c, 0x3e, 0x39, 0x64, 0x44, 0x4d, 0x4e, + 0x8c, 0x64, 0xc5, 0x45, 0x5e, 0x39, 0x6a, 0xa0, 0x30, 0xb9, 0xf1, 0x90, 0x9d, 0x18, 0xf2, 0xe3, + 0x25, 0xc3, 0xc2, 0xe4, 0x58, 0x98, 0x2c, 0x85, 0x91, 0x27, 0x1d, 0x99, 0x52, 0x92, 0x2b, 0x33, + 0xd9, 0xc6, 0xc8, 0x57, 0x85, 0x4b, 0xb8, 0x62, 0xc7, 0x41, 0x94, 0x94, 0xfd, 0x57, 0x30, 0x6e, + 0x21, 0x1b, 0x59, 0x73, 0x93, 0x77, 0x11, 0x32, 0x17, 0x4b, 0xee, 0x45, 0xc9, 0x5e, 0x18, 0xf9, + 0x0b, 0x63, 0x03, 0xe1, 0xec, 0xc0, 0xc6, 0x16, 0x8c, 0xec, 0xc1, 0xcd, 0x26, 0xe1, 0x83, 0x5c, + 0x9c, 0x92, 0x20, 0x22, 0x0e, 0x66, 0xd9, 0x65, 0x1a, 0x8d, 0xf3, 0x71, 0x5e, 0xe6, 0x11, 0xc1, + 0x44, 0xe5, 0x30, 0x93, 0x28, 0xa6, 0x12, 0xce, 0x5c, 0xc2, 0x99, 0xac, 0x34, 0x66, 0xe3, 0x63, + 0x3a, 0x4e, 0xe6, 0x0b, 0x57, 0xf1, 0xe7, 0xf3, 0x1a, 0x0a, 0xa2, 0x23, 0x6c, 0x21, 0x63, 0x51, + 0x84, 0x76, 0x36, 0xaa, 0xe8, 0xdd, 0x9b, 0xfd, 0xec, 0x5b, 0xb9, 0xe2, 0xed, 0xca, 0x30, 0x4c, + 0xac, 0x63, 0x64, 0x1a, 0x7c, 0x52, 0xce, 0x9e, 0x3e, 0xc2, 0x95, 0xbe, 0xd6, 0xf1, 0xa3, 0xbb, + 0xbb, 0x9d, 0x08, 0x89, 0x75, 0x82, 0x40, 0x45, 0x67, 0xeb, 0x1a, 0x77, 0x3c, 0x53, 0x36, 0xf2, + 0x57, 0xe5, 0x14, 0x6f, 0x2e, 0x1a, 0x9d, 0x29, 0x36, 0x02, 0xa4, 0x7e, 0x75, 0x47, 0xbd, 0xf5, + 0xe8, 0xfa, 0x83, 0x3f, 0xe8, 0xc3, 0x07, 0x77, 0x0c, 0xef, 0xef, 0x47, 0x77, 0x88, 0x37, 0xe5, + 0xec, 0x38, 0xc3, 0x6e, 0x2b, 0x4b, 0x73, 0xc1, 0x6f, 0x38, 0xb9, 0x0f, 0xb3, 0x2a, 0x2e, 0x38, + 0xd7, 0x9d, 0xa5, 0xcb, 0xf8, 0x77, 0xec, 0xfb, 0x3b, 0xd7, 0x97, 0x36, 0xa3, 0x74, 0x18, 0xf3, + 0x99, 0x74, 0x9a, 0x34, 0xe9, 0xa4, 0x49, 0xb7, 0x5f, 0x99, 0xc7, 0xad, 0x4d, 0x42, 0x3a, 0x98, + 0x98, 0xe6, 0x12, 0xea, 0x06, 0x0f, 0xf2, 0x37, 0xea, 0xa3, 0x5b, 0x96, 0x48, 0x12, 0xea, 0x24, + 0xc2, 0x1f, 0xd8, 0xd2, 0x55, 0xc7, 0xb0, 0xb1, 0x3e, 0x59, 0xb2, 0x6d, 0x9a, 0x4b, 0x9b, 0x36, + 0x34, 0x3c, 0x42, 0x67, 0x93, 0x40, 0x05, 0x10, 0xe3, 0xa9, 0x16, 0x80, 0x6c, 0xa0, 0x4f, 0x31, + 0x7a, 0xaa, 0x01, 0x77, 0xfa, 0x3b, 0x50, 0x27, 0xfe, 0xdc, 0xdd, 0xa2, 0xb2, 0x99, 0x93, 0x1a, + 0x7a, 0x2c, 0x94, 0x72, 0x39, 0x0d, 0x1b, 0x7e, 0x83, 0x86, 0x25, 0x96, 0x45, 0x67, 0xbe, 0xd0, + 0xa1, 0xe6, 0xb5, 0xe4, 0xf0, 0x26, 0xe3, 0x46, 0xf2, 0x6c, 0x20, 0x79, 0xa1, 0xd9, 0xd3, 0x27, + 0x4c, 0xdd, 0x23, 0xf3, 0x1e, 0x5b, 0x0c, 0xbf, 0x27, 0x63, 0xf8, 0x45, 0x4d, 0x8c, 0xa3, 0x88, + 0xe1, 0xf7, 0xf8, 0x62, 0xf8, 0x3d, 0x19, 0xc3, 0x97, 0x31, 0xfc, 0x8a, 0x63, 0xf8, 0xd2, 0x09, + 0x95, 0x4e, 0xa8, 0x74, 0x42, 0xa5, 0x13, 0x2a, 0x9d, 0x50, 0xe9, 0x84, 0x4a, 0x27, 0xf4, 0x80, + 0x9c, 0xd0, 0x5e, 0x87, 0xc5, 0xc4, 0xa4, 0x77, 0x42, 0x7b, 0xc7, 0xe2, 0x84, 0xf6, 0xb8, 0x9d, + 0x50, 0xa6, 0xda, 0x33, 0xca, 0x85, 0xb0, 0x2d, 0x20, 0x7d, 0xea, 0xaf, 0x8c, 0x75, 0x90, 0x39, + 0x53, 0xa3, 0x98, 0x52, 0x5a, 0x75, 0x68, 0x1e, 0x95, 0xc5, 0xe7, 0xbe, 0x9d, 0x61, 0x64, 0x76, + 0xca, 0xcc, 0x74, 0x26, 0x4b, 0xf8, 0x0d, 0x3e, 0x67, 0x57, 0xc5, 0x6e, 0x41, 0xf6, 0x5c, 0x17, + 0x9b, 0x35, 0x30, 0xa8, 0x57, 0x69, 0xec, 0x76, 0x9a, 0xc2, 0xaa, 0x63, 0x4d, 0x6f, 0x94, 0xbc, + 0xba, 0x58, 0x17, 0x8a, 0x5c, 0x11, 0xdb, 0xdd, 0x77, 0x45, 0x2c, 0x09, 0x65, 0xac, 0x1a, 0xb8, + 0xc2, 0xa2, 0x58, 0x02, 0x4a, 0xf9, 0x64, 0x53, 0x6e, 0x5d, 0xec, 0x93, 0xbe, 0x74, 0x58, 0x0a, + 0x63, 0x03, 0x78, 0xba, 0xa8, 0x9a, 0x56, 0x97, 0xa8, 0x1a, 0x0d, 0x79, 0x1c, 0x40, 0x60, 0x8d, + 0x82, 0x7c, 0xc4, 0xe8, 0x6e, 0x6a, 0x4f, 0x87, 0xbd, 0x48, 0x83, 0xb2, 0x18, 0x83, 0x2f, 0x82, + 0xec, 0x51, 0x6f, 0x8f, 0x91, 0xda, 0x7b, 0x92, 0xda, 0x25, 0xb5, 0xd7, 0x96, 0xda, 0xf7, 0x6d, + 0xaa, 0x86, 0xbb, 0xde, 0x21, 0xd8, 0x01, 0x64, 0x33, 0xf1, 0xd7, 0xcd, 0x2b, 0x1e, 0x3e, 0xb8, + 0xaf, 0xa0, 0xb5, 0x74, 0x53, 0x8c, 0xb1, 0x6f, 0xf0, 0xb9, 0x9b, 0x6f, 0xb0, 0x78, 0x50, 0x64, + 0x83, 0x45, 0x93, 0x06, 0x4b, 0x3d, 0x0c, 0x96, 0x5c, 0x5e, 0xa3, 0xe7, 0xb1, 0x1c, 0xde, 0xa2, + 0xa6, 0xb0, 0x1e, 0x15, 0x85, 0xf5, 0x24, 0x85, 0x49, 0x0a, 0xe3, 0xa1, 0xb0, 0x95, 0x6e, 0xcc, + 0x74, 0x6c, 0xe6, 0x13, 0xd9, 0x06, 0x50, 0xd2, 0x59, 0x23, 0xe8, 0xec, 0xd6, 0x47, 0x97, 0xf5, + 0x4c, 0xf0, 0x87, 0x2b, 0xa5, 0x45, 0x62, 0x8c, 0xe0, 0x2f, 0x5e, 0x64, 0xc6, 0x53, 0x9d, 0x20, + 0x45, 0xba, 0x29, 0x9f, 0x90, 0x8d, 0xaf, 0x30, 0xce, 0x08, 0x24, 0xdc, 0x22, 0xe3, 0xe3, 0x12, + 0xba, 0xdb, 0x6c, 0xa7, 0x53, 0x9c, 0x72, 0xab, 0xff, 0x88, 0x40, 0x74, 0xdf, 0x9d, 0x9f, 0x0f, + 0xdf, 0x9e, 0x9f, 0x6b, 0x6f, 0xfb, 0x6f, 0xb5, 0xd1, 0x60, 0xd0, 0x1d, 0x76, 0x53, 0x0e, 0xd5, + 0x2a, 0x7f, 0xb5, 0x66, 0xd0, 0x82, 0xb3, 0x6b, 0x77, 0x6a, 0x86, 0xb3, 0x5c, 0x92, 0x40, 0xfe, + 0x66, 0x43, 0x77, 0x72, 0x5e, 0xc2, 0xb1, 0xac, 0x18, 0x19, 0x31, 0x50, 0x94, 0x6f, 0xfe, 0xd0, + 0xc4, 0xc9, 0xe0, 0x6a, 0x8d, 0x9f, 0xa7, 0xa6, 0x31, 0xcf, 0x8e, 0x93, 0x6d, 0x41, 0xd2, 0xe3, + 0x64, 0x5a, 0x49, 0x71, 0xb2, 0x5a, 0x47, 0xc7, 0x58, 0x63, 0x62, 0x99, 0x9c, 0x18, 0xdf, 0xe6, + 0xb4, 0x35, 0x6f, 0x18, 0xaf, 0x4f, 0x81, 0x4e, 0x34, 0x83, 0x06, 0x46, 0xf8, 0xd9, 0x82, 0x04, + 0x84, 0x46, 0x81, 0xf6, 0x16, 0xfa, 0xdc, 0x0c, 0x5a, 0x57, 0xac, 0x86, 0xf3, 0x13, 0x16, 0xec, + 0xb4, 0x9e, 0xd7, 0xd8, 0xbc, 0xa2, 0x88, 0x77, 0x06, 0x80, 0xb5, 0xd1, 0xbb, 0x04, 0x54, 0x35, + 0x42, 0xe1, 0x66, 0xa3, 0xb2, 0x6c, 0x8b, 0x2e, 0x9b, 0xb3, 0x52, 0xb9, 0x6c, 0x40, 0x80, 0xb9, + 0x09, 0x5e, 0x75, 0x4d, 0xea, 0x25, 0x91, 0x41, 0x4b, 0xaa, 0xbe, 0xcc, 0x0b, 0x0e, 0x28, 0x7f, + 0xd7, 0x97, 0x8e, 0xd7, 0x33, 0x24, 0x3f, 0xc7, 0xcd, 0x58, 0xb8, 0x36, 0xdb, 0xcd, 0xd0, 0x70, + 0xa6, 0x43, 0xd9, 0x87, 0xed, 0x57, 0x31, 0xae, 0x89, 0x1f, 0xa1, 0x75, 0xb5, 0x5c, 0x98, 0x55, + 0x0c, 0x6e, 0xd9, 0x7a, 0xd1, 0xe8, 0xd4, 0x98, 0x95, 0x4b, 0xa8, 0xbc, 0x0e, 0x9f, 0x10, 0xaf, + 0x69, 0xa5, 0xdf, 0xb5, 0x94, 0x7e, 0x52, 0xfa, 0x49, 0xe9, 0x27, 0xa5, 0x5f, 0x03, 0xa5, 0x5f, + 0x59, 0xde, 0x1f, 0x99, 0x9d, 0xb2, 0xfd, 0xbf, 0x9b, 0xc8, 0x73, 0x34, 0x2e, 0x83, 0x81, 0xa1, + 0x35, 0xd7, 0x53, 0xc4, 0xd9, 0x96, 0xb1, 0x43, 0x90, 0x3d, 0x57, 0x4a, 0xa0, 0x79, 0xbd, 0x4b, + 0x24, 0xd0, 0x5c, 0x98, 0xbb, 0xa0, 0xcf, 0x56, 0xc8, 0x50, 0x6d, 0xac, 0x63, 0x8a, 0xd6, 0x61, + 0x51, 0xe0, 0xac, 0xcc, 0x3c, 0x45, 0x19, 0xb5, 0x02, 0x0d, 0xaf, 0x3c, 0x32, 0x15, 0x60, 0x5c, + 0xb3, 0x38, 0x20, 0x9a, 0x37, 0x3b, 0x00, 0x98, 0x46, 0x2b, 0x65, 0x6b, 0xe4, 0x7c, 0x3a, 0x89, + 0xb1, 0x31, 0xe1, 0x94, 0x92, 0xf2, 0xd1, 0x70, 0xf2, 0x7b, 0x93, 0x28, 0x7f, 0x9a, 0x5f, 0xfd, + 0x40, 0x22, 0x55, 0x3a, 0xb3, 0xeb, 0xc5, 0x3d, 0x7c, 0x1a, 0xa4, 0xc8, 0x65, 0xf6, 0x3c, 0x45, + 0x87, 0xec, 0x6c, 0x9a, 0xa5, 0xce, 0xb8, 0x9a, 0x37, 0x84, 0x4a, 0xa4, 0x18, 0xe8, 0x66, 0xbc, + 0x0b, 0xd0, 0xa3, 0x98, 0x62, 0xb0, 0x98, 0x0b, 0xd0, 0xdd, 0x4b, 0xbe, 0x95, 0xa1, 0xcc, 0x59, + 0x59, 0x39, 0x76, 0xfe, 0xa1, 0x0a, 0x86, 0x1c, 0x74, 0xab, 0x75, 0x06, 0x2e, 0xc1, 0x89, 0xbf, + 0xe2, 0x93, 0x36, 0xd0, 0x8d, 0x19, 0xb0, 0xb1, 0x6e, 0x61, 0x5b, 0xfd, 0x8e, 0xf0, 0x63, 0xeb, + 0xec, 0xac, 0xe3, 0x2a, 0xa7, 0x53, 0x70, 0x62, 0x3f, 0xdb, 0x18, 0xae, 0xb4, 0x93, 0x76, 0x1b, + 0x98, 0x16, 0x30, 0x4c, 0xdc, 0xca, 0x83, 0xa3, 0xa1, 0x86, 0x8f, 0x96, 0x65, 0x5a, 0xb7, 0xd0, + 0xb6, 0xf5, 0x05, 0x64, 0x3f, 0x5d, 0x15, 0xe1, 0x0c, 0xe0, 0xee, 0x0c, 0x98, 0x40, 0x40, 0x4d, + 0x89, 0x80, 0xb3, 0xde, 0x3a, 0x2a, 0x7d, 0xa0, 0x3b, 0x7d, 0x75, 0x15, 0xcc, 0x9f, 0xa1, 0x06, + 0xb7, 0x48, 0x71, 0x75, 0x4c, 0x38, 0x11, 0xb6, 0x60, 0x3f, 0x05, 0xbc, 0x99, 0xbf, 0x12, 0x8e, + 0xe4, 0x28, 0x16, 0x9c, 0x43, 0x8b, 0xaa, 0x3c, 0x9f, 0x81, 0x96, 0xbf, 0xfc, 0xf6, 0x01, 0xf4, + 0xde, 0x0d, 0xfb, 0x17, 0xe0, 0xcf, 0x47, 0x08, 0x6e, 0x36, 0xe6, 0x8e, 0x0d, 0x7e, 0xb7, 0x4c, + 0x67, 0x0d, 0x6e, 0x6f, 0xae, 0x81, 0x0a, 0xd0, 0xfc, 0xca, 0xdd, 0xb1, 0xaf, 0x58, 0xc7, 0x8e, + 0x5d, 0x72, 0xe9, 0xcb, 0x76, 0x95, 0xfb, 0xac, 0x7e, 0xe1, 0xd8, 0x86, 0xf2, 0x68, 0xa0, 0x40, + 0xf0, 0x61, 0x06, 0xed, 0xa9, 0x85, 0xd6, 0xc4, 0x3a, 0x94, 0xa8, 0xe7, 0x14, 0x02, 0x4b, 0x93, + 0xa7, 0xf1, 0x26, 0x4f, 0x3e, 0x3a, 0xe3, 0xd9, 0x4c, 0x02, 0xcc, 0x27, 0x68, 0x2c, 0x3c, 0xbf, + 0xac, 0xb0, 0x9c, 0xb9, 0x45, 0xf4, 0xa7, 0x39, 0xfc, 0xd8, 0x04, 0xc3, 0x61, 0xde, 0xdf, 0x2c, + 0x7d, 0xea, 0x2e, 0xf7, 0x57, 0xb4, 0x40, 0x59, 0xf9, 0xd0, 0xf4, 0x2d, 0x83, 0x0b, 0xdd, 0x3b, + 0x4a, 0x93, 0x96, 0xc6, 0xe4, 0xf4, 0xe0, 0x95, 0x5b, 0xfd, 0x07, 0xfb, 0x52, 0x7b, 0x83, 0x41, + 0xfd, 0x16, 0x5b, 0x81, 0xe0, 0x0a, 0x1d, 0x6d, 0x15, 0x93, 0x08, 0x3e, 0xe9, 0x98, 0xfb, 0xf0, + 0x52, 0x7c, 0x35, 0x5e, 0x7c, 0xed, 0xa9, 0x26, 0x08, 0x3b, 0x14, 0xf5, 0x40, 0xd8, 0x91, 0x14, + 0xd5, 0x7c, 0x8a, 0x72, 0x90, 0x81, 0xbb, 0x43, 0x0a, 0x8a, 0x1a, 0x12, 0x40, 0xbe, 0xe8, 0xc6, + 0x02, 0x56, 0xa6, 0x09, 0xb5, 0xe3, 0xd1, 0x84, 0xc3, 0xc1, 0xa0, 0x2f, 0x75, 0xa1, 0x3b, 0xc9, + 0x20, 0xb2, 0x9d, 0x23, 0xa4, 0x3c, 0x28, 0x29, 0xa5, 0x1a, 0x2f, 0xa5, 0xb6, 0x96, 0x0c, 0x01, + 0xa3, 0x4d, 0xb1, 0xdc, 0xfb, 0x47, 0x64, 0xb9, 0x6b, 0x87, 0x23, 0xac, 0x08, 0x04, 0xf5, 0x59, + 0xc7, 0x18, 0x5a, 0x46, 0x2e, 0x45, 0x29, 0xad, 0xd5, 0x62, 0x85, 0xb5, 0x97, 0x20, 0x94, 0xf9, + 0xb2, 0x34, 0x5b, 0xda, 0x4b, 0xf7, 0x4e, 0x53, 0x47, 0x63, 0xef, 0xcf, 0x4b, 0xaf, 0x75, 0xa7, + 0xa9, 0xe7, 0xc1, 0x3f, 0x06, 0x77, 0x9a, 0x3a, 0x18, 0xb7, 0x5f, 0xee, 0xba, 0xe1, 0xef, 0xde, + 0xc7, 0xf6, 0x0b, 0xc4, 0x8f, 0xd0, 0x32, 0x20, 0x56, 0x5b, 0xde, 0x17, 0xad, 0xfb, 0xfb, 0x59, + 0xfb, 0xa7, 0x76, 0xda, 0x7d, 0x6d, 0x75, 0xee, 0xf4, 0xc9, 0x74, 0x36, 0x6e, 0xbf, 0x6f, 0x75, + 0x76, 0x7e, 0x6a, 0xbf, 0xef, 0xb4, 0x76, 0xc1, 0xdb, 0x2f, 0x2d, 0x77, 0xf4, 0xee, 0xd8, 0xfd, + 0xe6, 0xa5, 0xd5, 0xed, 0xdd, 0x69, 0xea, 0xbb, 0x71, 0xbb, 0xdd, 0x7e, 0x41, 0xd6, 0x84, 0x6f, + 0x6a, 0x4b, 0x7d, 0xb1, 0x33, 0x4c, 0xcf, 0x1b, 0x46, 0xd3, 0xb4, 0x76, 0xbb, 0xad, 0x94, 0xa2, + 0x0b, 0x6c, 0x67, 0x92, 0x9d, 0xa6, 0x4c, 0xca, 0xe2, 0x28, 0x74, 0xcd, 0x0e, 0x12, 0x4b, 0xdd, + 0xb0, 0x19, 0x21, 0xf7, 0xe8, 0x30, 0x4d, 0x46, 0x94, 0x33, 0xe3, 0x05, 0x58, 0x1b, 0x4d, 0x29, + 0x34, 0x81, 0xf1, 0x71, 0xc3, 0xce, 0x71, 0x12, 0x69, 0x51, 0x44, 0x14, 0xbb, 0xe2, 0x03, 0x9c, + 0x24, 0x5a, 0xa5, 0x54, 0x16, 0xc2, 0x4f, 0x6e, 0xd2, 0xd3, 0x27, 0xa0, 0xcc, 0xcc, 0x6e, 0x13, + 0x62, 0x34, 0x19, 0xda, 0xed, 0xd4, 0x59, 0x32, 0xb5, 0xe1, 0x53, 0x6c, 0x19, 0xdb, 0xf0, 0x31, + 0xfa, 0xcc, 0x2d, 0x83, 0xc1, 0x02, 0xd8, 0x32, 0xb9, 0xdb, 0xec, 0x00, 0x4b, 0x46, 0x77, 0x57, + 0x00, 0xe4, 0x65, 0x76, 0x85, 0x25, 0xc7, 0x72, 0x28, 0x8f, 0xa3, 0xc1, 0x15, 0x5d, 0x06, 0x98, + 0xde, 0x36, 0x4e, 0xd0, 0x36, 0x45, 0x46, 0x98, 0x21, 0x29, 0xcc, 0x99, 0x17, 0xde, 0xb2, 0x03, + 0x4f, 0x7e, 0x98, 0xc4, 0xaf, 0x7c, 0x79, 0x62, 0x5e, 0x39, 0x9a, 0x25, 0x53, 0x79, 0xf3, 0xc6, + 0x85, 0xc5, 0x6c, 0xa6, 0xc8, 0x2d, 0x9a, 0x47, 0xa6, 0x67, 0x19, 0x36, 0xc8, 0x7c, 0xa8, 0x71, + 0x09, 0x2d, 0x20, 0x68, 0x72, 0x8f, 0x9c, 0x49, 0x2b, 0x69, 0x44, 0x48, 0x23, 0x82, 0x91, 0x5c, + 0x68, 0x23, 0x26, 0xac, 0x91, 0x13, 0x0e, 0x2d, 0xc1, 0x12, 0x49, 0x49, 0x84, 0x19, 0x58, 0x6f, + 0x71, 0xe3, 0x8d, 0x36, 0xf0, 0x47, 0x1d, 0x18, 0x0d, 0x17, 0xe6, 0x88, 0x4b, 0x32, 0xf2, 0x42, + 0x9b, 0x33, 0xad, 0xc3, 0xa6, 0xd4, 0x58, 0x60, 0x23, 0x63, 0x06, 0x7f, 0xd0, 0x8b, 0x6a, 0x1f, + 0x5c, 0x0a, 0x69, 0x29, 0xa4, 0x73, 0xf6, 0xdf, 0x41, 0x06, 0xee, 0xf7, 0x18, 0xe4, 0xf3, 0x5b, + 0x0a, 0x50, 0xba, 0x3c, 0xdc, 0xbe, 0xa5, 0xb3, 0x26, 0xa5, 0xf3, 0xee, 0x96, 0x8c, 0x46, 0xa3, + 0x91, 0x14, 0xcf, 0x07, 0xe2, 0xd2, 0x6a, 0x87, 0xe1, 0xcb, 0x46, 0xe3, 0xe2, 0xc0, 0xd3, 0x63, + 0xa1, 0xbf, 0xa6, 0x81, 0xb9, 0x69, 0x81, 0x60, 0x72, 0x20, 0x2f, 0x7a, 0x7e, 0x7c, 0x3e, 0x2e, + 0xc7, 0xd6, 0x1d, 0xa1, 0xef, 0x8b, 0x69, 0x94, 0x64, 0x48, 0x8e, 0x84, 0x92, 0x35, 0x69, 0x48, + 0x49, 0x43, 0x8a, 0xf1, 0x58, 0x71, 0xc2, 0xdb, 0xa5, 0xf0, 0x8e, 0xd8, 0x8e, 0x19, 0x27, 0x09, + 0x03, 0xa9, 0x14, 0x34, 0x1c, 0x37, 0x0d, 0x6c, 0xa6, 0x5b, 0x05, 0x38, 0x05, 0xfd, 0xc4, 0x42, + 0xb3, 0x05, 0x9c, 0x29, 0x65, 0x98, 0x45, 0x9c, 0x53, 0xb2, 0x4c, 0x07, 0x33, 0xcd, 0x88, 0x0a, + 0x72, 0x7c, 0x8c, 0x51, 0xf6, 0x4c, 0xdb, 0x02, 0x59, 0x93, 0x13, 0xcf, 0x02, 0xc9, 0x84, 0xd8, + 0x94, 0x15, 0xe4, 0x80, 0x2d, 0xf5, 0x45, 0x1e, 0x04, 0x9a, 0xe7, 0x0d, 0x65, 0xac, 0x73, 0x20, + 0x9e, 0x1e, 0x8d, 0x4a, 0xcc, 0x20, 0x97, 0x67, 0x5d, 0x23, 0x0d, 0xd8, 0xce, 0x7a, 0x6d, 0x5a, + 0x18, 0xce, 0x80, 0x69, 0x00, 0xfc, 0x88, 0x6c, 0x69, 0xf8, 0x24, 0x94, 0x03, 0xd5, 0x66, 0x35, + 0xe1, 0x4a, 0x18, 0x1e, 0x4e, 0x3b, 0x03, 0xbf, 0x5c, 0x82, 0x93, 0xa5, 0x39, 0xd5, 0x97, 0xea, + 0x0a, 0x79, 0xb8, 0x99, 0x41, 0x3b, 0xe0, 0x9e, 0x96, 0x10, 0x2e, 0xab, 0x80, 0xfe, 0x13, 0xeb, + 0x01, 0xc8, 0x96, 0xfc, 0x40, 0xc9, 0x0f, 0x5c, 0x9b, 0x77, 0xa8, 0xae, 0x00, 0x53, 0xbd, 0x4f, + 0xd0, 0xf1, 0x91, 0x14, 0x3f, 0x25, 0x77, 0x7d, 0x8c, 0xc6, 0xaa, 0xc8, 0xdd, 0x1f, 0xa3, 0x21, + 0x9c, 0x08, 0xe4, 0xb9, 0x36, 0x22, 0xf5, 0x9c, 0xc9, 0xef, 0xfe, 0x98, 0x04, 0xcd, 0xec, 0x02, + 0x99, 0xb7, 0x1b, 0x02, 0x3a, 0x65, 0x87, 0xf4, 0xd5, 0xa1, 0x28, 0x78, 0x03, 0xe4, 0x96, 0x21, + 0x9b, 0xa7, 0x1f, 0xbe, 0x46, 0x5f, 0x25, 0xb2, 0xd3, 0x67, 0x4a, 0x59, 0xaf, 0x6c, 0xf2, 0x19, + 0x43, 0x23, 0x5b, 0x93, 0x97, 0x74, 0x24, 0xa5, 0xb7, 0x78, 0x59, 0x42, 0x7d, 0xbe, 0x44, 0x36, + 0xce, 0xee, 0xf0, 0x12, 0x42, 0xec, 0xb9, 0xc1, 0x4b, 0xc6, 0xb8, 0xa0, 0x5e, 0x6d, 0x5e, 0xc2, + 0x59, 0x8a, 0x6a, 0xf6, 0x02, 0x0d, 0x6c, 0x3d, 0xe7, 0x17, 0xb2, 0xfa, 0x60, 0x35, 0x3b, 0xdd, + 0x40, 0x40, 0x19, 0xab, 0xfa, 0xaf, 0xb0, 0x8e, 0x35, 0x1b, 0xa5, 0x7c, 0xda, 0x6d, 0x8f, 0x27, + 0xfc, 0x04, 0xea, 0xce, 0x1e, 0xb5, 0xee, 0xec, 0xef, 0x5d, 0x71, 0xee, 0xaf, 0xd1, 0x16, 0x49, + 0x08, 0x65, 0x0a, 0xe0, 0x4f, 0xa9, 0x04, 0x94, 0x2d, 0x7f, 0x2d, 0x38, 0x57, 0x4d, 0xaf, 0x64, + 0x43, 0x5f, 0x92, 0xe5, 0x70, 0x0c, 0x72, 0xbf, 0x2d, 0x97, 0x1f, 0x82, 0xf1, 0xeb, 0x2f, 0x8e, + 0x13, 0xcd, 0xcd, 0x80, 0x80, 0x16, 0xcc, 0x84, 0xd5, 0x6f, 0x78, 0xf2, 0x6d, 0x9a, 0xb9, 0x10, + 0x20, 0xeb, 0xc6, 0xb0, 0xb1, 0xee, 0x77, 0x26, 0x49, 0x5f, 0x95, 0xf2, 0x79, 0x43, 0x7e, 0x5b, + 0xdb, 0xcd, 0x23, 0x2d, 0x1a, 0x1a, 0x42, 0x36, 0x56, 0x1d, 0x03, 0xfd, 0xaf, 0x43, 0x68, 0xd4, + 0x16, 0x05, 0xda, 0xb7, 0x26, 0x47, 0x36, 0x7e, 0x48, 0x1d, 0xba, 0x6e, 0xd4, 0x13, 0x99, 0xa8, + 0x28, 0x7d, 0x6e, 0x43, 0xeb, 0x09, 0x5a, 0x14, 0x27, 0x53, 0x7c, 0xb8, 0x9a, 0x9d, 0x49, 0x21, + 0xa3, 0xae, 0x49, 0x4a, 0x9d, 0x84, 0x5a, 0x3e, 0xbd, 0x9e, 0x7b, 0x4a, 0x05, 0xad, 0x19, 0x6a, + 0x87, 0xd6, 0x4d, 0xcb, 0x77, 0xd1, 0x91, 0xc6, 0x01, 0x24, 0xbe, 0xa8, 0x48, 0xa7, 0xaa, 0x0c, + 0x58, 0x3d, 0x2e, 0xb7, 0x23, 0x1e, 0xc8, 0x4e, 0xcc, 0x39, 0xe7, 0x18, 0xaf, 0xa4, 0x76, 0x49, + 0xed, 0xb5, 0xa6, 0xf6, 0xb5, 0x69, 0x61, 0x7a, 0x6a, 0xf7, 0xa0, 0x25, 0xb5, 0x4b, 0x6a, 0xe7, + 0x2a, 0x13, 0x25, 0xb6, 0x67, 0xd9, 0xa5, 0x9f, 0xa1, 0x2c, 0x13, 0xa5, 0xce, 0x78, 0xd5, 0xbe, + 0x4c, 0x94, 0xa1, 0xdd, 0x4b, 0x1d, 0xb6, 0xa5, 0x89, 0x09, 0x27, 0x52, 0x87, 0x98, 0x72, 0xf3, + 0x4d, 0x74, 0x09, 0x89, 0x6a, 0xf2, 0x4f, 0x2c, 0x9d, 0x83, 0xc3, 0xa8, 0x83, 0xb0, 0x7e, 0xab, + 0x68, 0x0d, 0x28, 0x34, 0x66, 0x51, 0xb5, 0x43, 0xad, 0xdb, 0xc4, 0xa9, 0x9d, 0xcd, 0xc2, 0xf6, + 0xdd, 0x51, 0xa9, 0xbc, 0x2c, 0x63, 0x24, 0xa4, 0xd4, 0x21, 0x86, 0x2f, 0x72, 0xc2, 0xa5, 0xc8, + 0xc6, 0x7f, 0xf3, 0xde, 0xf2, 0xf0, 0xd5, 0x7f, 0x4b, 0xe5, 0x51, 0xdf, 0xcc, 0x58, 0x19, 0xed, + 0x4a, 0x68, 0xc2, 0x76, 0xab, 0xc5, 0x0a, 0xab, 0x14, 0x57, 0x2c, 0xec, 0xc0, 0xed, 0x39, 0x78, + 0xe7, 0x4a, 0x93, 0x9a, 0x07, 0x7d, 0xfd, 0x29, 0x8a, 0x0a, 0xd8, 0x35, 0xb7, 0xb9, 0x58, 0x16, + 0xaa, 0x1a, 0x14, 0xa6, 0xcb, 0x40, 0x65, 0xd9, 0x89, 0xb7, 0xec, 0xe8, 0x3e, 0x4d, 0x94, 0x9f, + 0x2d, 0x86, 0x4f, 0x90, 0x67, 0x29, 0x5c, 0x46, 0xd7, 0xec, 0xb5, 0x8e, 0x2d, 0x5e, 0x25, 0x35, + 0xd6, 0x8e, 0x1a, 0xef, 0x5c, 0x6a, 0xbc, 0x9c, 0x3a, 0x96, 0x05, 0x0d, 0xdc, 0x6a, 0x6f, 0x0a, + 0x0e, 0xc7, 0x9d, 0x9d, 0x66, 0xc1, 0x8d, 0xb9, 0x68, 0xc2, 0x43, 0x50, 0xeb, 0xcc, 0x5b, 0x49, + 0xb4, 0xd7, 0xc4, 0x25, 0xb8, 0x0f, 0xba, 0xb2, 0xdc, 0x2b, 0xe5, 0x5f, 0x18, 0xf1, 0xe7, 0x23, + 0x04, 0x2b, 0xdd, 0xd0, 0x17, 0x9e, 0x79, 0xbf, 0x2d, 0xee, 0x03, 0x53, 0xdd, 0xb5, 0x41, 0xc0, + 0x04, 0x82, 0xa0, 0xb1, 0xcc, 0xec, 0xec, 0x68, 0xae, 0x90, 0x60, 0xda, 0x94, 0xaa, 0x2f, 0x95, + 0xa8, 0xda, 0xe0, 0x24, 0x9a, 0x78, 0x64, 0x9b, 0xf3, 0x76, 0xb1, 0xc2, 0x4c, 0x15, 0x5f, 0x06, + 0xc4, 0xdf, 0x4d, 0xeb, 0x9b, 0x8a, 0xb6, 0x29, 0xe9, 0x0c, 0xc3, 0x33, 0x01, 0xb9, 0x67, 0xd3, + 0xd3, 0x40, 0xf5, 0xb6, 0x3b, 0x0d, 0x24, 0xaf, 0xf8, 0xda, 0x97, 0x35, 0x91, 0x4a, 0x0b, 0x0d, + 0x32, 0x25, 0xd2, 0x68, 0xa5, 0x6c, 0x3b, 0x42, 0x5e, 0xf1, 0x95, 0x35, 0xcd, 0x86, 0x5d, 0xf1, + 0x75, 0x54, 0x37, 0xdb, 0x48, 0x4e, 0x67, 0xe7, 0x74, 0x79, 0xb3, 0x8d, 0xbc, 0xd9, 0x46, 0xcc, + 0x62, 0xab, 0xbc, 0xd9, 0x86, 0xe1, 0x52, 0x9b, 0xba, 0xd5, 0xc9, 0x49, 0xa1, 0x95, 0x67, 0xe2, + 0x26, 0xd1, 0xad, 0x5a, 0x70, 0xce, 0xd2, 0x5c, 0x2b, 0xfa, 0x18, 0x5d, 0x3d, 0x45, 0xb7, 0x2e, + 0xf5, 0x14, 0x44, 0xf2, 0x10, 0x91, 0xcf, 0xaa, 0xb8, 0x8c, 0x82, 0x44, 0x3e, 0x94, 0xa2, 0x25, + 0xaf, 0x23, 0x77, 0x0e, 0x59, 0x31, 0x48, 0x13, 0x6e, 0xe9, 0xc2, 0x68, 0x22, 0x71, 0x93, 0x1b, + 0x0f, 0xd9, 0x15, 0x27, 0xbf, 0x22, 0xa1, 0xa7, 0x42, 0xe4, 0x28, 0x24, 0xf6, 0x54, 0x98, 0x3c, + 0xe9, 0xa3, 0x4c, 0x80, 0xa5, 0x07, 0x35, 0x6d, 0xd1, 0x0f, 0x47, 0x10, 0x98, 0x27, 0x28, 0x5c, + 0x2c, 0x65, 0xc1, 0xbb, 0x0b, 0x1c, 0xbd, 0x32, 0xd8, 0x42, 0xc6, 0xec, 0xd6, 0x69, 0xe6, 0xbe, + 0x9f, 0x9d, 0xc5, 0xce, 0xcc, 0x82, 0x7b, 0x47, 0xd3, 0xfa, 0xf0, 0x12, 0x68, 0x3c, 0x67, 0xcc, + 0x0b, 0x9d, 0xca, 0x4f, 0xb2, 0x74, 0x74, 0x5e, 0x9b, 0x1e, 0x53, 0x53, 0x4f, 0xa8, 0x38, 0x16, + 0x9c, 0x81, 0xef, 0x8f, 0xd0, 0x88, 0x04, 0x5a, 0x91, 0x0d, 0x6c, 0x88, 0x39, 0xa6, 0x0d, 0x0a, + 0x1e, 0xd3, 0x17, 0x19, 0x90, 0x16, 0x2e, 0x24, 0xb2, 0x05, 0x06, 0xd7, 0xee, 0x72, 0x8d, 0xfd, + 0xfa, 0xa6, 0xdc, 0x27, 0x5e, 0x4f, 0xf7, 0xc5, 0x2d, 0x89, 0x86, 0x76, 0xa7, 0x27, 0x4b, 0x33, + 0xe8, 0xef, 0xee, 0x77, 0xb4, 0x38, 0x3b, 0xeb, 0x78, 0x8d, 0x42, 0x2e, 0xc1, 0x49, 0x54, 0x24, + 0x4f, 0xcd, 0xd5, 0xca, 0x34, 0x2e, 0x56, 0xfa, 0x54, 0x7d, 0xb2, 0xe6, 0x27, 0xed, 0xea, 0xb9, + 0xeb, 0x93, 0x69, 0xae, 0x27, 0xfa, 0xf4, 0xdb, 0x16, 0xc7, 0x76, 0x24, 0x57, 0x11, 0x21, 0x04, + 0xd3, 0x00, 0xbb, 0x91, 0x71, 0x60, 0xce, 0x81, 0xb7, 0xca, 0x60, 0x3d, 0x92, 0xe9, 0x28, 0x99, + 0x4e, 0xe8, 0xa6, 0x4b, 0x5e, 0xdc, 0xe5, 0x45, 0xbf, 0xf5, 0x60, 0x2a, 0x3f, 0xfe, 0x92, 0xce, + 0x90, 0x33, 0x3f, 0x71, 0x50, 0x07, 0x86, 0xfc, 0xea, 0x4d, 0x7e, 0x87, 0x32, 0x80, 0x69, 0x2c, + 0x9f, 0x19, 0x68, 0x23, 0x58, 0x8f, 0x64, 0x48, 0x4a, 0x86, 0x14, 0xba, 0xe9, 0x47, 0xce, 0x90, + 0x99, 0x85, 0x17, 0xe3, 0x98, 0x89, 0x79, 0xe7, 0xf5, 0xbd, 0x89, 0x97, 0x65, 0x44, 0x7f, 0x1f, + 0x6f, 0x19, 0x36, 0xd9, 0x6f, 0xaa, 0x66, 0x56, 0xa9, 0x2b, 0x78, 0x7c, 0x1a, 0x48, 0x76, 0x43, + 0xa2, 0x95, 0xeb, 0x92, 0x57, 0x79, 0x2c, 0x56, 0x21, 0x3b, 0x5f, 0x4f, 0x86, 0xa5, 0x86, 0x1e, + 0x8b, 0xaa, 0x23, 0xa1, 0x08, 0xd6, 0x51, 0xdd, 0x3b, 0x48, 0xc5, 0x34, 0x32, 0xda, 0x24, 0xa3, + 0x4d, 0x47, 0x1b, 0x6d, 0x4a, 0x29, 0x49, 0x8c, 0x28, 0xbe, 0x28, 0x9b, 0x74, 0xfc, 0xf6, 0x70, + 0xc7, 0x1c, 0x98, 0xe2, 0xed, 0xb9, 0x58, 0x82, 0xd6, 0xcf, 0x0f, 0x95, 0xc4, 0xfb, 0xa2, 0xcb, + 0x58, 0x14, 0x8b, 0x38, 0xe1, 0xdb, 0x5d, 0xa9, 0xbd, 0x29, 0x20, 0xf2, 0x12, 0x4d, 0x94, 0xe7, + 0xa2, 0xb6, 0x76, 0x40, 0x76, 0x55, 0xe7, 0xae, 0x79, 0xd5, 0x89, 0xca, 0x32, 0xfa, 0x6c, 0x26, + 0x20, 0xd6, 0x7f, 0xfe, 0xe1, 0x0f, 0xb2, 0x69, 0x30, 0x14, 0xe9, 0xd7, 0x18, 0x7e, 0xfa, 0x02, + 0x73, 0x62, 0x23, 0xb2, 0xe9, 0x83, 0x4c, 0xdb, 0x96, 0xc3, 0x4d, 0xec, 0x87, 0xde, 0x77, 0x99, + 0x46, 0xf5, 0x25, 0xdd, 0x5c, 0xa5, 0x20, 0x20, 0x20, 0xaf, 0xb4, 0x93, 0x57, 0xda, 0xd5, 0x60, + 0x53, 0xe4, 0x9d, 0x49, 0x14, 0x36, 0xac, 0xab, 0xf9, 0xb6, 0xff, 0x4a, 0xbd, 0x1d, 0x29, 0x91, + 0x4c, 0x4a, 0xcb, 0x24, 0x19, 0x88, 0x2b, 0x8b, 0x24, 0xa8, 0x63, 0x7a, 0x85, 0x59, 0xa3, 0xc3, + 0xed, 0xa4, 0x5e, 0x5d, 0x56, 0xa8, 0x39, 0x37, 0x10, 0x30, 0xb3, 0x52, 0x4a, 0x2e, 0x28, 0x35, + 0x11, 0x64, 0x20, 0xae, 0x24, 0x90, 0x20, 0x7e, 0xaa, 0x30, 0xe9, 0x73, 0xb8, 0xfc, 0x54, 0x5d, + 0x52, 0xe7, 0x50, 0xf8, 0x69, 0x37, 0x60, 0x65, 0x3b, 0x13, 0xbf, 0x17, 0x99, 0x3a, 0x81, 0x73, + 0xd3, 0x82, 0xad, 0x30, 0x82, 0x75, 0x0a, 0x4e, 0xce, 0x4e, 0xd2, 0x33, 0x3c, 0xdb, 0x87, 0xf4, + 0x39, 0x86, 0x56, 0xf2, 0x19, 0x31, 0x79, 0x9e, 0x32, 0xae, 0x04, 0xac, 0x20, 0xaf, 0x73, 0x24, + 0x37, 0x06, 0xee, 0x3f, 0x6f, 0xf3, 0x2a, 0x9b, 0x3a, 0xc9, 0xa6, 0x4e, 0xfb, 0x3c, 0xa5, 0x4f, + 0xb0, 0x34, 0x1e, 0x4d, 0x3a, 0xd9, 0x56, 0xf0, 0x84, 0xfe, 0x4d, 0x9a, 0x1d, 0xa9, 0xcf, 0x66, + 0x70, 0x06, 0xb0, 0x09, 0xf4, 0x0d, 0x43, 0x81, 0x84, 0xd2, 0x73, 0xe7, 0x77, 0x34, 0x47, 0xf6, + 0x8b, 0xed, 0x52, 0x3d, 0xcf, 0xf0, 0x97, 0xd7, 0xfd, 0x2a, 0x3b, 0xba, 0xcb, 0xd5, 0x05, 0x2b, + 0x33, 0x8e, 0x5b, 0xa4, 0x7b, 0x4c, 0x73, 0x7b, 0x19, 0xc9, 0x03, 0x55, 0xd4, 0x21, 0xd4, 0xed, + 0x45, 0x9a, 0xd0, 0x35, 0x2f, 0xa7, 0x18, 0xce, 0xf2, 0x82, 0xa5, 0xf2, 0x24, 0x68, 0xbd, 0x4e, + 0x82, 0x9e, 0xbf, 0x3d, 0x9c, 0x93, 0xa0, 0xe4, 0x8e, 0x44, 0x18, 0x5a, 0x46, 0x2e, 0x49, 0x29, + 0x77, 0x57, 0xea, 0x7f, 0xe9, 0xea, 0xff, 0x69, 0xea, 0xe8, 0x97, 0xff, 0xf8, 0xa7, 0x7f, 0xfe, + 0x97, 0xff, 0xbe, 0x77, 0x34, 0xad, 0x37, 0x6c, 0xb5, 0x5f, 0xfe, 0xed, 0xf2, 0x7f, 0xfe, 0xff, + 0xec, 0xf4, 0xe4, 0xe1, 0xe2, 0xdf, 0xdf, 0xab, 0xe3, 0x2d, 0x14, 0x20, 0x81, 0xfd, 0xab, 0x52, + 0xca, 0xb1, 0xd5, 0xb5, 0x65, 0x62, 0x73, 0x6a, 0x2e, 0xf3, 0xe5, 0x6b, 0x08, 0x29, 0x0f, 0xad, + 0x36, 0xf5, 0xd0, 0xea, 0x64, 0xc1, 0x70, 0x97, 0x83, 0x0b, 0x2c, 0x0f, 0xa8, 0xca, 0x03, 0xaa, + 0xe9, 0x80, 0x34, 0xad, 0x7e, 0x32, 0x71, 0x46, 0xd7, 0xd2, 0x25, 0x3e, 0x31, 0x8a, 0x56, 0x40, + 0x49, 0x77, 0xd1, 0xc8, 0x6f, 0xb3, 0x42, 0x21, 0xf1, 0x19, 0x4c, 0x49, 0x6e, 0xda, 0xe7, 0xe1, + 0x81, 0xe2, 0xbc, 0x50, 0x34, 0x4a, 0x25, 0xeb, 0x17, 0x79, 0x4c, 0x5d, 0x81, 0x7c, 0x01, 0x28, + 0x5b, 0x1f, 0x25, 0x23, 0x11, 0x34, 0xad, 0x90, 0x92, 0x4b, 0x63, 0x69, 0x8d, 0x94, 0x78, 0x9a, + 0xad, 0x55, 0x52, 0xe2, 0x71, 0xfa, 0xd6, 0x49, 0x9c, 0x98, 0x8b, 0x2c, 0x93, 0xb6, 0xb5, 0x52, + 0xe2, 0x51, 0xa6, 0x56, 0x4b, 0x59, 0x02, 0x2b, 0xaf, 0xf5, 0x52, 0x4d, 0xf2, 0x13, 0x8d, 0x2a, + 0x3c, 0x05, 0xad, 0x33, 0x70, 0x09, 0x4e, 0x02, 0xfc, 0x78, 0x37, 0x97, 0xdf, 0x1b, 0xa0, 0x75, + 0x76, 0xd6, 0xd1, 0xe7, 0x48, 0xb5, 0xf5, 0x39, 0xba, 0xdb, 0x7c, 0xf0, 0xfc, 0xce, 0xcb, 0x13, + 0xb4, 0x7e, 0x3a, 0x57, 0x1d, 0x03, 0x4d, 0x75, 0x1b, 0x9f, 0x8c, 0x77, 0x3a, 0x73, 0x9e, 0xf8, + 0xa8, 0xa2, 0x7c, 0xcf, 0x50, 0xc4, 0x7b, 0xe0, 0xd3, 0xda, 0x28, 0x38, 0x8f, 0x73, 0x75, 0xa9, + 0x4f, 0xe0, 0x12, 0xce, 0x84, 0xad, 0x4b, 0xe0, 0xfb, 0x96, 0xfd, 0xa7, 0xb5, 0xa1, 0x8a, 0xda, + 0xf5, 0xf0, 0x6d, 0x42, 0xf6, 0xde, 0x32, 0x1d, 0x0c, 0x55, 0xac, 0x5b, 0x0b, 0x48, 0x78, 0x4f, + 0xf5, 0x05, 0xcd, 0x7f, 0x35, 0xfc, 0x78, 0xe6, 0xa3, 0x17, 0xf2, 0xb4, 0xa0, 0x6d, 0x83, 0xb9, + 0xbe, 0x42, 0x4b, 0x04, 0xed, 0xb0, 0x08, 0xd7, 0x9f, 0x2d, 0x75, 0x13, 0x57, 0x91, 0x19, 0xad, + 0x32, 0x32, 0x5b, 0x42, 0x33, 0x5c, 0xa9, 0x66, 0x05, 0xd3, 0xa6, 0xca, 0xf2, 0x65, 0xea, 0x28, + 0xd3, 0x86, 0xc7, 0x38, 0x1c, 0x88, 0xcd, 0x93, 0x6c, 0x46, 0x7b, 0x57, 0x1a, 0xed, 0xd2, 0x68, + 0x2f, 0xe6, 0xf8, 0x16, 0x72, 0x80, 0x05, 0x19, 0xfc, 0xdc, 0x0e, 0x71, 0xc2, 0x4a, 0x65, 0x7a, + 0x72, 0xcc, 0x38, 0x43, 0x36, 0x4f, 0x99, 0x9b, 0xf9, 0x8a, 0x30, 0xa1, 0x38, 0x66, 0x14, 0xa5, + 0x1d, 0x0b, 0x33, 0xa7, 0x70, 0x75, 0x28, 0x84, 0x59, 0x39, 0xd5, 0x16, 0xab, 0xdf, 0xc6, 0xea, + 0x79, 0x0b, 0x64, 0x48, 0x5e, 0x4f, 0xbc, 0x98, 0x47, 0x2e, 0xc6, 0x33, 0x17, 0xe4, 0xa1, 0x0b, + 0xf0, 0xd4, 0x39, 0x31, 0x2f, 0xc0, 0x73, 0x17, 0xe3, 0xc1, 0x17, 0xf5, 0xe4, 0xeb, 0x67, 0x10, + 0xb2, 0x9c, 0x8d, 0x88, 0x39, 0x4d, 0x05, 0xb4, 0x62, 0xec, 0x35, 0x52, 0xeb, 0x48, 0xad, 0x73, + 0xe0, 0x5a, 0x07, 0xcd, 0xa0, 0x81, 0x11, 0x7e, 0x66, 0x3b, 0xbb, 0x9e, 0xd0, 0x3a, 0x03, 0x8e, + 0x67, 0x6f, 0x82, 0xa1, 0xaf, 0x75, 0x5b, 0x40, 0xf8, 0x61, 0xb2, 0x58, 0xab, 0x81, 0x8b, 0xac, + 0x7a, 0x2e, 0xf2, 0x33, 0x2f, 0x1d, 0x79, 0x45, 0x05, 0x36, 0x97, 0x75, 0xcb, 0x1f, 0x2c, 0x4c, + 0x5d, 0x13, 0x7c, 0x5a, 0x1b, 0x0a, 0xf7, 0xab, 0x5e, 0x4f, 0xab, 0x9e, 0x7f, 0x5a, 0xd8, 0xaf, + 0xf1, 0xeb, 0x39, 0x8c, 0x75, 0x0c, 0x0f, 0x0c, 0x2f, 0xc3, 0x43, 0x58, 0x47, 0x32, 0x0c, 0x7d, + 0x10, 0xab, 0x39, 0x08, 0xdc, 0x44, 0xc3, 0xf0, 0x05, 0xd6, 0xc1, 0xf5, 0xe4, 0xb8, 0x66, 0x66, + 0x47, 0x81, 0xe4, 0x5b, 0xf8, 0x0e, 0xae, 0x24, 0x5c, 0x71, 0x9c, 0xc6, 0x9a, 0x3d, 0x7a, 0x69, + 0x39, 0x2f, 0xb7, 0xd5, 0xf6, 0xce, 0x28, 0xed, 0x56, 0x24, 0x7b, 0x47, 0x95, 0xc0, 0x25, 0x88, + 0x75, 0xd7, 0x09, 0xff, 0xf3, 0x6f, 0xfe, 0xdb, 0x9c, 0xa8, 0xdd, 0x1c, 0xf9, 0x2b, 0xe2, 0xbc, + 0x0a, 0x49, 0xc8, 0x24, 0x56, 0xfa, 0xf1, 0xef, 0x9f, 0xff, 0x00, 0xc8, 0x06, 0x86, 0x89, 0x81, + 0xed, 0xac, 0xd7, 0xa6, 0x85, 0xe1, 0x0c, 0x20, 0x23, 0x51, 0x8e, 0x6e, 0x03, 0x13, 0x3f, 0x42, + 0x0b, 0xe0, 0x47, 0xdd, 0x28, 0xd8, 0xf7, 0x51, 0x94, 0x77, 0x90, 0xe5, 0x29, 0x88, 0xca, 0xdc, + 0x08, 0x77, 0x1e, 0x32, 0x1d, 0x89, 0x62, 0x58, 0x28, 0x34, 0x97, 0xd7, 0x3d, 0x8b, 0x2b, 0x4e, + 0x61, 0x2d, 0x92, 0xa9, 0x63, 0x49, 0xd3, 0x43, 0x66, 0xee, 0xe8, 0x42, 0x25, 0x93, 0x57, 0xcd, + 0xe4, 0x62, 0xb0, 0x21, 0x99, 0x9d, 0x8d, 0xd9, 0x53, 0xaa, 0x37, 0xda, 0xe0, 0x80, 0x79, 0xfe, + 0x53, 0x5f, 0x6a, 0xf4, 0x1a, 0x30, 0x7b, 0x41, 0x34, 0x48, 0x2e, 0xe7, 0xe4, 0xf2, 0x61, 0x84, + 0xcb, 0x25, 0x93, 0x4b, 0x26, 0x97, 0x4c, 0x7e, 0x38, 0x4c, 0x9e, 0x5a, 0x28, 0x7a, 0xd0, 0x6c, + 0xee, 0xae, 0x55, 0xbd, 0xf9, 0xfc, 0x74, 0x2e, 0x79, 0xbd, 0x72, 0x5e, 0x17, 0x80, 0x0b, 0xc9, + 0xf0, 0xcc, 0x0c, 0x3f, 0x3c, 0x4e, 0x86, 0x1f, 0x4a, 0x86, 0xaf, 0x0d, 0xc3, 0x0f, 0x8f, 0x85, + 0xe1, 0xdf, 0x94, 0x9b, 0x7a, 0xa8, 0xea, 0x34, 0x50, 0xd0, 0xb9, 0x88, 0xa7, 0x7a, 0x87, 0xae, + 0xa5, 0x51, 0xe2, 0x29, 0xea, 0x16, 0x47, 0xc9, 0x27, 0x0b, 0xb4, 0x3c, 0x4a, 0xbc, 0x8c, 0xbe, + 0x05, 0x52, 0xf6, 0xa3, 0xb9, 0x2d, 0x91, 0x78, 0xb1, 0xc2, 0xd8, 0x09, 0x3c, 0x7c, 0x8e, 0xa5, + 0x67, 0xcc, 0xa6, 0x35, 0x40, 0x67, 0xb2, 0x58, 0x77, 0x18, 0x6b, 0xf3, 0x01, 0x53, 0x3f, 0x99, + 0xcf, 0xc1, 0x48, 0x0f, 0xd7, 0x8b, 0xf5, 0xc3, 0xd5, 0x1c, 0x7d, 0x75, 0x07, 0xda, 0xe7, 0x91, + 0x05, 0x07, 0x9b, 0x86, 0xb9, 0x32, 0x1d, 0x5b, 0xf5, 0x3b, 0x51, 0x72, 0x9c, 0x5d, 0x48, 0xbc, + 0x42, 0x9e, 0x3c, 0x96, 0x87, 0x18, 0x84, 0xf0, 0xfa, 0xad, 0x6e, 0xcc, 0x74, 0x6c, 0x5a, 0xcf, + 0x0c, 0x67, 0x5d, 0x0a, 0x9c, 0x56, 0xb6, 0x55, 0xc3, 0x59, 0x4d, 0xa0, 0xc5, 0x71, 0x56, 0x99, + 0xe5, 0xba, 0x95, 0x2f, 0xba, 0xe1, 0xd9, 0x7e, 0xa5, 0x1f, 0x15, 0xe5, 0xe9, 0x76, 0x1e, 0x3e, + 0xcc, 0xd9, 0xf5, 0x3c, 0x7c, 0xbe, 0x68, 0xa3, 0xef, 0x2d, 0x7a, 0x78, 0x1b, 0x7e, 0x17, 0x70, + 0x48, 0xb8, 0xba, 0xa2, 0x27, 0xb6, 0xee, 0xbc, 0x37, 0x3a, 0x1f, 0x0d, 0xdf, 0xf6, 0x46, 0x83, + 0xe6, 0xef, 0xe1, 0x01, 0x1c, 0xcf, 0xf3, 0xf2, 0x68, 0x96, 0x8a, 0x66, 0xec, 0x3a, 0x6e, 0xfb, + 0xa8, 0xd4, 0x6d, 0x52, 0xb7, 0x35, 0x50, 0xb7, 0xa1, 0xb0, 0x8e, 0x99, 0xa7, 0x11, 0xc7, 0x88, + 0xe1, 0x99, 0x60, 0x8e, 0x7b, 0xeb, 0x83, 0xe0, 0x05, 0x77, 0xd9, 0xd7, 0x96, 0x58, 0xe3, 0x3b, + 0x8e, 0x67, 0x69, 0x3b, 0xa7, 0x65, 0xbe, 0xa0, 0xd5, 0xba, 0xd3, 0xd4, 0xd1, 0xf8, 0xe5, 0xae, + 0xab, 0x8e, 0xc6, 0xfe, 0xc7, 0xae, 0xf7, 0x3f, 0xff, 0x73, 0xef, 0x4e, 0x53, 0xcf, 0x37, 0x9f, + 0x07, 0x77, 0x9a, 0x3a, 0x18, 0xb7, 0xef, 0xef, 0xcf, 0xda, 0x3f, 0xfb, 0xaf, 0xec, 0x0f, 0x2a, + 0x65, 0xbb, 0xe9, 0xa7, 0x7b, 0x44, 0xf9, 0xb0, 0xc1, 0x28, 0xbf, 0x78, 0x71, 0x11, 0xa3, 0xab, + 0xf3, 0x2b, 0xf5, 0xb7, 0xf1, 0x4f, 0xed, 0xf4, 0xfc, 0xb5, 0x7d, 0xd1, 0x6e, 0xed, 0x7e, 0x77, + 0xd1, 0xfe, 0xa9, 0x9d, 0x0e, 0x5e, 0x5b, 0xad, 0x94, 0x5f, 0xde, 0xa7, 0xbd, 0xa3, 0xfd, 0xd2, + 0x6a, 0xb5, 0x02, 0x64, 0xc7, 0x08, 0xe0, 0x4e, 0xeb, 0x8e, 0xdf, 0x7b, 0x1f, 0xfd, 0xbf, 0x21, + 0x09, 0x51, 0x01, 0xb7, 0xcb, 0x27, 0x9c, 0x86, 0xdd, 0x45, 0x56, 0xc7, 0x2b, 0x5c, 0x0c, 0x13, + 0xb7, 0xa8, 0x63, 0xdb, 0xb1, 0xa8, 0xf6, 0xbd, 0x12, 0x5c, 0xc9, 0x71, 0xaf, 0x54, 0x70, 0xb1, + 0xc4, 0xf5, 0xef, 0x9f, 0xc3, 0x2e, 0xea, 0x5e, 0x50, 0x67, 0x13, 0x33, 0x5d, 0x9b, 0xb6, 0x8d, + 0x26, 0x4b, 0x48, 0x73, 0x99, 0x08, 0x90, 0x3d, 0xec, 0x03, 0xd3, 0x46, 0xd0, 0x76, 0x1e, 0xee, + 0x65, 0x12, 0x39, 0xf7, 0xec, 0xd3, 0x36, 0x3e, 0x2f, 0x81, 0x0b, 0xe2, 0x89, 0x02, 0xef, 0xfe, + 0x81, 0x4c, 0x44, 0x31, 0xf4, 0x3f, 0x3f, 0x0e, 0x9a, 0x67, 0xde, 0xbc, 0xba, 0x5f, 0xcd, 0x40, + 0xe1, 0xce, 0xae, 0x2d, 0x68, 0x43, 0x63, 0x0a, 0xcb, 0x54, 0x2b, 0x1f, 0x36, 0x17, 0x5c, 0x80, + 0xeb, 0xdf, 0x3f, 0xef, 0xdf, 0x53, 0xf4, 0xd7, 0x57, 0xa5, 0xaf, 0x18, 0xdf, 0x80, 0x86, 0xdd, + 0x35, 0x57, 0xe6, 0xa5, 0xa9, 0xd1, 0x14, 0x89, 0xd0, 0x9b, 0x52, 0xa3, 0x19, 0x11, 0x65, 0x2f, + 0x37, 0x94, 0x94, 0x71, 0xeb, 0x40, 0x4e, 0x73, 0x69, 0xbe, 0x2d, 0x29, 0x72, 0xe7, 0x00, 0x26, + 0x85, 0x0e, 0x42, 0x7e, 0xf7, 0xa0, 0xb2, 0xba, 0x31, 0x53, 0x34, 0xe8, 0x51, 0x88, 0x09, 0xec, + 0xb1, 0xbc, 0xcd, 0xa0, 0xf1, 0xb7, 0x19, 0xd0, 0x1d, 0xed, 0xa7, 0x39, 0xc2, 0xcf, 0x76, 0x54, + 0x7f, 0xeb, 0xe9, 0x20, 0x95, 0x40, 0xa5, 0xf1, 0x18, 0x3c, 0xdd, 0x79, 0x7b, 0xc6, 0x58, 0x30, + 0x75, 0x8d, 0x06, 0x85, 0x21, 0xcb, 0x38, 0x34, 0x9d, 0xe9, 0x22, 0x7e, 0x5c, 0xb4, 0xa6, 0xbb, + 0xb3, 0x51, 0xfc, 0xc8, 0xd4, 0xd7, 0x45, 0x92, 0x75, 0xe9, 0x98, 0x95, 0x4f, 0xde, 0x90, 0xbf, + 0xd9, 0x59, 0x28, 0xe1, 0x92, 0x2b, 0x72, 0x25, 0x48, 0x7e, 0xc5, 0x07, 0x57, 0x65, 0x07, 0x45, + 0x05, 0x07, 0x45, 0xa5, 0xc6, 0xee, 0x22, 0x73, 0xf4, 0x24, 0x8b, 0x7e, 0x4c, 0xe1, 0x5e, 0x6a, + 0x75, 0x18, 0xa7, 0x85, 0x2d, 0x5e, 0x22, 0xd3, 0x55, 0xd6, 0x7e, 0x94, 0x0e, 0x43, 0x3b, 0xd9, + 0x3c, 0x69, 0x7b, 0x01, 0x44, 0x04, 0x68, 0x17, 0x9f, 0xa9, 0x1a, 0x29, 0x53, 0x13, 0x91, 0x34, + 0x50, 0x9a, 0xe6, 0x49, 0x5b, 0x7e, 0x8e, 0xba, 0xa1, 0x56, 0x33, 0xd4, 0xea, 0x25, 0x4b, 0xad, + 0x28, 0x8c, 0xb4, 0x9f, 0xa9, 0x3b, 0xb6, 0x2b, 0xf7, 0x3b, 0x79, 0xa5, 0x2c, 0x9a, 0x10, 0x89, + 0xcd, 0xbb, 0xe8, 0x86, 0x7c, 0x53, 0x5c, 0xbe, 0x3e, 0xd9, 0x24, 0x69, 0x73, 0x32, 0xf7, 0xcc, + 0x39, 0x58, 0xfa, 0x5c, 0xeb, 0x2b, 0xf9, 0x0a, 0x3b, 0xfa, 0x25, 0x74, 0xb5, 0xea, 0xd6, 0x40, + 0x29, 0x3e, 0x53, 0x4c, 0xbf, 0xdc, 0x50, 0xba, 0xf2, 0xa8, 0x2f, 0x97, 0x26, 0x88, 0x04, 0xba, + 0x53, 0xee, 0x8c, 0x19, 0x53, 0x48, 0x03, 0x4b, 0x37, 0x16, 0xd0, 0x65, 0xf3, 0x4f, 0x50, 0x9f, + 0x2f, 0x11, 0x49, 0x26, 0x24, 0x41, 0xa5, 0x64, 0x28, 0x51, 0x32, 0x38, 0xc8, 0xc0, 0xfd, 0x1e, + 0x41, 0x32, 0xa4, 0x70, 0x67, 0x4e, 0xfd, 0x8c, 0x28, 0xb9, 0x50, 0x25, 0x53, 0x89, 0x12, 0x0c, + 0x7d, 0xad, 0x76, 0x92, 0xe1, 0xb4, 0x54, 0xa4, 0x0d, 0x34, 0xed, 0x00, 0xd0, 0x36, 0xd0, 0x7a, + 0x47, 0x86, 0xb7, 0xd1, 0x68, 0x34, 0x6a, 0x3e, 0xde, 0xaa, 0x5d, 0x05, 0xad, 0x26, 0x26, 0x8a, + 0xec, 0x06, 0x7b, 0x2b, 0x64, 0xd5, 0x6f, 0xa3, 0x85, 0x01, 0x67, 0x14, 0x8a, 0x3f, 0x00, 0x94, + 0x6a, 0xbf, 0x44, 0xb5, 0x9f, 0xa7, 0xf5, 0xfb, 0x95, 0x69, 0xfd, 0x7e, 0xb9, 0x0a, 0xc4, 0xdd, + 0xfc, 0xf2, 0xe5, 0xd0, 0xb0, 0xba, 0x25, 0x54, 0xa3, 0xf4, 0x1b, 0x8f, 0xb1, 0xc1, 0x71, 0x61, + 0xac, 0xdf, 0x6b, 0x3c, 0xc6, 0xba, 0x47, 0x85, 0x30, 0xe9, 0x0c, 0x55, 0x62, 0x9c, 0x91, 0x4d, + 0x1a, 0xc7, 0xa0, 0x36, 0x6a, 0x42, 0x50, 0x69, 0xd6, 0xc8, 0x68, 0x86, 0x64, 0x60, 0x19, 0xcd, + 0x90, 0xd1, 0x0c, 0x19, 0xcd, 0x38, 0xc0, 0x68, 0xc6, 0x9b, 0xf8, 0xa7, 0x60, 0x2f, 0xb2, 0x32, + 0x97, 0x0a, 0xb2, 0x7f, 0xd3, 0xbf, 0xc1, 0x2f, 0xa6, 0x99, 0xd4, 0x20, 0xbb, 0xd9, 0x4c, 0x25, + 0xfa, 0x53, 0x2c, 0x53, 0xf9, 0x2b, 0x7c, 0x42, 0x9b, 0xac, 0xe4, 0xeb, 0x9b, 0xd7, 0x7f, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x01, 0x00, 0x00, 0xff, 0xff, 0x96, 0xa3, 0xd9, 0xc2, 0x2a, 0x7c, 0x01, + 0x00, } ) @@ -1710,6 +1718,9 @@ func initΛEnumTypes(){ "/interface/admin-state": []reflect.Type{ reflect.TypeOf((E_SdcioModelIf_AdminState)(0)), }, + "/interface/subinterface/admin-state": []reflect.Type{ + reflect.TypeOf((E_SdcioModelIf_AdminState)(0)), + }, "/interface/subinterface/type": []reflect.Type{ reflect.TypeOf((E_SdcioModelCommon_SiType)(0)), },