diff --git a/configs/BUILD b/configs/BUILD index 62d0a4c46..f4a36f13e 100644 --- a/configs/BUILD +++ b/configs/BUILD @@ -7,6 +7,7 @@ go_library( "arista_default.textproto", "lemming_default.textproto", "nokia_default.textproto", + "fault_config.textproto", ], importpath = "github.com/openconfig/lemming/configs", visibility = ["//visibility:public"], diff --git a/configs/fault_config.textproto b/configs/fault_config.textproto new file mode 100644 index 000000000..652214f28 --- /dev/null +++ b/configs/fault_config.textproto @@ -0,0 +1,19 @@ +fault_config { + gnoi_faults { + rpc_method: "/gnoi.system.System/Reboot" + faults { + msg_id: "reboot_fault_1" + status { + code: 10 + message: "Simulated reboot failure - system maintenance mode" + } + } + faults { + msg_id: "reboot_fault_2" + status { + code: 14 + message: "Simulated reboot failure - resource temporarily unavailable" + } + } + } +} diff --git a/fault/BUILD b/fault/BUILD index bd062dd65..594b725b7 100644 --- a/fault/BUILD +++ b/fault/BUILD @@ -9,6 +9,7 @@ go_library( importpath = "github.com/openconfig/lemming/fault", visibility = ["//visibility:public"], deps = [ + "//proto/config", "//proto/fault", "@com_github_golang_glog//:glog", "@com_github_google_uuid//:uuid", @@ -23,13 +24,19 @@ go_library( go_test( name = "fault_test", - srcs = ["fault_test.go"], + srcs = [ + "fault_test.go", + "interceptor_test.go", + ], embed = [":fault"], deps = [ "//fault/proto/test", + "//proto/config", "//proto/fault", "@com_github_google_go_cmp//cmp", "@com_github_openconfig_gnmi//errdiff", + "@com_github_openconfig_gnoi//system", + "@org_golang_google_genproto_googleapis_rpc//status", "@org_golang_google_grpc//:grpc", "@org_golang_google_grpc//codes", "@org_golang_google_grpc//credentials/insecure", diff --git a/fault/config_fault/README.md b/fault/config_fault/README.md new file mode 100644 index 000000000..b49583254 --- /dev/null +++ b/fault/config_fault/README.md @@ -0,0 +1,96 @@ +# Config-Based Fault Injection + +## Overview + +Config-based fault injection allows you to pre-configure fault responses for specific gNOI RPC methods directly in the Lemming configuration file. This provides deterministic, reproducible fault scenarios for integration testing without requiring external fault injection clients. + +## Configuration + +### Basic Structure + +```protobuf +fault_config { + gnoi_faults { + rpc_method: "/gnoi.system.System/Reboot" + faults { + msg_id: "reboot_fault_1" + status { + code: 3 # INVALID_ARGUMENT + message: "Simulated reboot failure" + } + } + } +} +``` + +### Configuration Fields + +- **`rpc_method`**: Full gRPC method name (e.g., `/gnoi.system.System/Reboot`) +- **`faults`**: Array of fault messages to inject + - **`msg_id`**: Unique identifier for the fault + - **`msg`**: Optional modified request/response message + - **`status`**: gRPC status to return (code and message) + +## Fault Behavior + +### Exhaustible Application + +When multiple faults are configured for an RPC method, they are applied sequentially and then exhausted: + +```protobuf +gnoi_faults { + rpc_method: "/gnoi.system.System/Reboot" + faults { + msg_id: "fault_1" + status { code: 3 message: "First fault" } + } + faults { + msg_id: "fault_2" + status { code: 14 message: "Second fault" } + } +} +``` + +- 1st call → fault_1 (INVALID_ARGUMENT) +- 2nd call → fault_2 (UNAVAILABLE) +- 3rd call → **normal behavior** (continues normally) + +### Fault Types + +#### Status-Only Faults +Return an error without executing the RPC: + +```protobuf +faults { + msg_id: "permission_denied" + status { + code: 7 # PERMISSION_DENIED + message: "Access denied in maintenance mode" + } +} +``` + +#### Message Modification Faults +Modify the request before processing: + +```protobuf +faults { + msg_id: "modify_request" + msg { + # Encoded modified request message + type_url: "type.googleapis.com/gnoi.system.RebootRequest" + value: "..." # Modified request proto bytes + } + status { + code: 0 # OK - process with modified request + } +} +``` + +### Running Lemming with Fault Config + +```bash +# Start lemming with fault configuration +./lemming \ + --config_file=path_to_fault_config +``` diff --git a/fault/fault.go b/fault/fault.go index 9206fe500..9054e2e7f 100644 --- a/fault/fault.go +++ b/fault/fault.go @@ -29,22 +29,43 @@ import ( "github.com/google/uuid" + configpb "github.com/openconfig/lemming/proto/config" faultpb "github.com/openconfig/lemming/proto/fault" ) func NewInterceptor() *Interceptor { return &Interceptor{ - receivers: map[string]chan *faultMessage{}, - faultSubs: map[string]*faultSubscription{}, + receivers: map[string]chan *faultMessage{}, + faultSubs: map[string]*faultSubscription{}, + configuredFaults: map[string][]*faultpb.FaultMessage{}, } } +// NewInterceptorFromConfig creates a new interceptor configured with static faults from config +func NewInterceptorFromConfig(faultConfig *configpb.FaultServiceConfiguration) *Interceptor { + interceptor := NewInterceptor() + + if faultConfig != nil { + for _, gnoiFault := range faultConfig.GetGnoiFaults() { + if err := interceptor.configureFaults(gnoiFault.GetRpcMethod(), gnoiFault.GetFaults()); err != nil { + log.Warningf("Failed to configure faults for RPC method %s: %v", gnoiFault.GetRpcMethod(), err) + } + } + } + + return interceptor +} + type Interceptor struct { faultpb.UnimplementedFaultInjectServer faultSubsMu sync.Mutex faultSubs map[string]*faultSubscription receiversMu sync.Mutex receivers map[string]chan *faultMessage + // configuredFaults is a map of RPC methods to a slice of faults. + configuredFaults map[string][]*faultpb.FaultMessage + // Mutex for configured faults. + configMu sync.Mutex } type faultMessage struct { @@ -117,8 +138,90 @@ func (i *Interceptor) sendRecvFault(ch chan *faultMessage, rpcID string, msg any return res, recv.status.Err() } -// Unary implements the grpc unary server imterceptor interface, adding fault injection to unary RPCs. +// configureFaults sets pre-configured faults for a specific RPC method +func (i *Interceptor) configureFaults(rpcMethod string, faults []*faultpb.FaultMessage) error { + i.configMu.Lock() + defer i.configMu.Unlock() + + if rpcMethod == "" { + return status.Errorf(codes.InvalidArgument, "rpc_method cannot be empty") + } + + if len(faults) == 0 { + delete(i.configuredFaults, rpcMethod) + log.Infof("Removed fault configuration for RPC method: %s", rpcMethod) + return nil + } + + i.configuredFaults[rpcMethod] = faults + log.Infof("Configured %d faults for RPC method: %s", len(faults), rpcMethod) + return nil +} + +// nextConfiguredFault returns the next configured fault for an RPC method +func (i *Interceptor) nextConfiguredFault(rpcMethod string) *faultpb.FaultMessage { + i.configMu.Lock() + defer i.configMu.Unlock() + + faults, exists := i.configuredFaults[rpcMethod] + if !exists || len(faults) == 0 { + return nil + } + + // Consume the first fault from the slice + fault := faults[0] + i.configuredFaults[rpcMethod] = faults[1:] + + return fault +} + +// applyConfiguredFault applies a configured fault to the message +func (i *Interceptor) applyConfiguredFault(rpcMethod string, fault *faultpb.FaultMessage, originalMsg any, originalErr error) (any, error) { + if fault == nil { + return originalMsg, originalErr + } + + log.Infof("Applying configured fault for RPC %s: msg_id=%s", rpcMethod, fault.GetMsgId()) + + // If fault has a message, use it; otherwise use original. + if fault.GetMsg() != nil { + faultMsg, err := fault.GetMsg().UnmarshalNew() + if err == nil { + if fault.GetStatus() != nil { + // If status is OK, return modified message with no error + if fault.GetStatus().Code == 0 { + return faultMsg, nil + } + // Otherwise return the fault status error + return faultMsg, status.FromProto(fault.GetStatus()).Err() + } + // If no status provided, return modified message with default error + return faultMsg, status.Errorf(codes.Internal, "configured fault without status for %s", rpcMethod) + } + log.Warningf("Failed to unmarshal fault message for %s: %v", rpcMethod, err) + } + + // If fault only has status, return original message with fault status + if fault.GetStatus() != nil { + return originalMsg, status.FromProto(fault.GetStatus()).Err() + } + return originalMsg, status.Errorf(codes.Internal, "configured fault without status for %s", rpcMethod) +} + +// Unary implements the grpc unary server interceptor interface, adding fault injection to unary RPCs. func (i *Interceptor) Unary(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { + // Check for configured faults first + if fault := i.nextConfiguredFault(info.FullMethod); fault != nil { + modifiedReq, faultErr := i.applyConfiguredFault(info.FullMethod, fault, req, nil) + + // If fault has an error status, return immediately without calling handler. + if faultErr != nil { + return modifiedReq, faultErr + } + req = modifiedReq + } + + // Check for live fault subscriptions i.faultSubsMu.Lock() sub, ok := i.faultSubs[info.FullMethod] i.faultSubsMu.Unlock() @@ -165,12 +268,22 @@ func (si *streamInt) SendMsg(m any) error { return si.ServerStream.SendMsg(msg) } -// Stream implements the grpc strean server imterceptor interface, adding fault injection to streanubg RPCs. +// Stream implements the grpc stream server interceptor interface, adding fault injection to streaming RPCs. func (i *Interceptor) Stream(srv any, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { if info.FullMethod == "/lemming.fault.FaultInject/Intercept" { // Do not self-intercept return handler(srv, stream) } + // Check for configured faults first - for streaming, apply fault immediately + if fault := i.nextConfiguredFault(info.FullMethod); fault != nil { + log.Infof("Applying configured stream fault for RPC %s: msg_id=%s", info.FullMethod, fault.GetMsgId()) + if fault.GetStatus() != nil { + return status.FromProto(fault.GetStatus()).Err() + } + return status.Errorf(codes.Internal, "configured fault without status for %s", info.FullMethod) + } + + // Check for live fault subscriptions i.faultSubsMu.Lock() sub, ok := i.faultSubs[info.FullMethod] i.faultSubsMu.Unlock() diff --git a/fault/interceptor_test.go b/fault/interceptor_test.go new file mode 100644 index 000000000..3bf8c16f7 --- /dev/null +++ b/fault/interceptor_test.go @@ -0,0 +1,751 @@ +package fault + +import ( + "context" + "fmt" + "testing" + + statuspb "google.golang.org/genproto/googleapis/rpc/status" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/anypb" + + spb "github.com/openconfig/gnoi/system" + configpb "github.com/openconfig/lemming/proto/config" + faultpb "github.com/openconfig/lemming/proto/fault" +) + +const ( + rebootMethod = "/gnoi.system.System/Reboot" + pingMethod = "/gnoi.system.System/Ping" +) + +// Test helper to configure faults and handle errors +func mustConfigureFaults(t *testing.T, interceptor *Interceptor, rpcMethod string, faults []*faultpb.FaultMessage) { + t.Helper() + if err := interceptor.configureFaults(rpcMethod, faults); err != nil { + t.Fatalf("Failed to configure faults for %s: %v", rpcMethod, err) + } +} + +func TestInterceptorConfigureFaults(t *testing.T) { + interceptor := NewInterceptor() + faults := []*faultpb.FaultMessage{ + { + MsgId: "test_fault_1", + Status: &statuspb.Status{ + Code: int32(codes.Internal), + Message: "test error 1", + }, + }, + { + MsgId: "test_fault_2", + Status: &statuspb.Status{ + Code: int32(codes.Unavailable), + Message: "test error 2", + }, + }, + } + + mustConfigureFaults(t, interceptor, rebootMethod, faults) + + // Test fault selection order + tests := []struct { + name string + wantID string + wantNil bool + }{ + {"first fault", "test_fault_1", false}, + {"second fault", "test_fault_2", false}, + {"exhausted - first call", "", true}, + {"exhausted - second call", "", true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fault := interceptor.nextConfiguredFault(rebootMethod) + if tt.wantNil { + if fault != nil { + t.Errorf("expected nil fault, got %v", fault) + } + return + } + + if fault == nil { + t.Fatal("expected fault, got nil") + } + if got := fault.GetMsgId(); got != tt.wantID { + t.Errorf("expected fault ID %q, got %q", tt.wantID, got) + } + }) + } +} + +func TestInterceptorConfigureFaultsEmpty(t *testing.T) { + interceptor := NewInterceptor() + mustConfigureFaults(t, interceptor, rebootMethod, []*faultpb.FaultMessage{}) + + if fault := interceptor.nextConfiguredFault(rebootMethod); fault != nil { + t.Errorf("expected no fault after clearing, got %v", fault) + } +} + +func TestFaultBehavior(t *testing.T) { + interceptor := NewInterceptor() + faults := []*faultpb.FaultMessage{ + { + MsgId: "first_failure", + Status: &statuspb.Status{ + Code: int32(codes.PermissionDenied), + Message: "First failure", + }, + }, + { + MsgId: "second_failure", + Status: &statuspb.Status{ + Code: int32(codes.ResourceExhausted), + Message: "Second failure", + }, + }, + } + + mustConfigureFaults(t, interceptor, rebootMethod, faults) + + var handlerCallCount int + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + handlerCallCount++ + return &spb.RebootResponse{}, nil + } + + info := &grpc.UnaryServerInfo{FullMethod: rebootMethod} + req := &spb.RebootRequest{Method: spb.RebootMethod_COLD} + + tests := []struct { + name string + wantCode codes.Code + wantMessage string + wantHandlerCall bool + wantError bool + }{ + { + name: "first fault", + wantCode: codes.PermissionDenied, + wantMessage: "First failure", + wantError: true, + }, + { + name: "second fault", + wantCode: codes.ResourceExhausted, + wantMessage: "Second failure", + wantError: true, + }, + { + name: "pass through after exhaustion", + wantHandlerCall: true, + }, + { + name: "continue pass through", + wantHandlerCall: true, + }, + } + + expectedHandlerCalls := 0 + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp, err := interceptor.Unary(context.Background(), req, info, handler) + + if tt.wantError { + if err == nil { + t.Fatal("expected error") + } + st := status.Convert(err) + if st.Code() != tt.wantCode { + t.Errorf("expected code %v, got %v", tt.wantCode, st.Code()) + } + if st.Message() != tt.wantMessage { + t.Errorf("expected message %q, got %q", tt.wantMessage, st.Message()) + } + } else { + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if _, ok := resp.(*spb.RebootResponse); !ok { + t.Errorf("expected RebootResponse, got %T", resp) + } + } + + if tt.wantHandlerCall { + expectedHandlerCalls++ + } + if handlerCallCount != expectedHandlerCalls { + t.Errorf("expected %d handler calls, got %d", expectedHandlerCalls, handlerCallCount) + } + }) + } +} + +func TestSingleFaultExhaustion(t *testing.T) { + interceptor := NewInterceptor() + fault := &faultpb.FaultMessage{ + MsgId: "single_fault", + Status: &statuspb.Status{ + Code: int32(codes.Unavailable), + Message: "Single fault", + }, + } + + mustConfigureFaults(t, interceptor, pingMethod, []*faultpb.FaultMessage{fault}) + + var handlerCallCount int + handler := func(srv interface{}, stream grpc.ServerStream) error { + handlerCallCount++ + return nil + } + + info := &grpc.StreamServerInfo{FullMethod: pingMethod} + + // First call: should get the fault + err := interceptor.Stream(nil, nil, info, handler) + if err == nil { + t.Fatal("expected fault error") + } + + st := status.Convert(err) + if st.Code() != codes.Unavailable { + t.Errorf("expected Unavailable, got %v", st.Code()) + } + if handlerCallCount != 0 { + t.Error("handler should not be called on fault") + } + + // Second call: should pass through normally + if err := interceptor.Stream(nil, nil, info, handler); err != nil { + t.Errorf("expected success, got error: %v", err) + } + if handlerCallCount != 1 { + t.Errorf("expected 1 handler call, got %d", handlerCallCount) + } +} + +func TestFaultResetOnReconfiguration(t *testing.T) { + interceptor := NewInterceptor() + + // Configure initial fault + initialFault := &faultpb.FaultMessage{ + MsgId: "initial_fault", + Status: &statuspb.Status{ + Code: int32(codes.Internal), + Message: "Initial fault", + }, + } + mustConfigureFaults(t, interceptor, rebootMethod, []*faultpb.FaultMessage{initialFault}) + + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return &spb.RebootResponse{}, nil + } + info := &grpc.UnaryServerInfo{FullMethod: rebootMethod} + req := &spb.RebootRequest{Method: spb.RebootMethod_COLD} + + // Consume initial fault + if _, err := interceptor.Unary(context.Background(), req, info, handler); err == nil { + t.Fatal("expected fault error") + } + + // Verify fault is exhausted + if _, err := interceptor.Unary(context.Background(), req, info, handler); err != nil { + t.Errorf("expected success after exhaustion, got error: %v", err) + } + + // Reconfigure with new fault + newFault := &faultpb.FaultMessage{ + MsgId: "new_fault", + Status: &statuspb.Status{ + Code: int32(codes.FailedPrecondition), + Message: "New fault", + }, + } + mustConfigureFaults(t, interceptor, rebootMethod, []*faultpb.FaultMessage{newFault}) + + // Should get the new fault + _, err := interceptor.Unary(context.Background(), req, info, handler) + if err == nil { + t.Fatal("expected new fault error after reconfiguration") + } + + st := status.Convert(err) + if st.Code() != codes.FailedPrecondition { + t.Errorf("expected FailedPrecondition, got %v", st.Code()) + } + if st.Message() != "New fault" { + t.Errorf("expected 'New fault', got %q", st.Message()) + } + + // Should pass through after new fault exhausted + if _, err := interceptor.Unary(context.Background(), req, info, handler); err != nil { + t.Errorf("expected success after new fault exhaustion, got error: %v", err) + } +} + +func TestZeroFaultsConfiguration(t *testing.T) { + interceptor := NewInterceptor() + + // Configure then clear faults + fault := &faultpb.FaultMessage{ + MsgId: "will_be_cleared", + Status: &statuspb.Status{ + Code: int32(codes.Internal), + Message: "Will be cleared", + }, + } + mustConfigureFaults(t, interceptor, rebootMethod, []*faultpb.FaultMessage{fault}) + mustConfigureFaults(t, interceptor, rebootMethod, []*faultpb.FaultMessage{}) + + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return &spb.RebootResponse{}, nil + } + info := &grpc.UnaryServerInfo{FullMethod: rebootMethod} + req := &spb.RebootRequest{Method: spb.RebootMethod_COLD} + + if _, err := interceptor.Unary(context.Background(), req, info, handler); err != nil { + t.Errorf("expected success with no faults configured, got error: %v", err) + } +} + +func TestInterceptorUnaryWithConfiguredFaults(t *testing.T) { + interceptor := NewInterceptor() + fault := &faultpb.FaultMessage{ + MsgId: "reboot_denied", + Status: &statuspb.Status{ + Code: int32(codes.PermissionDenied), + Message: "Reboot not allowed", + }, + } + mustConfigureFaults(t, interceptor, rebootMethod, []*faultpb.FaultMessage{fault}) + + var handlerCalled bool + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + handlerCalled = true + return &spb.RebootResponse{}, nil + } + + info := &grpc.UnaryServerInfo{FullMethod: rebootMethod} + req := &spb.RebootRequest{Method: spb.RebootMethod_COLD} + + resp, err := interceptor.Unary(context.Background(), req, info, handler) + + if handlerCalled { + t.Error("handler should not have been called due to configured fault") + } + if err == nil { + t.Fatal("expected error from configured fault") + } + + st, ok := status.FromError(err) + if !ok { + t.Fatal("expected gRPC status error") + } + if st.Code() != codes.PermissionDenied { + t.Errorf("expected PermissionDenied, got %v", st.Code()) + } + if st.Message() != "Reboot not allowed" { + t.Errorf("expected 'Reboot not allowed', got %q", st.Message()) + } + + // Response should be the original request when fault has no message override + if resp != req { + t.Error("expected response to be original request when fault has no message") + } +} + +func TestInterceptorUnaryWithConfiguredFaultMessage(t *testing.T) { + interceptor := NewInterceptor() + + // Create modified request for the fault + faultReq := &spb.RebootRequest{ + Method: spb.RebootMethod_POWERUP, + Force: true, + } + faultReqAny, err := anypb.New(faultReq) + if err != nil { + t.Fatalf("failed to create Any message: %v", err) + } + + fault := &faultpb.FaultMessage{ + MsgId: "reboot_modified", + Msg: faultReqAny, + Status: &statuspb.Status{ + Code: int32(codes.OK), + Message: "", + }, + } + mustConfigureFaults(t, interceptor, rebootMethod, []*faultpb.FaultMessage{fault}) + + var receivedReq *spb.RebootRequest + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + receivedReq = req.(*spb.RebootRequest) + return &spb.RebootResponse{}, nil + } + + info := &grpc.UnaryServerInfo{FullMethod: rebootMethod} + originalReq := &spb.RebootRequest{Method: spb.RebootMethod_COLD} + + _, err = interceptor.Unary(context.Background(), originalReq, info, handler) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if receivedReq == nil { + t.Fatal("handler was not called") + } + if receivedReq.GetMethod() != spb.RebootMethod_POWERUP { + t.Errorf("expected modified method POWERUP, got %v", receivedReq.GetMethod()) + } + if !receivedReq.GetForce() { + t.Error("expected modified force=true") + } +} + +func TestInterceptorUnaryWithoutConfiguredFaults(t *testing.T) { + interceptor := NewInterceptor() + + var handlerCalled bool + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + handlerCalled = true + return &spb.RebootResponse{}, nil + } + + info := &grpc.UnaryServerInfo{FullMethod: rebootMethod} + req := &spb.RebootRequest{Method: spb.RebootMethod_COLD} + + resp, err := interceptor.Unary(context.Background(), req, info, handler) + + if !handlerCalled { + t.Error("handler should have been called when no faults configured") + } + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if _, ok := resp.(*spb.RebootResponse); !ok { + t.Errorf("expected RebootResponse, got %T", resp) + } +} + +func TestInterceptorUnaryWithModifiedRequest(t *testing.T) { + interceptor := NewInterceptor() + + // Create a modified request message + modifiedReq := &spb.RebootRequest{Method: spb.RebootMethod_WARM} + anyMsg, err := anypb.New(modifiedReq) + if err != nil { + t.Fatalf("Failed to create any message: %v", err) + } + + fault := &faultpb.FaultMessage{ + MsgId: "modify_request", + Msg: anyMsg, + Status: &statuspb.Status{ + Code: 0, // OK - should allow modified request to be processed + Message: "Modified request", + }, + } + mustConfigureFaults(t, interceptor, rebootMethod, []*faultpb.FaultMessage{fault}) + + var handlerCalled bool + var receivedReq *spb.RebootRequest + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + handlerCalled = true + if r, ok := req.(*spb.RebootRequest); ok { + receivedReq = r + } + return &spb.RebootResponse{}, nil + } + + info := &grpc.UnaryServerInfo{FullMethod: rebootMethod} + originalReq := &spb.RebootRequest{Method: spb.RebootMethod_COLD} + + resp, err := interceptor.Unary(context.Background(), originalReq, info, handler) + + if !handlerCalled { + t.Error("handler should have been called with modified request") + } + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if receivedReq == nil { + t.Error("handler should have received the modified request") + } + if receivedReq.Method != spb.RebootMethod_WARM { + t.Errorf("expected WARM reboot method, got %v", receivedReq.Method) + } + if _, ok := resp.(*spb.RebootResponse); !ok { + t.Errorf("expected RebootResponse, got %T", resp) + } +} + +func TestInterceptorUnaryWithModifiedRequestAndError(t *testing.T) { + interceptor := NewInterceptor() + + // Create a modified request message + modifiedReq := &spb.RebootRequest{Method: spb.RebootMethod_WARM} + anyMsg, err := anypb.New(modifiedReq) + if err != nil { + t.Fatalf("Failed to create any message: %v", err) + } + + fault := &faultpb.FaultMessage{ + MsgId: "modify_request_with_error", + Msg: anyMsg, + Status: &statuspb.Status{ + Code: int32(codes.PermissionDenied), // Non-zero code - should return error + Message: "Permission denied", + }, + } + mustConfigureFaults(t, interceptor, rebootMethod, []*faultpb.FaultMessage{fault}) + + var handlerCalled bool + handler := func(ctx context.Context, req any) (any, error) { + handlerCalled = true + return &spb.RebootResponse{}, nil + } + + info := &grpc.UnaryServerInfo{FullMethod: rebootMethod} + originalReq := &spb.RebootRequest{Method: spb.RebootMethod_COLD} + + resp, err := interceptor.Unary(context.Background(), originalReq, info, handler) + + if handlerCalled { + t.Error("handler should not have been called due to fault error") + } + if err == nil { + t.Fatal("expected error from fault") + } + + st := status.Convert(err) + if st.Code() != codes.PermissionDenied { + t.Errorf("expected PermissionDenied error, got %v", st.Code()) + } + if st.Message() != "Permission denied" { + t.Errorf("expected 'Permission denied' message, got %q", st.Message()) + } + if resp == nil { + t.Error("expected modified request as response") + } +} + +func TestInterceptorStreamWithConfiguredFaults(t *testing.T) { + interceptor := NewInterceptor() + fault := &faultpb.FaultMessage{ + MsgId: "stream_fault", + Status: &statuspb.Status{ + Code: int32(codes.Unavailable), + Message: "Stream unavailable", + }, + } + mustConfigureFaults(t, interceptor, pingMethod, []*faultpb.FaultMessage{fault}) + + var handlerCalled bool + handler := func(srv interface{}, stream grpc.ServerStream) error { + handlerCalled = true + return nil + } + + info := &grpc.StreamServerInfo{FullMethod: pingMethod} + + err := interceptor.Stream(nil, nil, info, handler) + + if handlerCalled { + t.Error("handler should not have been called due to configured fault") + } + if err == nil { + t.Fatal("expected error from configured stream fault") + } + + st, ok := status.FromError(err) + if !ok { + t.Fatal("expected gRPC status error") + } + if st.Code() != codes.Unavailable { + t.Errorf("expected Unavailable, got %v", st.Code()) + } +} + +func TestInterceptorStreamWithoutConfiguredFaults(t *testing.T) { + interceptor := NewInterceptor() + + var handlerCalled bool + handler := func(srv interface{}, stream grpc.ServerStream) error { + handlerCalled = true + return nil + } + + info := &grpc.StreamServerInfo{FullMethod: pingMethod} + + err := interceptor.Stream(nil, nil, info, handler) + + if !handlerCalled { + t.Error("handler should have been called when no faults configured") + } + if err != nil { + t.Errorf("unexpected error: %v", err) + } +} + +// TestConfigureFaultsWithNil tests edge case with nil faults slice +func TestConfigureFaultsWithNil(t *testing.T) { + interceptor := NewInterceptor() + + // Configure with nil should not panic + mustConfigureFaults(t, interceptor, rebootMethod, nil) + + // Should pass through normally + var handlerCalled bool + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + handlerCalled = true + return &spb.RebootResponse{}, nil + } + + info := &grpc.UnaryServerInfo{FullMethod: rebootMethod} + req := &spb.RebootRequest{Method: spb.RebootMethod_COLD} + + _, err := interceptor.Unary(context.Background(), req, info, handler) + + if !handlerCalled { + t.Error("handler should have been called with nil faults") + } + if err != nil { + t.Errorf("unexpected error: %v", err) + } +} + +// TestConfigureFaultsInvalidArgument tests error handling for invalid arguments +func TestConfigureFaultsInvalidArgument(t *testing.T) { + interceptor := NewInterceptor() + + // Test empty RPC method + err := interceptor.configureFaults("", []*faultpb.FaultMessage{ + {MsgId: "test", Status: &statuspb.Status{Code: int32(codes.Internal)}}, + }) + + if err == nil { + t.Fatal("expected error for empty RPC method") + } + + st, ok := status.FromError(err) + if !ok { + t.Fatal("expected gRPC status error") + } + + if st.Code() != codes.InvalidArgument { + t.Errorf("expected InvalidArgument, got %v", st.Code()) + } + + expectedMessage := "rpc_method cannot be empty" + if st.Message() != expectedMessage { + t.Errorf("expected error message %q, got %q", expectedMessage, st.Message()) + } +} + +// TestInterceptorConcurrency tests concurrent access to interceptor +func TestInterceptorConcurrency(t *testing.T) { + interceptor := NewInterceptor() + fault := &faultpb.FaultMessage{ + MsgId: "concurrent_fault", + Status: &statuspb.Status{ + Code: int32(codes.Internal), + Message: "Concurrent fault", + }, + } + + // Configure faults concurrently + const numGoroutines = 50 + done := make(chan bool, numGoroutines) + + for i := 0; i < numGoroutines; i++ { + go func(id int) { + defer func() { done <- true }() + + // Configure and use faults + method := fmt.Sprintf("/test.Service/Method%d", id%5) + mustConfigureFaults(t, interceptor, method, []*faultpb.FaultMessage{fault}) + + // Try to get next fault + _ = interceptor.nextConfiguredFault(method) + + // Clear faults + mustConfigureFaults(t, interceptor, method, []*faultpb.FaultMessage{}) + }(i) + } + + // Wait for all goroutines + for i := 0; i < numGoroutines; i++ { + <-done + } +} + +// TestNewInterceptorFromConfig tests the new constructor that loads config +func TestNewInterceptorFromConfig(t *testing.T) { + t.Parallel() + + // Test with nil config + interceptor := NewInterceptorFromConfig(nil) + if interceptor == nil { + t.Fatal("expected non-nil interceptor with nil config") + } + + // Should pass through normally with no config + var handlerCalled bool + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + handlerCalled = true + return &spb.RebootResponse{}, nil + } + + info := &grpc.UnaryServerInfo{FullMethod: rebootMethod} + req := &spb.RebootRequest{Method: spb.RebootMethod_COLD} + + _, err := interceptor.Unary(context.Background(), req, info, handler) + if err != nil { + t.Errorf("expected success with nil config, got error: %v", err) + } + if !handlerCalled { + t.Error("handler should have been called with nil config") + } + + // Test with actual config + faultConfig := &configpb.FaultServiceConfiguration{ + GnoiFaults: []*configpb.GNOIFaults{ + { + RpcMethod: rebootMethod, + Faults: []*faultpb.FaultMessage{ + { + MsgId: "config_fault", + Status: &statuspb.Status{ + Code: int32(codes.Internal), + Message: "Config-based fault", + }, + }, + }, + }, + }, + } + + interceptor = NewInterceptorFromConfig(faultConfig) + handlerCalled = false + + // Should inject the configured fault + _, err = interceptor.Unary(context.Background(), req, info, handler) + if err == nil { + t.Fatal("expected fault error from config") + } + + st := status.Convert(err) + if st.Code() != codes.Internal { + t.Errorf("expected Internal error, got %v", st.Code()) + } + if st.Message() != "Config-based fault" { + t.Errorf("expected 'Config-based fault', got %q", st.Message()) + } + if handlerCalled { + t.Error("handler should not have been called due to fault") + } +} diff --git a/internal/config/loader.go b/internal/config/loader.go index 009718aea..ffcf6ad36 100644 --- a/internal/config/loader.go +++ b/internal/config/loader.go @@ -30,6 +30,7 @@ import ( const ( VendorArista = "arista" VendorNokia = "nokia" + FaultConfig = "fault_config" ) var parseFromEmbeddedFn = parseFromEmbedded @@ -85,6 +86,8 @@ func determineConfigPath(flagValue string) (string, error) { case VendorArista, VendorNokia: // For known vendor presets, use the embedded config return fmt.Sprintf("embedded:%s_default.textproto", envConfigFile), nil + case FaultConfig: + return fmt.Sprintf("embedded:%s.textproto", envConfigFile), nil default: if _, err := os.Stat(envConfigFile); err == nil { return envConfigFile, nil @@ -107,6 +110,7 @@ func mergeWithDefaults(userConfig *configpb.Config) *configpb.Config { NetworkSimulation: defaultNetworkSimulation(), Interfaces: defaultInterfaces(), LinkQualification: defaultLinkQualification(), + FaultConfig: defaultFaultConfig(), } if userConfig == nil { @@ -141,6 +145,10 @@ func mergeWithDefaults(userConfig *configpb.Config) *configpb.Config { config.LinkQualification = userConfig.LinkQualification } + if userConfig.FaultConfig != nil { + config.FaultConfig = userConfig.FaultConfig + } + return config } @@ -241,6 +249,13 @@ func defaultLinkQualification() *configpb.LinkQualificationConfig { } } +// defaultFaultConfig returns default fault configuration +func defaultFaultConfig() *configpb.FaultServiceConfiguration { + return &configpb.FaultServiceConfiguration{ + GnoiFaults: []*configpb.GNOIFaults{}, + } +} + // parseFromEmbedded parses configuration from an embedded file func parseFromEmbedded(path string) (*configpb.Config, error) { data, err := configs.FS.ReadFile(path) @@ -323,6 +338,12 @@ func validate(config *configpb.Config) error { } } + if config.FaultConfig != nil { + if err := validateFaultConfig(config.FaultConfig); err != nil { + return fmt.Errorf("fault config validation failed: %v", err) + } + } + return nil } @@ -615,3 +636,61 @@ func validateCapabilityFields(lq *configpb.LinkQualificationConfig) error { return nil } + +// validateFaultConfig validates fault service configuration +func validateFaultConfig(faultConfig *configpb.FaultServiceConfiguration) error { + if faultConfig == nil { + return fmt.Errorf("fault config is nil") + } + + // Track RPC methods to prevent duplicates + rpcMethods := make(map[string]bool) + + for i, gnoiFault := range faultConfig.GetGnoiFaults() { + if gnoiFault.GetRpcMethod() == "" { + return fmt.Errorf("gnoi_faults[%d]: rpc_method is required", i) + } + + rpcMethod := gnoiFault.GetRpcMethod() + if rpcMethods[rpcMethod] { + return fmt.Errorf("gnoi_faults[%d]: duplicate rpc_method %q", i, rpcMethod) + } + rpcMethods[rpcMethod] = true + + // Validate RPC method format + if !isValidRPCMethod(rpcMethod) { + return fmt.Errorf("gnoi_faults[%d]: invalid rpc_method format %q, expected format: /service.Package/Method", i, rpcMethod) + } + + // Validate faults + if len(gnoiFault.GetFaults()) == 0 { + return fmt.Errorf("gnoi_faults[%d]: at least one fault must be specified for rpc_method %q", i, rpcMethod) + } + + for j, fault := range gnoiFault.GetFaults() { + if fault.GetMsgId() == "" { + return fmt.Errorf("gnoi_faults[%d].faults[%d]: msg_id is required", i, j) + } + } + } + + return nil +} + +// isValidRPCMethod validates the RPC method format: /package.service/Method +func isValidRPCMethod(method string) bool { + // Must start with "/" and contain exactly two parts split by "/" + if !strings.HasPrefix(method, "/") { + return false + } + + parts := strings.Split(method[1:], "/") + if len(parts) != 2 { + return false + } + + service, methodName := parts[0], parts[1] + + // Both service and method must be non-empty and contain a dot (package.service format) + return service != "" && methodName != "" && strings.Contains(service, ".") +} diff --git a/internal/config/loader_test.go b/internal/config/loader_test.go index 322627889..7fdd06989 100644 --- a/internal/config/loader_test.go +++ b/internal/config/loader_test.go @@ -1444,3 +1444,86 @@ func TestValidateLinkQualification(t *testing.T) { }) } } + +func TestIsValidRPCMethod(t *testing.T) { + tests := []struct { + name string + method string + want bool + }{ + { + name: "valid gnoi system method", + method: "/gnoi.system.System/Reboot", + want: true, + }, + { + name: "valid gnoi file method", + method: "/gnoi.file.File/Get", + want: true, + }, + { + name: "valid custom service", + method: "/com.example.service.MyService/DoSomething", + want: true, + }, + { + name: "missing leading slash", + method: "gnoi.system.System/Reboot", + want: false, + }, + { + name: "empty string", + method: "", + want: false, + }, + { + name: "only slash", + method: "/", + want: false, + }, + { + name: "missing service part", + method: "/Reboot", + want: false, + }, + { + name: "missing method part", + method: "/gnoi.system.System/", + want: false, + }, + { + name: "no package in service", + method: "/System/Reboot", + want: false, + }, + { + name: "empty service part", + method: "//Reboot", + want: false, + }, + { + name: "empty method part", + method: "/gnoi.system.System/", + want: false, + }, + { + name: "too many slashes", + method: "/gnoi.system.System/Reboot/Extra", + want: false, + }, + { + name: "single character parts", + method: "/a.b/C", + want: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := isValidRPCMethod(tt.method) + if got != tt.want { + t.Errorf("isValidRPCMethod(%q) = %v, want %v", tt.method, got, tt.want) + } + }) + } +} diff --git a/lemming.go b/lemming.go index 8c5b85c3e..4bf002903 100644 --- a/lemming.go +++ b/lemming.go @@ -272,8 +272,13 @@ func New(targetName, zapiURL string, opts ...Option) (*Device, error) { var faultService *gRPCService + // Initialize fault interceptor with static config + faultInt := fault.NewInterceptorFromConfig(lemmingConfig.GetFaultConfig()) + streamInt = append(streamInt, faultInt.Stream) + unaryInt = append(unaryInt, faultInt.Unary) + + // Only start fault service if explicitly enabled for external clients if resolvedOpts.faultInject { - faultInt := fault.NewInterceptor() l, err := net.Listen("tcp", resolvedOpts.faultAddr) if err != nil { return nil, err @@ -281,8 +286,6 @@ func New(targetName, zapiURL string, opts ...Option) (*Device, error) { srv := grpc.NewServer(grpc.Creds(creds)) faultpb.RegisterFaultInjectServer(srv, faultInt) - streamInt = append(streamInt, faultInt.Stream) - unaryInt = append(unaryInt, faultInt.Unary) faultService = &gRPCService{ lis: l, s: srv, diff --git a/proto/config/BUILD b/proto/config/BUILD index 8185c62d0..2558f1c81 100644 --- a/proto/config/BUILD +++ b/proto/config/BUILD @@ -6,6 +6,7 @@ proto_library( name = "config_proto", srcs = ["lemming_config.proto"], visibility = ["//visibility:public"], + deps = ["//proto/fault:fault_proto"], ) go_proto_library( @@ -13,6 +14,7 @@ go_proto_library( importpath = "github.com/openconfig/lemming/proto/config", proto = ":config_proto", visibility = ["//visibility:public"], + deps = ["//proto/fault"], ) go_library( diff --git a/proto/config/lemming_config.pb.go b/proto/config/lemming_config.pb.go index 70a91df41..38e8eb275 100644 --- a/proto/config/lemming_config.pb.go +++ b/proto/config/lemming_config.pb.go @@ -7,6 +7,7 @@ package config import ( + fault "github.com/openconfig/lemming/proto/fault" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" @@ -21,14 +22,15 @@ const ( ) type Config struct { - state protoimpl.MessageState `protogen:"open.v1"` - Components *ComponentConfig `protobuf:"bytes,1,opt,name=components,proto3" json:"components,omitempty"` - Processes *ProcessesConfig `protobuf:"bytes,2,opt,name=processes,proto3" json:"processes,omitempty"` - Timing *TimingConfig `protobuf:"bytes,3,opt,name=timing,proto3" json:"timing,omitempty"` - NetworkSimulation *NetworkSimConfig `protobuf:"bytes,4,opt,name=network_simulation,json=networkSimulation,proto3" json:"network_simulation,omitempty"` - Vendor *VendorConfig `protobuf:"bytes,5,opt,name=vendor,proto3" json:"vendor,omitempty"` - Interfaces *InterfaceConfig `protobuf:"bytes,6,opt,name=interfaces,proto3" json:"interfaces,omitempty"` - LinkQualification *LinkQualificationConfig `protobuf:"bytes,7,opt,name=link_qualification,json=linkQualification,proto3" json:"link_qualification,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Components *ComponentConfig `protobuf:"bytes,1,opt,name=components,proto3" json:"components,omitempty"` + Processes *ProcessesConfig `protobuf:"bytes,2,opt,name=processes,proto3" json:"processes,omitempty"` + Timing *TimingConfig `protobuf:"bytes,3,opt,name=timing,proto3" json:"timing,omitempty"` + NetworkSimulation *NetworkSimConfig `protobuf:"bytes,4,opt,name=network_simulation,json=networkSimulation,proto3" json:"network_simulation,omitempty"` + Vendor *VendorConfig `protobuf:"bytes,5,opt,name=vendor,proto3" json:"vendor,omitempty"` + Interfaces *InterfaceConfig `protobuf:"bytes,6,opt,name=interfaces,proto3" json:"interfaces,omitempty"` + LinkQualification *LinkQualificationConfig `protobuf:"bytes,7,opt,name=link_qualification,json=linkQualification,proto3" json:"link_qualification,omitempty"` + FaultConfig *FaultServiceConfiguration `protobuf:"bytes,8,opt,name=fault_config,json=faultConfig,proto3" json:"fault_config,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -112,6 +114,13 @@ func (x *Config) GetLinkQualification() *LinkQualificationConfig { return nil } +func (x *Config) GetFaultConfig() *FaultServiceConfiguration { + if x != nil { + return x.FaultConfig + } + return nil +} + type ProcessesConfig struct { state protoimpl.MessageState `protogen:"open.v1"` Process []*ProcessConfig `protobuf:"bytes,1,rep,name=process,proto3" json:"process,omitempty"` @@ -832,171 +841,285 @@ func (x *VendorConfig) GetOsVersion() string { return "" } +type GNOIFaults struct { + state protoimpl.MessageState `protogen:"open.v1"` + RpcMethod string `protobuf:"bytes,1,opt,name=rpc_method,json=rpcMethod,proto3" json:"rpc_method,omitempty"` + Faults []*fault.FaultMessage `protobuf:"bytes,2,rep,name=faults,proto3" json:"faults,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GNOIFaults) Reset() { + *x = GNOIFaults{} + mi := &file_proto_config_lemming_config_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GNOIFaults) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GNOIFaults) ProtoMessage() {} + +func (x *GNOIFaults) ProtoReflect() protoreflect.Message { + mi := &file_proto_config_lemming_config_proto_msgTypes[11] + 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 GNOIFaults.ProtoReflect.Descriptor instead. +func (*GNOIFaults) Descriptor() ([]byte, []int) { + return file_proto_config_lemming_config_proto_rawDescGZIP(), []int{11} +} + +func (x *GNOIFaults) GetRpcMethod() string { + if x != nil { + return x.RpcMethod + } + return "" +} + +func (x *GNOIFaults) GetFaults() []*fault.FaultMessage { + if x != nil { + return x.Faults + } + return nil +} + +type FaultServiceConfiguration struct { + state protoimpl.MessageState `protogen:"open.v1"` + GnoiFaults []*GNOIFaults `protobuf:"bytes,1,rep,name=gnoi_faults,json=gnoiFaults,proto3" json:"gnoi_faults,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FaultServiceConfiguration) Reset() { + *x = FaultServiceConfiguration{} + mi := &file_proto_config_lemming_config_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FaultServiceConfiguration) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FaultServiceConfiguration) ProtoMessage() {} + +func (x *FaultServiceConfiguration) ProtoReflect() protoreflect.Message { + mi := &file_proto_config_lemming_config_proto_msgTypes[12] + 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 FaultServiceConfiguration.ProtoReflect.Descriptor instead. +func (*FaultServiceConfiguration) Descriptor() ([]byte, []int) { + return file_proto_config_lemming_config_proto_rawDescGZIP(), []int{12} +} + +func (x *FaultServiceConfiguration) GetGnoiFaults() []*GNOIFaults { + if x != nil { + return x.GnoiFaults + } + return nil +} + var File_proto_config_lemming_config_proto protoreflect.FileDescriptor var file_proto_config_lemming_config_proto_rawDesc = []byte{ 0x0a, 0x21, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x6c, 0x65, 0x6d, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0e, 0x6c, 0x65, 0x6d, 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x22, 0xde, 0x03, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3f, - 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6c, 0x65, 0x6d, 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x52, 0x0a, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x12, - 0x3d, 0x0a, 0x09, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6c, 0x65, 0x6d, 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x73, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x34, - 0x0a, 0x06, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, + 0x66, 0x69, 0x67, 0x1a, 0x17, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x66, 0x61, 0x75, 0x6c, 0x74, + 0x2f, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xac, 0x04, 0x0a, + 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3f, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x70, 0x6f, + 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6c, 0x65, + 0x6d, 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x43, 0x6f, 0x6d, + 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0a, 0x63, 0x6f, + 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x3d, 0x0a, 0x09, 0x70, 0x72, 0x6f, 0x63, + 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6c, 0x65, + 0x6d, 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x50, 0x72, 0x6f, + 0x63, 0x65, 0x73, 0x73, 0x65, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09, 0x70, 0x72, + 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x34, 0x0a, 0x06, 0x74, 0x69, 0x6d, 0x69, 0x6e, + 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6c, 0x65, 0x6d, 0x6d, 0x69, 0x6e, + 0x67, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x12, 0x4f, 0x0a, + 0x12, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x73, 0x69, 0x6d, 0x75, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x6c, 0x65, 0x6d, 0x6d, + 0x69, 0x6e, 0x67, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, + 0x72, 0x6b, 0x53, 0x69, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x11, 0x6e, 0x65, 0x74, + 0x77, 0x6f, 0x72, 0x6b, 0x53, 0x69, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x34, + 0x0a, 0x06, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6c, 0x65, 0x6d, 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, - 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x74, 0x69, - 0x6d, 0x69, 0x6e, 0x67, 0x12, 0x4f, 0x0a, 0x12, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, - 0x73, 0x69, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x20, 0x2e, 0x6c, 0x65, 0x6d, 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x53, 0x69, 0x6d, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x52, 0x11, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x53, 0x69, 0x6d, 0x75, 0x6c, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x34, 0x0a, 0x06, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6c, 0x65, 0x6d, 0x6d, 0x69, 0x6e, 0x67, 0x2e, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x56, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x52, 0x06, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x12, 0x3f, 0x0a, 0x0a, 0x69, - 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1f, 0x2e, 0x6c, 0x65, 0x6d, 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x52, 0x0a, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x12, 0x56, 0x0a, 0x12, - 0x6c, 0x69, 0x6e, 0x6b, 0x5f, 0x71, 0x75, 0x61, 0x6c, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6c, 0x65, 0x6d, 0x6d, 0x69, - 0x6e, 0x67, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x4c, 0x69, 0x6e, 0x6b, 0x51, 0x75, - 0x61, 0x6c, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x52, 0x11, 0x6c, 0x69, 0x6e, 0x6b, 0x51, 0x75, 0x61, 0x6c, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x81, 0x01, 0x0a, 0x0f, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, - 0x65, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x37, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x63, - 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6c, 0x65, 0x6d, 0x6d, - 0x69, 0x6e, 0x67, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, - 0x73, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, - 0x73, 0x12, 0x35, 0x0a, 0x17, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x72, 0x65, 0x73, - 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6f, 0x6e, 0x5f, 0x6b, 0x69, 0x6c, 0x6c, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x14, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x73, 0x74, 0x61, - 0x72, 0x74, 0x4f, 0x6e, 0x4b, 0x69, 0x6c, 0x6c, 0x22, 0xd6, 0x02, 0x0a, 0x0f, 0x43, 0x6f, 0x6d, - 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x29, 0x0a, 0x10, - 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x31, 0x5f, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, - 0x6f, 0x72, 0x31, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x70, 0x65, 0x72, - 0x76, 0x69, 0x73, 0x6f, 0x72, 0x32, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0f, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x32, 0x4e, 0x61, - 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x68, 0x61, 0x73, 0x73, 0x69, 0x73, 0x5f, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x68, 0x61, 0x73, 0x73, 0x69, - 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x6c, 0x69, 0x6e, 0x65, 0x63, 0x61, 0x72, - 0x64, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, - 0x6c, 0x69, 0x6e, 0x65, 0x63, 0x61, 0x72, 0x64, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x23, - 0x0a, 0x0d, 0x66, 0x61, 0x62, 0x72, 0x69, 0x63, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x61, 0x62, 0x72, 0x69, 0x63, 0x50, 0x72, 0x65, - 0x66, 0x69, 0x78, 0x12, 0x3f, 0x0a, 0x08, 0x6c, 0x69, 0x6e, 0x65, 0x63, 0x61, 0x72, 0x64, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6c, 0x65, 0x6d, 0x6d, 0x69, 0x6e, 0x67, 0x2e, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, - 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, 0x6c, 0x69, 0x6e, 0x65, - 0x63, 0x61, 0x72, 0x64, 0x12, 0x3b, 0x0a, 0x06, 0x66, 0x61, 0x62, 0x72, 0x69, 0x63, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6c, 0x65, 0x6d, 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x63, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x54, - 0x79, 0x70, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x66, 0x61, 0x62, 0x72, 0x69, - 0x63, 0x22, 0x60, 0x0a, 0x13, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x54, 0x79, - 0x70, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, - 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, - 0x12, 0x0a, 0x04, 0x73, 0x74, 0x65, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x73, - 0x74, 0x65, 0x70, 0x22, 0x80, 0x02, 0x0a, 0x0d, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x70, 0x69, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x63, - 0x70, 0x75, 0x5f, 0x75, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x0c, 0x63, 0x70, 0x75, 0x55, 0x73, 0x61, 0x67, 0x65, 0x55, 0x73, 0x65, - 0x72, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x70, 0x75, 0x5f, 0x75, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x73, - 0x79, 0x73, 0x74, 0x65, 0x6d, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x63, 0x70, 0x75, - 0x55, 0x73, 0x61, 0x67, 0x65, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x27, 0x0a, 0x0f, 0x63, - 0x70, 0x75, 0x5f, 0x75, 0x74, 0x69, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x63, 0x70, 0x75, 0x55, 0x74, 0x69, 0x6c, 0x69, 0x7a, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x5f, 0x75, - 0x73, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x65, 0x6d, 0x6f, - 0x72, 0x79, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2d, 0x0a, 0x12, 0x6d, 0x65, 0x6d, 0x6f, 0x72, - 0x79, 0x5f, 0x75, 0x74, 0x69, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x0d, 0x52, 0x11, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55, 0x74, 0x69, 0x6c, 0x69, - 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xab, 0x01, 0x0a, 0x0c, 0x54, 0x69, 0x6d, 0x69, 0x6e, - 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x34, 0x0a, 0x16, 0x73, 0x77, 0x69, 0x74, 0x63, - 0x68, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, - 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68, 0x6f, - 0x76, 0x65, 0x72, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x12, 0x2c, 0x0a, - 0x12, 0x72, 0x65, 0x62, 0x6f, 0x6f, 0x74, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x72, 0x65, 0x62, 0x6f, 0x6f, - 0x74, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x12, 0x37, 0x0a, 0x18, 0x70, - 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x64, - 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x70, - 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x44, 0x65, 0x6c, - 0x61, 0x79, 0x4d, 0x73, 0x22, 0x4e, 0x0a, 0x0f, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, - 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3b, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x65, 0x72, - 0x66, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6c, 0x65, 0x6d, - 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x49, 0x6e, 0x74, 0x65, - 0x72, 0x66, 0x61, 0x63, 0x65, 0x53, 0x70, 0x65, 0x63, 0x52, 0x09, 0x69, 0x6e, 0x74, 0x65, 0x72, - 0x66, 0x61, 0x63, 0x65, 0x22, 0x60, 0x0a, 0x0d, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, - 0x65, 0x53, 0x70, 0x65, 0x63, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, - 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x69, - 0x66, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x69, - 0x66, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0xed, 0x03, 0x0a, 0x17, 0x4c, 0x69, 0x6e, 0x6b, 0x51, - 0x75, 0x61, 0x6c, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x12, 0x17, 0x0a, 0x07, 0x6d, 0x61, 0x78, 0x5f, 0x62, 0x70, 0x73, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x06, 0x6d, 0x61, 0x78, 0x42, 0x70, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x6d, - 0x61, 0x78, 0x5f, 0x70, 0x70, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6d, 0x61, - 0x78, 0x50, 0x70, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x6d, 0x69, 0x6e, 0x5f, 0x6d, 0x74, 0x75, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6d, 0x69, 0x6e, 0x4d, 0x74, 0x75, 0x12, 0x17, 0x0a, - 0x07, 0x6d, 0x61, 0x78, 0x5f, 0x6d, 0x74, 0x75, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, - 0x6d, 0x61, 0x78, 0x4d, 0x74, 0x75, 0x12, 0x34, 0x0a, 0x16, 0x6d, 0x61, 0x78, 0x5f, 0x68, 0x69, - 0x73, 0x74, 0x6f, 0x72, 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, - 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x14, 0x6d, 0x61, 0x78, 0x48, 0x69, 0x73, 0x74, 0x6f, - 0x72, 0x69, 0x63, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x31, 0x0a, 0x15, - 0x6d, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x74, 0x75, 0x70, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x6d, 0x69, 0x6e, - 0x53, 0x65, 0x74, 0x75, 0x70, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x12, - 0x37, 0x0a, 0x18, 0x6d, 0x69, 0x6e, 0x5f, 0x74, 0x65, 0x61, 0x72, 0x64, 0x6f, 0x77, 0x6e, 0x5f, - 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x15, 0x6d, 0x69, 0x6e, 0x54, 0x65, 0x61, 0x72, 0x64, 0x6f, 0x77, 0x6e, 0x44, 0x75, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x12, 0x33, 0x0a, 0x16, 0x6d, 0x69, 0x6e, 0x5f, - 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, - 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x6d, 0x69, 0x6e, 0x53, 0x61, 0x6d, - 0x70, 0x6c, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x4d, 0x73, 0x12, 0x2e, 0x0a, - 0x13, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x5f, - 0x72, 0x61, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x64, 0x65, 0x66, 0x61, - 0x75, 0x6c, 0x74, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x12, 0x2e, 0x0a, - 0x13, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x5f, - 0x73, 0x69, 0x7a, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x64, 0x65, 0x66, 0x61, - 0x75, 0x6c, 0x74, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x37, 0x0a, - 0x18, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x64, 0x75, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x15, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x54, 0x65, 0x73, 0x74, 0x44, 0x75, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x22, 0xdd, 0x01, 0x0a, 0x10, 0x4e, 0x65, 0x74, 0x77, 0x6f, - 0x72, 0x6b, 0x53, 0x69, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x26, 0x0a, 0x0f, 0x62, - 0x61, 0x73, 0x65, 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6d, 0x73, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x62, 0x61, 0x73, 0x65, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, - 0x79, 0x4d, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6a, - 0x69, 0x74, 0x74, 0x65, 0x72, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, - 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x4a, 0x69, 0x74, 0x74, 0x65, 0x72, 0x4d, 0x73, 0x12, - 0x28, 0x0a, 0x10, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x6c, 0x6f, 0x73, 0x73, 0x5f, 0x72, - 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0e, 0x70, 0x61, 0x63, 0x6b, 0x65, - 0x74, 0x4c, 0x6f, 0x73, 0x73, 0x52, 0x61, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x65, 0x66, - 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x74, 0x74, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, - 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x54, 0x74, 0x6c, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x61, - 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x45, 0x72, 0x72, - 0x6f, 0x72, 0x52, 0x61, 0x74, 0x65, 0x22, 0x57, 0x0a, 0x0c, 0x56, 0x65, 0x6e, 0x64, 0x6f, 0x72, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x6f, - 0x64, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x6f, 0x64, 0x65, 0x6c, - 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42, - 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70, - 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x6c, 0x65, 0x6d, 0x6d, 0x69, 0x6e, 0x67, - 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x56, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x76, 0x65, + 0x6e, 0x64, 0x6f, 0x72, 0x12, 0x3f, 0x0a, 0x0a, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, + 0x65, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6c, 0x65, 0x6d, 0x6d, 0x69, + 0x6e, 0x67, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, + 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0a, 0x69, 0x6e, 0x74, 0x65, 0x72, + 0x66, 0x61, 0x63, 0x65, 0x73, 0x12, 0x56, 0x0a, 0x12, 0x6c, 0x69, 0x6e, 0x6b, 0x5f, 0x71, 0x75, + 0x61, 0x6c, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x27, 0x2e, 0x6c, 0x65, 0x6d, 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x2e, 0x4c, 0x69, 0x6e, 0x6b, 0x51, 0x75, 0x61, 0x6c, 0x69, 0x66, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x11, 0x6c, 0x69, 0x6e, 0x6b, + 0x51, 0x75, 0x61, 0x6c, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x4c, 0x0a, + 0x0c, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x6c, 0x65, 0x6d, 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x46, 0x61, 0x75, 0x6c, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, + 0x66, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x81, 0x01, 0x0a, 0x0f, + 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, + 0x37, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1d, 0x2e, 0x6c, 0x65, 0x6d, 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, + 0x07, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x12, 0x35, 0x0a, 0x17, 0x64, 0x65, 0x66, 0x61, + 0x75, 0x6c, 0x74, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6f, 0x6e, 0x5f, 0x6b, + 0x69, 0x6c, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x64, 0x65, 0x66, 0x61, 0x75, + 0x6c, 0x74, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4f, 0x6e, 0x4b, 0x69, 0x6c, 0x6c, 0x22, + 0xd6, 0x02, 0x0a, 0x0f, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, + 0x72, 0x31, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, + 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x31, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x29, + 0x0a, 0x10, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x32, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, + 0x69, 0x73, 0x6f, 0x72, 0x32, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x68, 0x61, + 0x73, 0x73, 0x69, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x63, 0x68, 0x61, 0x73, 0x73, 0x69, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, + 0x6c, 0x69, 0x6e, 0x65, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6c, 0x69, 0x6e, 0x65, 0x63, 0x61, 0x72, 0x64, 0x50, + 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x61, 0x62, 0x72, 0x69, 0x63, 0x5f, + 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x61, + 0x62, 0x72, 0x69, 0x63, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x3f, 0x0a, 0x08, 0x6c, 0x69, + 0x6e, 0x65, 0x63, 0x61, 0x72, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6c, + 0x65, 0x6d, 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x43, 0x6f, + 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x52, 0x08, 0x6c, 0x69, 0x6e, 0x65, 0x63, 0x61, 0x72, 0x64, 0x12, 0x3b, 0x0a, 0x06, 0x66, + 0x61, 0x62, 0x72, 0x69, 0x63, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6c, 0x65, + 0x6d, 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x43, 0x6f, 0x6d, + 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x52, 0x06, 0x66, 0x61, 0x62, 0x72, 0x69, 0x63, 0x22, 0x60, 0x0a, 0x13, 0x43, 0x6f, 0x6d, 0x70, + 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, + 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x69, + 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x72, + 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x74, 0x65, 0x70, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x73, 0x74, 0x65, 0x70, 0x22, 0x80, 0x02, 0x0a, 0x0d, 0x50, + 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x70, + 0x69, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x63, 0x70, 0x75, 0x5f, 0x75, 0x73, 0x61, 0x67, 0x65, 0x5f, + 0x75, 0x73, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x63, 0x70, 0x75, 0x55, + 0x73, 0x61, 0x67, 0x65, 0x55, 0x73, 0x65, 0x72, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x70, 0x75, 0x5f, + 0x75, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x0e, 0x63, 0x70, 0x75, 0x55, 0x73, 0x61, 0x67, 0x65, 0x53, 0x79, 0x73, 0x74, + 0x65, 0x6d, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x70, 0x75, 0x5f, 0x75, 0x74, 0x69, 0x6c, 0x69, 0x7a, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x63, 0x70, 0x75, + 0x55, 0x74, 0x69, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x6d, + 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x5f, 0x75, 0x73, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x0b, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2d, + 0x0a, 0x12, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x5f, 0x75, 0x74, 0x69, 0x6c, 0x69, 0x7a, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x6d, 0x65, 0x6d, 0x6f, + 0x72, 0x79, 0x55, 0x74, 0x69, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xab, 0x01, + 0x0a, 0x0c, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x34, + 0x0a, 0x16, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x64, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, + 0x73, 0x77, 0x69, 0x74, 0x63, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x4d, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x72, 0x65, 0x62, 0x6f, 0x6f, 0x74, 0x5f, 0x64, + 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x10, 0x72, 0x65, 0x62, 0x6f, 0x6f, 0x74, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x4d, 0x73, 0x12, 0x37, 0x0a, 0x18, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x72, 0x65, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x6d, 0x73, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, + 0x74, 0x61, 0x72, 0x74, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x4d, 0x73, 0x22, 0x4e, 0x0a, 0x0f, 0x49, + 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3b, + 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x1d, 0x2e, 0x6c, 0x65, 0x6d, 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x53, 0x70, 0x65, 0x63, + 0x52, 0x09, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x22, 0x60, 0x0a, 0x0d, 0x49, + 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x53, 0x70, 0x65, 0x63, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x66, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x69, 0x66, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0xed, 0x03, + 0x0a, 0x17, 0x4c, 0x69, 0x6e, 0x6b, 0x51, 0x75, 0x61, 0x6c, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x17, 0x0a, 0x07, 0x6d, 0x61, 0x78, + 0x5f, 0x62, 0x70, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6d, 0x61, 0x78, 0x42, + 0x70, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x6d, 0x61, 0x78, 0x5f, 0x70, 0x70, 0x73, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x06, 0x6d, 0x61, 0x78, 0x50, 0x70, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x6d, + 0x69, 0x6e, 0x5f, 0x6d, 0x74, 0x75, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6d, 0x69, + 0x6e, 0x4d, 0x74, 0x75, 0x12, 0x17, 0x0a, 0x07, 0x6d, 0x61, 0x78, 0x5f, 0x6d, 0x74, 0x75, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6d, 0x61, 0x78, 0x4d, 0x74, 0x75, 0x12, 0x34, 0x0a, + 0x16, 0x6d, 0x61, 0x78, 0x5f, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x69, 0x63, 0x61, 0x6c, 0x5f, + 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x14, 0x6d, + 0x61, 0x78, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x69, 0x63, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x73, 0x12, 0x31, 0x0a, 0x15, 0x6d, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x74, 0x75, 0x70, + 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x12, 0x6d, 0x69, 0x6e, 0x53, 0x65, 0x74, 0x75, 0x70, 0x44, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x12, 0x37, 0x0a, 0x18, 0x6d, 0x69, 0x6e, 0x5f, 0x74, 0x65, + 0x61, 0x72, 0x64, 0x6f, 0x77, 0x6e, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x6d, 0x69, 0x6e, 0x54, 0x65, 0x61, + 0x72, 0x64, 0x6f, 0x77, 0x6e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x12, + 0x33, 0x0a, 0x16, 0x6d, 0x69, 0x6e, 0x5f, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x5f, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x13, 0x6d, 0x69, 0x6e, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, + 0x61, 0x6c, 0x4d, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, + 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x11, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, + 0x52, 0x61, 0x74, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, + 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x11, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, + 0x53, 0x69, 0x7a, 0x65, 0x12, 0x37, 0x0a, 0x18, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, + 0x74, 0x65, 0x73, 0x74, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x73, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x54, + 0x65, 0x73, 0x74, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x22, 0xdd, 0x01, + 0x0a, 0x10, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x53, 0x69, 0x6d, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x12, 0x26, 0x0a, 0x0f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x6e, + 0x63, 0x79, 0x5f, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x62, 0x61, 0x73, + 0x65, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x4d, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x61, + 0x74, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6a, 0x69, 0x74, 0x74, 0x65, 0x72, 0x5f, 0x6d, 0x73, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x4a, 0x69, + 0x74, 0x74, 0x65, 0x72, 0x4d, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, + 0x5f, 0x6c, 0x6f, 0x73, 0x73, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, + 0x52, 0x0e, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x4c, 0x6f, 0x73, 0x73, 0x52, 0x61, 0x74, 0x65, + 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x74, 0x74, 0x6c, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x54, 0x74, + 0x6c, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, + 0x72, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0f, 0x70, 0x61, + 0x63, 0x6b, 0x65, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x61, 0x74, 0x65, 0x22, 0x57, 0x0a, + 0x0c, 0x56, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x73, 0x5f, 0x76, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x73, 0x56, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x60, 0x0a, 0x0a, 0x47, 0x4e, 0x4f, 0x49, 0x46, 0x61, + 0x75, 0x6c, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x70, 0x63, 0x5f, 0x6d, 0x65, 0x74, 0x68, + 0x6f, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x70, 0x63, 0x4d, 0x65, 0x74, + 0x68, 0x6f, 0x64, 0x12, 0x33, 0x0a, 0x06, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6c, 0x65, 0x6d, 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x66, 0x61, + 0x75, 0x6c, 0x74, 0x2e, 0x46, 0x61, 0x75, 0x6c, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x52, 0x06, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x58, 0x0a, 0x19, 0x46, 0x61, 0x75, 0x6c, + 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3b, 0x0a, 0x0b, 0x67, 0x6e, 0x6f, 0x69, 0x5f, 0x66, 0x61, + 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x65, 0x6d, + 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x47, 0x4e, 0x4f, 0x49, + 0x46, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x52, 0x0a, 0x67, 0x6e, 0x6f, 0x69, 0x46, 0x61, 0x75, 0x6c, + 0x74, 0x73, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x6c, 0x65, 0x6d, 0x6d, + 0x69, 0x6e, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1011,19 +1134,22 @@ func file_proto_config_lemming_config_proto_rawDescGZIP() []byte { return file_proto_config_lemming_config_proto_rawDescData } -var file_proto_config_lemming_config_proto_msgTypes = make([]protoimpl.MessageInfo, 11) +var file_proto_config_lemming_config_proto_msgTypes = make([]protoimpl.MessageInfo, 13) var file_proto_config_lemming_config_proto_goTypes = []any{ - (*Config)(nil), // 0: lemming.config.Config - (*ProcessesConfig)(nil), // 1: lemming.config.ProcessesConfig - (*ComponentConfig)(nil), // 2: lemming.config.ComponentConfig - (*ComponentTypeConfig)(nil), // 3: lemming.config.ComponentTypeConfig - (*ProcessConfig)(nil), // 4: lemming.config.ProcessConfig - (*TimingConfig)(nil), // 5: lemming.config.TimingConfig - (*InterfaceConfig)(nil), // 6: lemming.config.InterfaceConfig - (*InterfaceSpec)(nil), // 7: lemming.config.InterfaceSpec - (*LinkQualificationConfig)(nil), // 8: lemming.config.LinkQualificationConfig - (*NetworkSimConfig)(nil), // 9: lemming.config.NetworkSimConfig - (*VendorConfig)(nil), // 10: lemming.config.VendorConfig + (*Config)(nil), // 0: lemming.config.Config + (*ProcessesConfig)(nil), // 1: lemming.config.ProcessesConfig + (*ComponentConfig)(nil), // 2: lemming.config.ComponentConfig + (*ComponentTypeConfig)(nil), // 3: lemming.config.ComponentTypeConfig + (*ProcessConfig)(nil), // 4: lemming.config.ProcessConfig + (*TimingConfig)(nil), // 5: lemming.config.TimingConfig + (*InterfaceConfig)(nil), // 6: lemming.config.InterfaceConfig + (*InterfaceSpec)(nil), // 7: lemming.config.InterfaceSpec + (*LinkQualificationConfig)(nil), // 8: lemming.config.LinkQualificationConfig + (*NetworkSimConfig)(nil), // 9: lemming.config.NetworkSimConfig + (*VendorConfig)(nil), // 10: lemming.config.VendorConfig + (*GNOIFaults)(nil), // 11: lemming.config.GNOIFaults + (*FaultServiceConfiguration)(nil), // 12: lemming.config.FaultServiceConfiguration + (*fault.FaultMessage)(nil), // 13: lemming.fault.FaultMessage } var file_proto_config_lemming_config_proto_depIdxs = []int32{ 2, // 0: lemming.config.Config.components:type_name -> lemming.config.ComponentConfig @@ -1033,15 +1159,18 @@ var file_proto_config_lemming_config_proto_depIdxs = []int32{ 10, // 4: lemming.config.Config.vendor:type_name -> lemming.config.VendorConfig 6, // 5: lemming.config.Config.interfaces:type_name -> lemming.config.InterfaceConfig 8, // 6: lemming.config.Config.link_qualification:type_name -> lemming.config.LinkQualificationConfig - 4, // 7: lemming.config.ProcessesConfig.process:type_name -> lemming.config.ProcessConfig - 3, // 8: lemming.config.ComponentConfig.linecard:type_name -> lemming.config.ComponentTypeConfig - 3, // 9: lemming.config.ComponentConfig.fabric:type_name -> lemming.config.ComponentTypeConfig - 7, // 10: lemming.config.InterfaceConfig.interface:type_name -> lemming.config.InterfaceSpec - 11, // [11:11] is the sub-list for method output_type - 11, // [11:11] is the sub-list for method input_type - 11, // [11:11] is the sub-list for extension type_name - 11, // [11:11] is the sub-list for extension extendee - 0, // [0:11] is the sub-list for field type_name + 12, // 7: lemming.config.Config.fault_config:type_name -> lemming.config.FaultServiceConfiguration + 4, // 8: lemming.config.ProcessesConfig.process:type_name -> lemming.config.ProcessConfig + 3, // 9: lemming.config.ComponentConfig.linecard:type_name -> lemming.config.ComponentTypeConfig + 3, // 10: lemming.config.ComponentConfig.fabric:type_name -> lemming.config.ComponentTypeConfig + 7, // 11: lemming.config.InterfaceConfig.interface:type_name -> lemming.config.InterfaceSpec + 13, // 12: lemming.config.GNOIFaults.faults:type_name -> lemming.fault.FaultMessage + 11, // 13: lemming.config.FaultServiceConfiguration.gnoi_faults:type_name -> lemming.config.GNOIFaults + 14, // [14:14] is the sub-list for method output_type + 14, // [14:14] is the sub-list for method input_type + 14, // [14:14] is the sub-list for extension type_name + 14, // [14:14] is the sub-list for extension extendee + 0, // [0:14] is the sub-list for field type_name } func init() { file_proto_config_lemming_config_proto_init() } @@ -1055,7 +1184,7 @@ func file_proto_config_lemming_config_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proto_config_lemming_config_proto_rawDesc, NumEnums: 0, - NumMessages: 11, + NumMessages: 13, NumExtensions: 0, NumServices: 0, }, diff --git a/proto/config/lemming_config.proto b/proto/config/lemming_config.proto index 31ed4863f..6d1bf5fe5 100644 --- a/proto/config/lemming_config.proto +++ b/proto/config/lemming_config.proto @@ -2,6 +2,8 @@ syntax = "proto3"; package lemming.config; +import "proto/fault/fault.proto"; + option go_package = "github.com/openconfig/lemming/proto/config"; // Main configuration message for lemming fake device @@ -20,6 +22,8 @@ message Config { InterfaceConfig interfaces = 6; // Link qualification configuration LinkQualificationConfig link_qualification = 7; + // Fault service configuration + FaultServiceConfiguration fault_config = 8; } // Container for process configuration @@ -153,3 +157,17 @@ message VendorConfig { // Operating system version string os_version = 3; } + +// Configuration for gNOI fault injection +message GNOIFaults { + // gRPC method name (e.g., "/gnoi.system.System/Reboot") + string rpc_method = 1; + // Fault messages to inject for this RPC method + repeated lemming.fault.FaultMessage faults = 2; +} + +// Configuration for fault service +message FaultServiceConfiguration { + // List of gNOI fault configurations + repeated GNOIFaults gnoi_faults = 1; +}