Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restore actions #6

Merged
merged 1 commit into from
May 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ The end goal is to enable use-cases where multiple interactions with devices are
- [JSON (GPBKV): OpenConfig](#json-gpbkv-openconfig)
- [GPB (Protobuf)](#gpb-protobuf)
+ [Config and Validate](#config-and-validate)
+ [Actions](#actions)
- [Ping](#ping)
- [Traceroute](#traceroute)
- [Log Generation](#log-generation)
- [Crypto Key Generation](#crypto-key-generation)
+ [Bypass the config file](#bypass-the-config-file)
* [XR gRPC Config](#xr-grpc-config)
+ [Port range](#port-range)
Expand Down Expand Up @@ -729,8 +734,6 @@ telemetry model-driven

### Actions

>*NOTE*: Support for actions has been deprecated on XR, most likely in favor of gNOI.

There are multiple actions than can be triggered via gRPC on IOS XR devices running 6.3.1 or later. Below the YANG models supported to the date and some examples using this library.

```console
Expand Down
79 changes: 44 additions & 35 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"io"
"net"
"os"
"strconv"
"time"

pb "github.com/nleiva/xrgrpc/proto/ems"
Expand Down Expand Up @@ -163,10 +162,6 @@ func Connect(xr CiscoGrpcClient) (*grpc.ClientConn, context.Context, error) {
// Add TLS credentials to config options array.
opts = append(opts, grpc.WithTransportCredentials(creds))

// grpc.WithTimeout is deprecated
// opts = append(opts, grpc.WithTimeout(time.Millisecond*time.Duration(1500)))
// opts = append(opts, grpc.WithBlock())

// Add gRPC overall timeout to the config options array.
ctx, _ := context.WithTimeout(context.Background(), time.Second*time.Duration(xr.Timeout))

Expand All @@ -189,10 +184,6 @@ func ConnectInsecure(xr CiscoGrpcClient) (*grpc.ClientConn, context.Context, err
// opts holds the config options to set up the connection.
var opts []grpc.DialOption

// grpc.WithTimeout is deprecated
// opts = append(opts, grpc.WithTimeout(time.Millisecond*time.Duration(1500)))
// opts = append(opts, grpc.WithBlock())

// Add gRPC overall timeout to the config options array.
ctx, _ := context.WithTimeout(context.Background(), time.Second*time.Duration(xr.Timeout))

Expand Down Expand Up @@ -235,15 +226,44 @@ func ShowCmdTextOutput(ctx context.Context, conn *grpc.ClientConn, cli string, i
return s, nil
}
if len(r.GetErrors()) != 0 {
si := strconv.FormatInt(id, 10)
return s, fmt.Errorf("error triggered by remote host for ReqId: %s; %s", si, r.GetErrors())
return s, fmt.Errorf("error triggered by remote host for ReqId: %v; %s", id, r.GetErrors())
}
if len(r.GetOutput()) > 0 {
s += r.GetOutput()
}
}
}

// ActionJSON returns the output of an action commands as a JSON structured output.
func ActionJSON(ctx context.Context, conn *grpc.ClientConn, j string, id int64) (string, error) {
var s string
// 'c' is the gRPC stub.
c := pb.NewGRPCExecClient(conn)

// 'a' is the object we send to the router via the stub.
a := pb.ActionJSONArgs{ReqId: id, Yangpathjson: j}

// 'st' is the streamed result that comes back from the target.
st, err := c.ActionJSON(context.Background(), &a)
if err != nil {
return s, fmt.Errorf("gRPC ActionJSON failed: %w", err)
}

for {
// Loop through the responses in the stream until there is nothing left.
r, err := st.Recv()
if err == io.EOF {
return s, nil
}
if len(r.GetErrors()) != 0 {
return s, fmt.Errorf("error triggered by remote host for ReqId: %v; %s", id, r.GetErrors())
}
if len(r.GetYangjson()) > 0 {
s += r.GetYangjson()
}
}
}

// ShowCmdJSONOutput returns the output of a CLI show commands
// as a JSON structured output.
func ShowCmdJSONOutput(ctx context.Context, conn *grpc.ClientConn, cli string, id int64) (string, error) {
Expand All @@ -267,8 +287,7 @@ func ShowCmdJSONOutput(ctx context.Context, conn *grpc.ClientConn, cli string, i
return s, nil
}
if len(r.GetErrors()) != 0 {
si := strconv.FormatInt(id, 10)
return s, fmt.Errorf("error triggered by remote host for ReqId: %s; %s", si, r.GetErrors())
return s, fmt.Errorf("error triggered by remote host for ReqId: %v; %s", id, r.GetErrors())
}
if len(r.GetJsonoutput()) > 0 {
s += r.GetJsonoutput()
Expand Down Expand Up @@ -299,8 +318,7 @@ func GetConfig(ctx context.Context, conn *grpc.ClientConn, js string, id int64)
return s, nil
}
if len(r.GetErrors()) != 0 {
si := strconv.FormatInt(id, 10)
return s, fmt.Errorf("error triggered by remote host for ReqId: %s; %s", si, r.GetErrors())
return s, fmt.Errorf("error triggered by remote host for ReqId: %v; %s", id, r.GetErrors())
}
if len(r.GetYangjson()) > 0 {
s += r.GetYangjson()
Expand All @@ -322,41 +340,36 @@ func CLIConfig(ctx context.Context, conn *grpc.ClientConn, cli string, id int64)
return fmt.Errorf("gRPC CliConfig failed: %w", err)
}
if len(r.GetErrors()) != 0 {
si := strconv.FormatInt(id, 10)
return fmt.Errorf("error triggered by remote host for ReqId: %s; %s", si, r.GetErrors())
return fmt.Errorf("error triggered by remote host for ReqId: %v; %s", id, r.GetErrors())
}
return err
}

// CommitConfig commits a config. Need to clarify its use-case.
func CommitConfig(ctx context.Context, conn *grpc.ClientConn, cm [2]string, id int64) (string, error) {
func CommitConfig(ctx context.Context, conn *grpc.ClientConn, cm uint32, id int64) (string, error) {
var s string
// 'c' is the gRPC stub.
c := pb.NewGRPCConfigOperClient(conn)
si := strconv.FormatInt(id, 10)
// Commit metadata
m := pb.CommitMsg{Label: cm[0], Comment: cm[1]}

// 'a' is the object we send to the router via the stub.
a := pb.CommitArgs{Msg: &m, ReqId: id}
a := pb.CommitArgs{CommitID: cm, ReqId: id}

// 'r' is the result that comes back from the target.
r, err := c.CommitConfig(ctx, &a)
if err != nil {
return s, fmt.Errorf("gRPC CommitConfig failed: %w", err)
}
if len(r.GetErrors()) != 0 {
return s, fmt.Errorf("error triggered by remote host for ReqId: %s; %s", si, r.GetErrors())
return s, fmt.Errorf("error triggered by remote host for ReqId: %v; %s", id, r.GetErrors())
}
// What about r.ResReqId. Seems to equal to id sent.
return r.Result.String(), err
return r.String(), err
}

// CommitReplace issues a cli and JSON config to the target.
func CommitReplace(ctx context.Context, conn *grpc.ClientConn, cli, js string, id int64) error {
// 'c' is the gRPC stub.
c := pb.NewGRPCConfigOperClient(conn)
si := strconv.FormatInt(id, 10)

// 'a' is the object we send to the router via the stub.
a := pb.CommitReplaceArgs{Cli: cli, Yangjson: js, ReqId: id}
Expand All @@ -367,7 +380,7 @@ func CommitReplace(ctx context.Context, conn *grpc.ClientConn, cli, js string, i
return fmt.Errorf("gRPC CommitReplace failed: %w", err)
}
if len(r.GetErrors()) != 0 {
return fmt.Errorf("error triggered by remote host for ReqId: %s; %s", si, r.GetErrors())
return fmt.Errorf("error triggered by remote host for ReqId: %v; %s", id, r.GetErrors())
}
return err
}
Expand All @@ -386,8 +399,7 @@ func MergeConfig(ctx context.Context, conn *grpc.ClientConn, js string, id int64
return -1, fmt.Errorf("gRPC MergeConfig failed: %w", err)
}
if len(r.GetErrors()) != 0 {
si := strconv.FormatInt(id, 10)
return -1, fmt.Errorf("error triggered by remote host for ReqId: %s; %s", si, r.GetErrors())
return -1, fmt.Errorf("error triggered by remote host for ReqId: %v; %s", id, r.GetErrors())
}
return r.ResReqId, nil
}
Expand All @@ -407,8 +419,7 @@ func DeleteConfig(ctx context.Context, conn *grpc.ClientConn, js string, id int6
return -1, fmt.Errorf("gRPC DeleteConfig failed: %w", err)
}
if len(r.GetErrors()) != 0 {
si := strconv.FormatInt(id, 10)
return -1, fmt.Errorf("error triggered by remote host for ReqId: %s; %s", si, r.GetErrors())
return -1, fmt.Errorf("error triggered by remote host for ReqId: %v; %s", id, r.GetErrors())
}
return r.ResReqId, nil
}
Expand All @@ -428,8 +439,7 @@ func ReplaceConfig(ctx context.Context, conn *grpc.ClientConn, js string, id int
return -1, fmt.Errorf("gRPC ReplaceConfig failed: %w", err)
}
if len(r.GetErrors()) != 0 {
si := strconv.FormatInt(id, 10)
return -1, fmt.Errorf("error triggered by remote host for ReqId: %s; %s", si, r.GetErrors())
return -1, fmt.Errorf("error triggered by remote host for ReqId: %v; %s", id, r.GetErrors())
}
return r.ResReqId, nil
}
Expand All @@ -452,20 +462,19 @@ func GetSubscription(ctx context.Context, conn *grpc.ClientConn, p string, id in
if err != nil {
return b, e, fmt.Errorf("gRPC CreateSubs failed: %w", err)
}
si := strconv.FormatInt(id, 10)

// TODO: Review the logic. Make sure this goroutine ends and propagate
// error messages
go func() {
r, err := st.Recv()
if err != nil {
close(b)
e <- fmt.Errorf("error triggered by remote host: %s, ReqID: %s", err, si)
e <- fmt.Errorf("error triggered by remote host: %s, ReqID: %v", err, id)
return
}
if len(r.GetErrors()) != 0 {
close(b)
e <- fmt.Errorf("error triggered by remote host: %s, ReqID: %s", r.GetErrors(), si)
e <- fmt.Errorf("error triggered by remote host: %s, ReqID: %v", r.GetErrors(), id)
return
}
for {
Expand Down
Loading