Skip to content

Commit

Permalink
simplify Reboot operation and add RebootStatus
Browse files Browse the repository at this point in the history
  • Loading branch information
greg-dennis committed Feb 9, 2024
1 parent 87413fd commit 45068f1
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 147 deletions.
64 changes: 19 additions & 45 deletions system/system.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ import (

spb "github.com/openconfig/gnoi/system"
tpb "github.com/openconfig/gnoi/types"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"

"github.com/openconfig/gnoigo/internal"
)
Expand Down Expand Up @@ -155,9 +153,7 @@ func (p *PingOperation) Execute(ctx context.Context, c *internal.Clients) ([]*sp

// RebootOperation represents the parameters of a Reboot operation.
type RebootOperation struct {
req *spb.RebootRequest
ignoreUnavailableErr bool
waitForActive bool
req *spb.RebootRequest
}

// NewRebootOperation creates an empty RebootOperation.
Expand Down Expand Up @@ -195,52 +191,30 @@ func (r *RebootOperation) Force(force bool) *RebootOperation {
return r
}

// IgnoreUnavailableErr ignores unavailable errors returned by reboot status.
func (r *RebootOperation) IgnoreUnavailableErr(ignoreUnavailableErr bool) *RebootOperation {
r.ignoreUnavailableErr = ignoreUnavailableErr
return r
// Execute performs the Reboot operation.
func (r *RebootOperation) Execute(ctx context.Context, c *internal.Clients) (*spb.RebootResponse, error) {
return c.System().Reboot(ctx, r.req)
}

// WaitForActive waits until RebootResponse returns active.
func (r *RebootOperation) WaitForActive(waitForActive bool) *RebootOperation {
r.waitForActive = waitForActive
return r
// RebootStatusOperation represents the parameters of a RebootStatus operation.
type RebootStatusOperation struct {
req *spb.RebootStatusRequest
}

// Execute performs the Reboot operation and will wait for rebootStatus to be active if waitForActive is set to true.
func (r *RebootOperation) Execute(ctx context.Context, c *internal.Clients) (*spb.RebootResponse, error) {
resp, err := c.System().Reboot(ctx, r.req)

if err != nil {
return nil, err
}
// NewRebootStatusOperation creates an empty RebootStatusOperation.
func NewRebootStatusOperation() *RebootStatusOperation {
return &RebootStatusOperation{req: &spb.RebootStatusRequest{}}
}

if r.waitForActive {
for {
rebootStatus, statusErr := c.System().RebootStatus(ctx, &spb.RebootStatusRequest{Subcomponents: r.req.GetSubcomponents()})
var waitTime time.Duration

switch {
case status.Code(statusErr) == codes.Unavailable && r.ignoreUnavailableErr:
waitTime = 10 * time.Second
case statusErr != nil:
return nil, statusErr
case rebootStatus.GetActive():
return resp, nil
default:
waitTime = time.Duration(rebootStatus.GetWait()) * time.Second
}

select {
case <-ctx.Done():
return nil, ctx.Err()
case <-time.After(waitTime):
continue
}
}
}
// Subcomponents specifies the sub-components whose status will be checked.
func (r *RebootStatusOperation) Subcomponents(subcomponents []*tpb.Path) *RebootStatusOperation {
r.req.Subcomponents = subcomponents
return r
}

return resp, nil
// Execute performs the RebootStatus operation.
func (r *RebootStatusOperation) Execute(ctx context.Context, c *internal.Clients) (*spb.RebootStatusResponse, error) {
return c.System().RebootStatus(ctx, r.req)
}

// SwitchControlProcessorOperation represents the parameters of a SwitchControlProcessor operation.
Expand Down
168 changes: 66 additions & 102 deletions system/system_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ import (
"testing"

"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/testing/protocmp"

"github.com/google/go-cmp/cmp"
Expand Down Expand Up @@ -180,109 +178,75 @@ func TestPing(t *testing.T) {
}

func TestReboot(t *testing.T) {
tests := []struct {
desc string
op *system.RebootOperation
want *spb.RebootResponse
rebootErr, wantErr string
statusErrs []error
statusResps []*spb.RebootStatusResponse
cancelContext bool
}{
{
desc: "Test reboot",
op: system.NewRebootOperation().RebootMethod(spb.RebootMethod_COLD).Subcomponents([]*tpb.Path{{
Elem: []*tpb.PathElem{
{Name: "components"},
{Name: "component", Key: map[string]string{"name": "RP0"}},
},
}}),
want: &spb.RebootResponse{},
},
{
desc: "Test reboot wait for active status",
op: system.NewRebootOperation().RebootMethod(spb.RebootMethod_COLD).WaitForActive(true),
statusResps: []*spb.RebootStatusResponse{{Active: true}},
want: &spb.RebootResponse{},
},
{
desc: "Test reboot wait for active status and ignore unavailable error",
op: system.NewRebootOperation().RebootMethod(spb.RebootMethod_COLD).WaitForActive(true).IgnoreUnavailableErr(true),
statusErrs: []error{status.Errorf(codes.Unavailable, "unavailable")},
statusResps: []*spb.RebootStatusResponse{{Active: true}},
want: &spb.RebootResponse{},
},
{
desc: "Test reboot with non active status response",
op: system.NewRebootOperation().RebootMethod(spb.RebootMethod_COLD).WaitForActive(true),
statusResps: []*spb.RebootStatusResponse{{Active: false, Wait: 2}, {Active: true}},
want: &spb.RebootResponse{},
},
{
desc: "Test reboot wait for active status returns unknown error",
op: system.NewRebootOperation().RebootMethod(spb.RebootMethod_COLD).WaitForActive(true).IgnoreUnavailableErr(true),
statusErrs: []error{status.Errorf(codes.Unknown, "unknown")},
wantErr: "unknown",
fakeSys := &fakeSystemClient{}
fakeClients := &internal.Clients{SystemClient: fakeSys}
op := system.NewRebootOperation().RebootMethod(spb.RebootMethod_COLD).Subcomponents([]*tpb.Path{{
Elem: []*tpb.PathElem{
{Name: "components"},
{Name: "component", Key: map[string]string{"name": "RP0"}},
},
{
desc: "Test reboot returns error on reboot",
op: system.NewRebootOperation().RebootMethod(spb.RebootMethod_COLD),
rebootErr: "Reboot operation error",
wantErr: "Reboot operation error",
},
{
desc: "Test reboot returns error on reboot status",
op: system.NewRebootOperation().RebootMethod(spb.RebootMethod_COLD).WaitForActive(true),
statusErrs: []error{status.Errorf(codes.Unavailable, "unavailable")},
wantErr: "unavailable",
},
{
desc: "Test reboot with context cancel",
op: system.NewRebootOperation().RebootMethod(spb.RebootMethod_COLD).WaitForActive(true),
statusResps: []*spb.RebootStatusResponse{{Wait: 20}},
wantErr: "context",
cancelContext: true,
}})

t.Run("success", func(t *testing.T) {
want := &spb.RebootResponse{}
fakeSys.RebootFn = func(context.Context, *spb.RebootRequest, ...grpc.CallOption) (*spb.RebootResponse, error) {
return want, nil
}
got, gotErr := op.Execute(context.Background(), fakeClients)
if gotErr != nil {
t.Errorf("Execute() got error: %v", gotErr)
}
if want != got {
t.Errorf("Execute() got unexpected response want %v got %v", want, got)
}
})

t.Run("failure", func(t *testing.T) {
wantErr := "reboot error"
fakeSys.RebootFn = func(context.Context, *spb.RebootRequest, ...grpc.CallOption) (*spb.RebootResponse, error) {
return nil, fmt.Errorf(wantErr)
}
_, gotErr := op.Execute(context.Background(), fakeClients)
if gotErr == nil || !strings.Contains(gotErr.Error(), wantErr) {
t.Errorf("Execute() got error %v, want %s", gotErr, wantErr)
}
})
}

func TestRebootStatus(t *testing.T) {
fakeSys := &fakeSystemClient{}
fakeClients := &internal.Clients{SystemClient: fakeSys}
op := system.NewRebootStatusOperation().Subcomponents([]*tpb.Path{{
Elem: []*tpb.PathElem{
{Name: "components"},
{Name: "component", Key: map[string]string{"name": "RP0"}},
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
var fakeClient internal.Clients
fakeClient.SystemClient = &fakeSystemClient{
RebootFn: func(context.Context, *spb.RebootRequest, ...grpc.CallOption) (*spb.RebootResponse, error) {
if tt.rebootErr != "" {
return nil, fmt.Errorf(tt.rebootErr)
}
return tt.want, nil
},
RebootStatusFn: func(context.Context, *spb.RebootStatusRequest, ...grpc.CallOption) (*spb.RebootStatusResponse, error) {
if len(tt.statusErrs) > 0 {
statusErr := tt.statusErrs[0]
tt.statusErrs = tt.statusErrs[1:]
return nil, statusErr
}
if len(tt.statusResps) > 0 {
statusResp := tt.statusResps[0]
tt.statusResps = tt.statusResps[1:]
return statusResp, nil
}
return &spb.RebootStatusResponse{}, nil
}}

ctx, cancel := context.WithCancel(context.Background())
defer cancel()
if tt.cancelContext {
cancel()
}
}})

got, gotErr := tt.op.Execute(ctx, &fakeClient)
if (gotErr == nil) != (tt.wantErr == "") || (gotErr != nil && !strings.Contains(gotErr.Error(), tt.wantErr)) {
t.Errorf("Execute() got unexpected error %v want %s", gotErr, tt.wantErr)
}
if tt.want != got {
t.Errorf("Execute() got unexpected response want %v got %v", tt.want, got)
}
})
}
t.Run("success", func(t *testing.T) {
want := &spb.RebootStatusResponse{}
fakeSys.RebootStatusFn = func(context.Context, *spb.RebootStatusRequest, ...grpc.CallOption) (*spb.RebootStatusResponse, error) {
return want, nil
}
got, gotErr := op.Execute(context.Background(), fakeClients)
if gotErr != nil {
t.Errorf("Execute() got error: %v", gotErr)
}
if want != got {
t.Errorf("Execute() got unexpected response want %v got %v", want, got)
}
})

t.Run("failure", func(t *testing.T) {
wantErr := "reboot status error"
fakeSys.RebootStatusFn = func(context.Context, *spb.RebootStatusRequest, ...grpc.CallOption) (*spb.RebootStatusResponse, error) {
return nil, fmt.Errorf(wantErr)
}
_, gotErr := op.Execute(context.Background(), fakeClients)
if gotErr == nil || !strings.Contains(gotErr.Error(), wantErr) {
t.Errorf("Execute() got error %v, want %s", gotErr, wantErr)
}
})
}

func TestSwitchControlProcessor(t *testing.T) {
Expand Down

0 comments on commit 45068f1

Please sign in to comment.