open-traffic-generator / openapiart

OpenAPI artifact generator
MIT License
6 stars 4 forks source link

Add support for external grpc client #328

Closed ajbalogh closed 2 years ago

ajbalogh commented 2 years ago

Is your feature request related to a use case? Please describe. I'd like to file a FR for gosnappi to support creating a gRPC transport from a connection, currently you can only specify a "location" which is the end address without being able to attach any gRPC metadata or use a custom dialer (useful for going through a proxy) For example adding a method to the GrpcTransport interface (https://github.com/open-traffic-generator/snappi/blob/322667fca511b5f111ed0771bdebb5d28ff136f6/gosnappi/common.go#L22) such as SetClientConn (or similar) that takes a *grpc.ClientConn Is this possible? This blocks our automated traffic testing internally

Opened on behalf of alexmasi@google.com

ashutshkumr commented 2 years ago

@alakendu could you please take a first look ?

I'll be more willing to have a method which gives you the underlying transport object, so one can modify it like they would modify a usual grpc conn.

alexmasi commented 2 years ago

Here would be my preferred solution, consider the example for an Ondatra binding:

func (a *kneATE) DialOTG() (gosnappi.GosnappiApi, error) {
        ctx := context.Background()
    addr := "addr_of_proxy" // my custom proxy address
    opts := []*grpc.DialOptions{ /* my custom options required for the proxy chain */ }
    conn, err := grpc.DialContext(ctx, addr, opts...)
    if err != nil {
        return nil, err
    }

    api := gosnappi.NewApi()
    api.NewGrpcTransport().
        SetClient(otg.NewOpenapiClient(conn))
    return api, nil
}

Now the api object already has a non-nil grpcClient field and even the first method call would not need to create a new grpcClient. From my understanding of the flow if SetConfig was the first method call:

func (api *gosnappiApi) SetConfig(config Config) (ResponseWarning, error) {

    err := config.Validate()
    if err != nil {
        return nil, err
    }

    if api.hasHttpTransport() {
        return api.httpSetConfig(config)
    }

    if err := api.grpcConnect(); err != nil {  // normally this would dial the location, however if we created the GosnappiApi like above then this would be a no-op
        return nil, err
    }
    request := otg.SetConfigRequest{Config: config.Msg()}
    ctx, cancelFunc := context.WithTimeout(context.Background(), api.grpc.requestTimeout)
    defer cancelFunc()
    resp, err := api.grpcClient.SetConfig(ctx, &request)
    if err != nil {
        return nil, err
    }
    if resp.GetStatusCode_200() != nil {
        return NewResponseWarning().SetMsg(resp.GetStatusCode_200()), nil
    }
    if resp.GetStatusCode_400() != nil {
        data, _ := yaml.Marshal(resp.GetStatusCode_400())
        return nil, fmt.Errorf(string(data))
    }
    if resp.GetStatusCode_500() != nil {
        data, _ := yaml.Marshal(resp.GetStatusCode_500())
        return nil, fmt.Errorf(string(data))
    }
    return nil, fmt.Errorf("response of 200, 400, 500 has not been implemented")
}