diff --git a/docs/cmd/set.md b/docs/cmd/set.md index e86cee64..721df3c0 100644 --- a/docs/cmd/set.md +++ b/docs/cmd/set.md @@ -93,6 +93,26 @@ It expects a file containing one or multiple CLI commands which will form the va See [this section](#templated-set-request-file) below. +### commit-id + +The `--commit-id` flag sets the commit ID when the client needs to perform a commit confirmed set request as per: https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-commit-confirmed.md + +### commit-request + +The `--commit-request` flag is used together with the `--commit-id` flag to set the commit action to `Request`, essentially starting a commit request. + +### commit-confirm + +The `--commit-confirm` flag is used together with the `--commit-id` flag to confirm an already started commit confirmed transaction. + +### commit-cancel + +The `--commit-cancel` flag is used together with the `--commit-id` flag to cancel an already started commit confirmed transaction. + +### rollback-duration + +The `--rollback-duration` flag is used together with the `--commit-id` flag to set the rollback duration of a commit confirmed transaction either at creation time or before the previous commit rollback expires. + ## Update Request There are several ways to perform an update operation with gNMI Set RPC: diff --git a/pkg/api/gnmi_msgs.go b/pkg/api/gnmi_msgs.go index 6e6ef379..89796b0c 100644 --- a/pkg/api/gnmi_msgs.go +++ b/pkg/api/gnmi_msgs.go @@ -22,6 +22,7 @@ import ( gvalue "github.com/openconfig/gnmi/value" "github.com/openconfig/gnmic/pkg/api/path" "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/durationpb" ) const ( @@ -290,6 +291,115 @@ func Extension(ext *gnmi_ext.Extension) func(msg proto.Message) error { } } +func Extension_CommitRequest(id string, dur time.Duration) func(msg proto.Message) error { + return func(msg proto.Message) error { + if msg == nil { + return ErrInvalidMsgType + } + switch msg := msg.ProtoReflect().Interface().(type) { + case *gnmi.SetRequest: + fn := Extension( + &gnmi_ext.Extension{ + Ext: &gnmi_ext.Extension_Commit{ + Commit: &gnmi_ext.Commit{ + Id: id, + Action: &gnmi_ext.Commit_Commit{ + Commit: &gnmi_ext.CommitRequest{ + RollbackDuration: durationpb.New(dur), + }, + }, + }, + }, + }, + ) + return fn(msg) + default: + return fmt.Errorf("option Extension_CommitRequest: %w: %T", ErrInvalidMsgType, msg) + } + } +} + +func Extension_CommitConfirm(id string) func(msg proto.Message) error { + return func(msg proto.Message) error { + if msg == nil { + return ErrInvalidMsgType + } + switch msg := msg.ProtoReflect().Interface().(type) { + case *gnmi.SetRequest: + fn := Extension( + &gnmi_ext.Extension{ + Ext: &gnmi_ext.Extension_Commit{ + Commit: &gnmi_ext.Commit{ + Id: id, + Action: &gnmi_ext.Commit_Confirm{ + Confirm: &gnmi_ext.CommitConfirm{}, + }, + }, + }, + }, + ) + return fn(msg) + default: + return fmt.Errorf("option Extension_CommitConfirm: %w: %T", ErrInvalidMsgType, msg) + } + } +} + +func Extension_CommitCancel(id string) func(msg proto.Message) error { + return func(msg proto.Message) error { + if msg == nil { + return ErrInvalidMsgType + } + switch msg := msg.ProtoReflect().Interface().(type) { + case *gnmi.SetRequest: + fn := Extension( + &gnmi_ext.Extension{ + Ext: &gnmi_ext.Extension_Commit{ + Commit: &gnmi_ext.Commit{ + Id: id, + Action: &gnmi_ext.Commit_Cancel{ + Cancel: &gnmi_ext.CommitCancel{}, + }, + }, + }, + }, + ) + return fn(msg) + default: + return fmt.Errorf("option Extension_CommitCancel: %w: %T", ErrInvalidMsgType, msg) + } + } +} + +func Extension_CommitSetRollbackDuration(id string, dur time.Duration) func(msg proto.Message) error { + return func(msg proto.Message) error { + if msg == nil { + return ErrInvalidMsgType + } + + switch msg := msg.ProtoReflect().Interface().(type) { + case *gnmi.SetRequest: + fn := Extension( + &gnmi_ext.Extension{ + Ext: &gnmi_ext.Extension_Commit{ + Commit: &gnmi_ext.Commit{ + Id: id, + Action: &gnmi_ext.Commit_SetRollbackDuration{ + SetRollbackDuration: &gnmi_ext.CommitSetRollbackDuration{ + RollbackDuration: durationpb.New(dur), + }, + }, + }, + }, + }, + ) + return fn(msg) + default: + return fmt.Errorf("option Extension_CommitCancel: %w: %T", ErrInvalidMsgType, msg) + } + } +} + // Extension_HistorySnapshotTime creates a GNMIOption that adds a gNMI extension of // type History Snapshot with the supplied snapshot time. // the snapshot value can be nanoseconds since Unix epoch or a date in RFC3339 format diff --git a/pkg/app/set.go b/pkg/app/set.go index 21927749..ba089d8b 100644 --- a/pkg/app/set.go +++ b/pkg/app/set.go @@ -139,6 +139,12 @@ func (a *App) InitSetFlags(cmd *cobra.Command) { cmd.Flags().StringArrayVarP(&a.Config.LocalFlags.SetUpdateCli, "update-cli", "", []string{}, "a cli command to be sent as a set update request") cmd.Flags().StringVarP(&a.Config.LocalFlags.SetUpdateCliFile, "update-cli-file", "", "", "path to a file containing a list of commands that will be sent as a set update request") + cmd.Flags().StringVarP(&a.Config.LocalFlags.SetCommitId, "commit-id", "", "", "commit ID value") + cmd.Flags().BoolVarP(&a.Config.LocalFlags.SetCommitRequest, "commit-request", "", false, "start a commit confirmed transaction") + cmd.Flags().BoolVarP(&a.Config.LocalFlags.SetCommitConfirm, "commit-confirm", "", false, "confirm the commit ID") + cmd.Flags().BoolVarP(&a.Config.LocalFlags.SetCommitCancel, "commit-cancel", "", false, "cancel the commit") + cmd.Flags().DurationVarP(&a.Config.LocalFlags.SetCommitRollbackDuration, "rollback-duration", "", 0, "set the commit rollback duration") + cmd.LocalFlags().VisitAll(func(flag *pflag.Flag) { a.Config.FileConfig.BindPFlag(fmt.Sprintf("%s-%s", cmd.Name(), flag.Name), flag) }) diff --git a/pkg/config/config.go b/pkg/config/config.go index d4b2fd8a..8de190a4 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -135,30 +135,35 @@ type LocalFlags struct { GetValuesOnly bool `mapstructure:"get-values-only,omitempty" json:"get-values-only,omitempty" yaml:"get-values-only,omitempty"` GetProcessor []string `mapstructure:"get-processor,omitempty" json:"get-processor,omitempty" yaml:"get-processor,omitempty"` // Set - SetPrefix string `mapstructure:"set-prefix,omitempty" json:"set-prefix,omitempty" yaml:"set-prefix,omitempty"` - SetDelete []string `mapstructure:"set-delete,omitempty" json:"set-delete,omitempty" yaml:"set-delete,omitempty"` - SetReplace []string `mapstructure:"set-replace,omitempty" json:"set-replace,omitempty" yaml:"set-replace,omitempty"` - SetUnionReplace []string `mapstructure:"set-union-replace,omitempty" json:"set-union-replace,omitempty" yaml:"set-union-replace,omitempty"` - SetUpdate []string `mapstructure:"set-update,omitempty" json:"set-update,omitempty" yaml:"set-update,omitempty"` - SetReplacePath []string `mapstructure:"set-replace-path,omitempty" json:"set-replace-path,omitempty" yaml:"set-replace-path,omitempty"` - SetUpdatePath []string `mapstructure:"set-update-path,omitempty" json:"set-update-path,omitempty" yaml:"set-update-path,omitempty"` - SetReplaceFile []string `mapstructure:"set-replace-file,omitempty" json:"set-replace-file,omitempty" yaml:"set-replace-file,omitempty"` - SetUpdateFile []string `mapstructure:"set-update-file,omitempty" json:"set-update-file,omitempty" yaml:"set-update-file,omitempty"` - SetReplaceValue []string `mapstructure:"set-replace-value,omitempty" json:"set-replace-value,omitempty" yaml:"set-replace-value,omitempty"` - SetUpdateValue []string `mapstructure:"set-update-value,omitempty" json:"set-update-value,omitempty" yaml:"set-update-value,omitempty"` - SetUnionReplacePath []string `mapstructure:"set-union-replace-path,omitempty" yaml:"set-union-replace-path,omitempty" json:"set-union-replace-path,omitempty"` - SetUnionReplaceValue []string `mapstructure:"set-union-replace-value,omitempty" yaml:"set-union-replace-value,omitempty" json:"set-union-replace-value,omitempty"` - SetUnionReplaceFile []string `mapstructure:"set-union-replace-file,omitempty" yaml:"set-union-replace-file,omitempty" json:"set-union-replace-file,omitempty"` - SetDelimiter string `mapstructure:"set-delimiter,omitempty" json:"set-delimiter,omitempty" yaml:"set-delimiter,omitempty"` - SetTarget string `mapstructure:"set-target,omitempty" json:"set-target,omitempty" yaml:"set-target,omitempty"` - SetRequestFile []string `mapstructure:"set-request-file,omitempty" json:"set-request-file,omitempty" yaml:"set-request-file,omitempty"` - SetRequestVars string `mapstructure:"set-request-vars,omitempty" json:"set-request-vars,omitempty" yaml:"set-request-vars,omitempty"` - SetRequestProtoFile []string `mapstructure:"set-proto-request-file,omitempty" yaml:"set-proto-request-file,omitempty" json:"set-proto-request-file,omitempty"` - SetDryRun bool `mapstructure:"set-dry-run,omitempty" json:"set-dry-run,omitempty" yaml:"set-dry-run,omitempty"` - SetReplaceCli []string `mapstructure:"set-replace-cli,omitempty" yaml:"set-replace-cli,omitempty" json:"set-replace-cli,omitempty"` - SetReplaceCliFile string `mapstructure:"set-replace-cli-file,omitempty" yaml:"set-replace-cli-file,omitempty" json:"set-replace-cli-file,omitempty"` - SetUpdateCli []string `mapstructure:"set-update-cli,omitempty" yaml:"set-update-cli,omitempty" json:"set-update-cli,omitempty"` - SetUpdateCliFile string `mapstructure:"set-update-cli-file,omitempty" yaml:"set-update-cli-file,omitempty" json:"set-update-cli-file,omitempty"` + SetPrefix string `mapstructure:"set-prefix,omitempty" json:"set-prefix,omitempty" yaml:"set-prefix,omitempty"` + SetDelete []string `mapstructure:"set-delete,omitempty" json:"set-delete,omitempty" yaml:"set-delete,omitempty"` + SetReplace []string `mapstructure:"set-replace,omitempty" json:"set-replace,omitempty" yaml:"set-replace,omitempty"` + SetUnionReplace []string `mapstructure:"set-union-replace,omitempty" json:"set-union-replace,omitempty" yaml:"set-union-replace,omitempty"` + SetUpdate []string `mapstructure:"set-update,omitempty" json:"set-update,omitempty" yaml:"set-update,omitempty"` + SetReplacePath []string `mapstructure:"set-replace-path,omitempty" json:"set-replace-path,omitempty" yaml:"set-replace-path,omitempty"` + SetUpdatePath []string `mapstructure:"set-update-path,omitempty" json:"set-update-path,omitempty" yaml:"set-update-path,omitempty"` + SetReplaceFile []string `mapstructure:"set-replace-file,omitempty" json:"set-replace-file,omitempty" yaml:"set-replace-file,omitempty"` + SetUpdateFile []string `mapstructure:"set-update-file,omitempty" json:"set-update-file,omitempty" yaml:"set-update-file,omitempty"` + SetReplaceValue []string `mapstructure:"set-replace-value,omitempty" json:"set-replace-value,omitempty" yaml:"set-replace-value,omitempty"` + SetUpdateValue []string `mapstructure:"set-update-value,omitempty" json:"set-update-value,omitempty" yaml:"set-update-value,omitempty"` + SetUnionReplacePath []string `mapstructure:"set-union-replace-path,omitempty" yaml:"set-union-replace-path,omitempty" json:"set-union-replace-path,omitempty"` + SetUnionReplaceValue []string `mapstructure:"set-union-replace-value,omitempty" yaml:"set-union-replace-value,omitempty" json:"set-union-replace-value,omitempty"` + SetUnionReplaceFile []string `mapstructure:"set-union-replace-file,omitempty" yaml:"set-union-replace-file,omitempty" json:"set-union-replace-file,omitempty"` + SetDelimiter string `mapstructure:"set-delimiter,omitempty" json:"set-delimiter,omitempty" yaml:"set-delimiter,omitempty"` + SetTarget string `mapstructure:"set-target,omitempty" json:"set-target,omitempty" yaml:"set-target,omitempty"` + SetRequestFile []string `mapstructure:"set-request-file,omitempty" json:"set-request-file,omitempty" yaml:"set-request-file,omitempty"` + SetRequestVars string `mapstructure:"set-request-vars,omitempty" json:"set-request-vars,omitempty" yaml:"set-request-vars,omitempty"` + SetRequestProtoFile []string `mapstructure:"set-proto-request-file,omitempty" yaml:"set-proto-request-file,omitempty" json:"set-proto-request-file,omitempty"` + SetDryRun bool `mapstructure:"set-dry-run,omitempty" json:"set-dry-run,omitempty" yaml:"set-dry-run,omitempty"` + SetReplaceCli []string `mapstructure:"set-replace-cli,omitempty" yaml:"set-replace-cli,omitempty" json:"set-replace-cli,omitempty"` + SetReplaceCliFile string `mapstructure:"set-replace-cli-file,omitempty" yaml:"set-replace-cli-file,omitempty" json:"set-replace-cli-file,omitempty"` + SetUpdateCli []string `mapstructure:"set-update-cli,omitempty" yaml:"set-update-cli,omitempty" json:"set-update-cli,omitempty"` + SetUpdateCliFile string `mapstructure:"set-update-cli-file,omitempty" yaml:"set-update-cli-file,omitempty" json:"set-update-cli-file,omitempty"` + SetCommitId string `mapstructure:"set-commit-id,omitempty" yaml:"set-commit-id,omitempty" json:"set-commit-id,omitempty"` + SetCommitRequest bool `mapstructure:"set-commit-request,omitempty" yaml:"set-commit-request,omitempty" json:"set-commit-request,omitempty"` + SetCommitRollbackDuration time.Duration `mapstructure:"set-commit-rollback-duration,omitempty" yaml:"set-commit-rollback-duration,omitempty" json:"set-commit-rollback-duration,omitempty"` + SetCommitCancel bool `mapstructure:"set-commit-cancel,omitempty" yaml:"set-commit-cancel,omitempty" json:"set-commit-cancel,omitempty"` + SetCommitConfirm bool `mapstructure:"set-commit-confirm,omitempty" yaml:"set-commit-confirm,omitempty" json:"set-commit-confirm,omitempty"` // Sub SubscribePrefix string `mapstructure:"subscribe-prefix,omitempty" json:"subscribe-prefix,omitempty" yaml:"subscribe-prefix,omitempty"` SubscribePath []string `mapstructure:"subscribe-path,omitempty" json:"subscribe-path,omitempty" yaml:"subscribe-path,omitempty"` @@ -776,6 +781,32 @@ func (c *Config) CreateSetRequest(targetName string) ([]*gnmi.SetRequest, error) ), ) } + + if c.LocalFlags.SetCommitId != "" { + if c.LocalFlags.SetCommitRequest { + gnmiOpts = append(gnmiOpts, + api.Extension_CommitRequest( + c.LocalFlags.SetCommitId, + c.LocalFlags.SetCommitRollbackDuration, + )) + } else if c.LocalFlags.SetCommitConfirm { + gnmiOpts = append(gnmiOpts, + api.Extension_CommitConfirm( + c.LocalFlags.SetCommitId, + )) + } else if c.LocalFlags.SetCommitCancel { + gnmiOpts = append(gnmiOpts, + api.Extension_CommitCancel( + c.LocalFlags.SetCommitId, + )) + } else { + gnmiOpts = append(gnmiOpts, + api.Extension_CommitSetRollbackDuration( + c.LocalFlags.SetCommitId, + c.LocalFlags.SetCommitRollbackDuration, + )) + } + } // req, err := api.NewSetRequest(gnmiOpts...) return []*gnmi.SetRequest{req}, err @@ -870,7 +901,8 @@ func (c *Config) ValidateSetInput() error { len(c.LocalFlags.SetUpdateCli) == 0 && len(c.LocalFlags.SetReplaceCliFile) == 0 && len(c.LocalFlags.SetUpdateCliFile) == 0 && - len(c.LocalFlags.SetRequestProtoFile) == 0 { + len(c.LocalFlags.SetRequestProtoFile) == 0 && + c.LocalFlags.SetCommitId == "" { return errors.New("no paths or request file provided") } if len(c.LocalFlags.SetUpdateFile) > 0 && len(c.LocalFlags.SetUpdateValue) > 0 { diff --git a/pkg/config/set.go b/pkg/config/set.go index 2dec4451..251bcaac 100644 --- a/pkg/config/set.go +++ b/pkg/config/set.go @@ -18,6 +18,7 @@ import ( "path/filepath" "strings" "text/template" + "time" "google.golang.org/protobuf/encoding/prototext" "gopkg.in/yaml.v2" @@ -40,12 +41,24 @@ type UpdateItem struct { } type SetRequestFile struct { - Updates []*UpdateItem `json:"updates,omitempty" yaml:"updates,omitempty"` - Replaces []*UpdateItem `json:"replaces,omitempty" yaml:"replaces,omitempty"` - UnionReplaces []*UpdateItem `json:"union-replaces,omitempty" yaml:"union-replaces,omitempty"` - Deletes []string `json:"deletes,omitempty" yaml:"deletes,omitempty"` + Updates []*UpdateItem `json:"updates,omitempty" yaml:"updates,omitempty"` + Replaces []*UpdateItem `json:"replaces,omitempty" yaml:"replaces,omitempty"` + UnionReplaces []*UpdateItem `json:"union-replaces,omitempty" yaml:"union-replaces,omitempty"` + Deletes []string `json:"deletes,omitempty" yaml:"deletes,omitempty"` + CommitID string `yaml:"commit-id,omitempty" json:"commit-id,omitempty"` + CommitAction commitAction `yaml:"commit-action,omitempty" json:"commit-action,omitempty"` + RollbackDuration time.Duration `yaml:"rollback-duration,omitempty" json:"rollback-duration,omitempty"` } +type commitAction string + +const ( + commitActionRequest commitAction = "request" + commitActionCancel commitAction = "cancel" + commitActionConfirm commitAction = "confirm" + commitActionSetRollbackDuration commitAction = "set-rollback-duration" +) + func (c *Config) ReadSetRequestTemplate() error { if len(c.SetRequestFile) == 0 { return nil @@ -223,6 +236,35 @@ func (c *Config) CreateSetRequestFromFile(targetName string) ([]*gnmi.SetRequest gnmiOpts = append(gnmiOpts, api.Delete(strings.TrimSpace(s))) } + if reqFile.CommitID != "" { + switch reqFile.CommitAction { + case commitActionRequest: + gnmiOpts = append(gnmiOpts, + api.Extension_CommitRequest( + c.LocalFlags.SetCommitId, + c.LocalFlags.SetCommitRollbackDuration, + )) + case commitActionCancel: + gnmiOpts = append(gnmiOpts, + api.Extension_CommitCancel( + c.LocalFlags.SetCommitId, + )) + case commitActionConfirm: + gnmiOpts = append(gnmiOpts, + api.Extension_CommitConfirm( + c.LocalFlags.SetCommitId, + )) + case commitActionSetRollbackDuration: + gnmiOpts = append(gnmiOpts, + api.Extension_CommitSetRollbackDuration( + c.LocalFlags.SetCommitId, + c.LocalFlags.SetCommitRollbackDuration, + )) + default: + return nil, fmt.Errorf("unknown commit action %s", reqFile.CommitAction) + } + } + setReq, err := api.NewSetRequest(gnmiOpts...) if err != nil { return nil, err